diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..09e6018f85 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.mdx text eol=lf diff --git a/.github/.probe/writer.txt b/.github/.probe/writer.txt new file mode 100644 index 0000000000..31a0b6ebc8 --- /dev/null +++ b/.github/.probe/writer.txt @@ -0,0 +1,16 @@ +You are helpful senior technical writer. +Your role is to automatically assist with GitHub issues. + +Before jumping on the task or replying, you first should analyze the user request or issue details thoroughly. + +When responding: +1. Be concise but thorough in your responses. +2. If the issue description is unclear, ask clarifying questions. +3. Request any additional information you might need to better assist +4. Provide helpful information related to the query +5. Try to provide an elegant and concise solution. +6. If there are multiple different solutions or next steps, convey it in the response +7. If solution is clear, you can jump to implementation right away, if not, you can ask user a clarification question, by calling attempt_completion tool, with required details. + +When writing content: +Don’t use title case. Following sentence case conventions, like in Google and GitHub style guides. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f6faee6938 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/scripts/sync-versions-updater.py b/.github/scripts/sync-versions-updater.py new file mode 100644 index 0000000000..96c3d4b62e --- /dev/null +++ b/.github/scripts/sync-versions-updater.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 + +import argparse +import json +import re +import sys +import urllib.error +import urllib.request +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +VERSION_TAG = re.compile(r"^v(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$") +PINNED_VERSION = r"v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?" +USER_AGENT = "tyk-version-updater/1.0" + + +COMPONENTS = { + "dashboard": { + "repo": "tykio/tyk-dashboard", + "allow_prerelease": False, + }, + "gateway_ee": { + "repo": "tykio/tyk-gateway-ee", + "allow_prerelease": False, + }, + "pump": { + "repo": "tykio/tyk-pump-docker-pub", + "allow_prerelease": False, + }, + "portal": { + "repo": "tykio/portal", + "allow_prerelease": False, + }, + "ai_studio": { + "repo": "tykio/tyk-ai-studio", + "allow_prerelease": True, + }, + "microgateway": { + "repo": "tykio/tyk-microgateway", + "allow_prerelease": True, + }, +} + + +def fetch_json(url: str) -> dict: + req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=30) as response: + return json.load(response) + + +def prerelease_sort_key(prerelease: str) -> tuple: + parts = [] + for part in prerelease.split("."): + number_match = re.search(r"\d+", part) + prefix = part[: number_match.start()] if number_match else part + number = int(number_match.group()) if number_match else -1 + suffix = part[number_match.end() :] if number_match else "" + parts.append((prefix, number, suffix)) + return tuple(parts) + + +def tag_sort_key(tag: str) -> tuple: + match = VERSION_TAG.match(tag) + if not match: + raise ValueError(f"Invalid version tag: {tag}") + + major, minor, patch, prerelease = match.groups() + base = tuple(int(part) for part in (major, minor, patch)) + if prerelease is None: + return (*base, 1, ()) + return (*base, 0, prerelease_sort_key(prerelease)) + + +def latest_tag(image_repo: str, allow_prerelease: bool) -> str: + namespace, repo = image_repo.split("/", 1) + url = ( + f"https://hub.docker.com/v2/namespaces/{namespace}/repositories/" + f"{repo}/tags?page_size=100" + ) + version_tags = [] + + # Docker Hub orders tags by last push, so the newest release is always on + # the first page; scanning further pages would only matter if 100+ tags + # were pushed after it. + payload = fetch_json(url) + for result in payload.get("results", []): + name = result.get("name", "") + match = VERSION_TAG.match(name) + if not match: + continue + prerelease = match.group(4) + if prerelease and not allow_prerelease: + continue + if prerelease and not prerelease.startswith("rc"): + continue + version_tags.append((tag_sort_key(name), name)) + + if not version_tags: + release_type = "version" if allow_prerelease else "stable version" + raise RuntimeError(f"No {release_type} tags found for {image_repo}") + + version_tags.sort() + return version_tags[-1][1] + + +def replace_pattern( + text: str, + pattern: str, + replacement: str, + path: Path, + label: str, + expected_count: int = 1, +) -> str: + updated_text, count = re.subn(pattern, replacement, text, flags=re.MULTILINE) + if count != expected_count: + raise RuntimeError( + f"{path}: expected {expected_count} replacement(s) for {label}, got {count}" + ) + return updated_text + + +def render_file(path: Path, replacements: list[tuple[str, str, str]]) -> str: + original = path.read_text() + updated = original + for pattern, replacement, label in replacements: + updated = replace_pattern(updated, pattern, replacement, path, label) + return updated + + +def render_replacements( + replacements: dict[Path, list[tuple[str, str, str]]], +) -> dict[Path, str]: + return { + path: render_file(path, file_replacements) + for path, file_replacements in replacements.items() + } + + +def build_replacements(versions: dict[str, str]) -> dict[Path, list[tuple[str, str, str]]]: + return { + REPO_ROOT / "docker/self-managed/.env.example": [ + (r"^(DASHBOARD_VERSION=).+$", rf"\g<1>{versions['dashboard']}", "dashboard env"), + (r"^(GATEWAY_VERSION=).+$", rf"\g<1>{versions['gateway_ee']}", "gateway env"), + (r"^(PUMP_VERSION=).+$", rf"\g<1>{versions['pump']}", "pump env"), + (r"^(PORTAL_VERSION=).+$", rf"\g<1>{versions['portal']}", "portal env"), + ], + REPO_ROOT / "docker/hybrid/.env.example": [ + (r"^(GATEWAY_VERSION=).+$", rf"\g<1>{versions['gateway_ee']}", "gateway env"), + (r"^(PUMP_VERSION=).+$", rf"\g<1>{versions['pump']}", "pump env"), + ], + REPO_ROOT / "docker/self-managed/docker-compose.yml": [ + ( + r"(tykio/tyk-dashboard:\$\{DASHBOARD_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['dashboard']}\2", + "dashboard fallback", + ), + ( + r"(tykio/tyk-gateway-ee:\$\{GATEWAY_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['gateway_ee']}\2", + "gateway fallback", + ), + ( + r"(tykio/tyk-pump-docker-pub:\$\{PUMP_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['pump']}\2", + "pump fallback", + ), + ( + r"(tykio/portal:\$\{PORTAL_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['portal']}\2", + "portal fallback", + ), + ], + REPO_ROOT / "docker/hybrid/docker-compose.yml": [ + ( + r"(tykio/tyk-gateway-ee:\$\{GATEWAY_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['gateway_ee']}\2", + "gateway fallback", + ), + ( + r"(tykio/tyk-pump-docker-pub:\$\{PUMP_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['pump']}\2", + "pump fallback", + ), + ], + REPO_ROOT / "docker/self-managed/README.md": [ + (r"^(DASHBOARD_VERSION=).+$", rf"\g<1>{versions['dashboard']}", "dashboard readme"), + (r"^(GATEWAY_VERSION=).+$", rf"\g<1>{versions['gateway_ee']}", "gateway readme"), + (r"^(PUMP_VERSION=).+$", rf"\g<1>{versions['pump']}", "pump readme"), + (r"^(PORTAL_VERSION=).+$", rf"\g<1>{versions['portal']}", "portal readme"), + ( + r"(tykio/portal:\$\{PORTAL_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['portal']}\2", + "portal image example", + ), + ], + REPO_ROOT / "docker/hybrid/README.md": [ + (r"^(GATEWAY_VERSION=).+$", rf"\g<1>{versions['gateway_ee']}", "gateway readme"), + (r"^(PUMP_VERSION=).+$", rf"\g<1>{versions['pump']}", "pump readme"), + ], + REPO_ROOT / "docker/ai-studio/.env.example": [ + (r"^(AI_STUDIO_VERSION=).+$", rf"\g<1>{versions['ai_studio']}", "ai studio env"), + ( + r"^(MICROGATEWAY_VERSION=).+$", + rf"\g<1>{versions['microgateway']}", + "microgateway env", + ), + ], + REPO_ROOT / "docker/ai-studio/docker-compose.yml": [ + ( + r"(tykio/tyk-ai-studio:\$\{AI_STUDIO_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['ai_studio']}\2", + "ai studio fallback", + ), + ( + r"(tykio/tyk-microgateway:\$\{MICROGATEWAY_VERSION:-)[^}]+(\})", + rf"\g<1>{versions['microgateway']}\2", + "microgateway fallback", + ), + ], + REPO_ROOT / "kubernetes/helm-self-managed/values.yaml": [ + ( + rf"(repository: tykio/tyk-gateway-ee\s+tag: ){PINNED_VERSION}", + rf"\g<1>{versions['gateway_ee']}", + "gateway tag", + ), + ( + rf"(repository: tykio/tyk-dashboard\s+tag: ){PINNED_VERSION}", + rf"\g<1>{versions['dashboard']}", + "dashboard tag", + ), + ( + rf"(repository: tykio/tyk-pump-docker-pub\s+tag: ){PINNED_VERSION}", + rf"\g<1>{versions['pump']}", + "pump tag", + ), + ( + rf"(repository: tykio/portal\s+tag: ){PINNED_VERSION}", + rf"\g<1>{versions['portal']}", + "portal tag", + ), + ], + REPO_ROOT / "kubernetes/helm-hybrid/values.yaml": [ + ( + rf"(repository: tykio/tyk-gateway-ee\s+tag: ){PINNED_VERSION}", + rf"\g<1>{versions['gateway_ee']}", + "gateway tag", + ), + ( + rf"(repository: tykio/tyk-pump-docker-pub\s+tag: ){PINNED_VERSION}", + rf"\g<1>{versions['pump']}", + "pump tag", + ), + ], + } + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Update pinned Tyk component versions across the repo." + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Print the resolved versions without modifying files.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + + try: + versions = { + name: latest_tag(component["repo"], component["allow_prerelease"]) + for name, component in COMPONENTS.items() + } + replacements = build_replacements(versions) + rendered_files = render_replacements(replacements) + except (RuntimeError, urllib.error.URLError) as exc: + print(f"Failed to prepare version updates: {exc}", file=sys.stderr) + return 1 + + print("Resolved latest Tyk component image tags:") + for name, component in sorted(COMPONENTS.items()): + suffix = " (RCs allowed)" if component["allow_prerelease"] else "" + print(f" {name}: {versions[name]}{suffix}") + + if args.dry_run: + print() + print("Validated replacement targets. No files changed.") + return 0 + + changed_files = [] + for path, rendered in rendered_files.items(): + if rendered != path.read_text(): + path.write_text(rendered) + changed_files.append(path) + + print() + if changed_files: + print("Updated files:") + for path in changed_files: + print(f" {path.relative_to(REPO_ROOT)}") + else: + print("No files changed.") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/autoupdate.yaml b/.github/workflows/autoupdate.yaml new file mode 100644 index 0000000000..408072bbbe --- /dev/null +++ b/.github/workflows/autoupdate.yaml @@ -0,0 +1,30 @@ +# autoupdate is a GitHub Action that auto-updates pull requests branches whenever changes land on their destination branch. +name: autoupdate +on: + push: + branches: + - main + +permissions: + contents: read + pull-requests: write + +jobs: + autoupdate: + name: autoupdate + runs-on: ubuntu-22.04 + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROBE_APP_ID }} + private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} + owner: TykTechnologies + + - uses: docker://chinthakagodawita/autoupdate-action:v1@sha256:53d7013ad4689b703d2715d4b17d4901bb0a385e495e0b481f37adbe9b3cc3fc + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + # Only monitor PRs that are not currently in the draft state. + PR_READY_STATE: "ready_for_review" + MERGE_CONFLICT_ACTION: "ignore" # Possible option to prevent retrying failed merges diff --git a/.github/workflows/check-external-links.yml b/.github/workflows/check-external-links.yml new file mode 100644 index 0000000000..6c5014495c --- /dev/null +++ b/.github/workflows/check-external-links.yml @@ -0,0 +1,24 @@ +name: Check External Links + +on: + schedule: + - cron: '0 9 * * 1' # Every Monday at 09:00 UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + external-links: + name: Check External URLs + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Check external links + uses: lycheeverse/lychee-action@v2 + with: + args: --config lychee.toml '**/*.md' '**/*.mdx' + fail: false diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index a3988f5b21..eb5243e5bf 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,35 +1,96 @@ name: Deploy Documentation on: - workflow_dispatch: # Manual trigger only + workflow_dispatch: inputs: subfolder: - description: 'Subfolder for documentation (e.g., docs3, docsv3, docs)' + description: 'Subfolder for documentation (leave empty for root deployment)' required: false - default: 'docs3' + default: '' type: string + triggering_pr_number: + description: 'PR number that triggered this deployment' + required: false + default: '' + type: string + triggering_pr_title: + description: 'PR title that triggered this deployment' + required: false + default: '' + type: string + triggering_commit_sha: + description: 'Commit SHA that triggered this deployment' + required: false + default: '' + type: string + triggering_branch: + description: 'Branch that triggered this deployment' + required: false + default: '' + type: string + original_pr_number: + description: 'Original PR number (if different from triggering PR)' + required: false + default: '' + type: string + +permissions: + contents: write + pull-requests: write + +# Ensure only latest deployment runs +concurrency: + group: docs-deployment + cancel-in-progress: true jobs: merge-docs: runs-on: ubuntu-latest steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROBE_APP_ID }} + private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} + owner: TykTechnologies + + - name: Log deployment trigger information + run: | + echo "🚀 Starting documentation deployment" + echo "📋 Trigger Information:" + echo " - Branch: ${{ inputs.triggering_branch || 'N/A' }}" + echo " - Commit: ${{ inputs.triggering_commit_sha || 'N/A' }}" + if [ -n "${{ inputs.triggering_pr_number }}" ]; then + echo " - PR: #${{ inputs.triggering_pr_number }} - ${{ inputs.triggering_pr_title }}" + if [ -n "${{ inputs.original_pr_number }}" ] && [ "${{ inputs.original_pr_number }}" != "${{ inputs.triggering_pr_number }}" ]; then + echo " - Original PR: #${{ inputs.original_pr_number }} (cherry-picked)" + fi + else + echo " - PR: N/A (direct push)" + fi + echo " - Timestamp: $(date)" + echo " - Workflow Run: ${{ github.run_id }}" + echo " - Subfolder: ${{ inputs.subfolder || '(root deployment)' }}" + - name: Checkout production branch - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: production - token: ${{ secrets.ORG_GH_TOKEN }} + token: ${{ steps.app-token.outputs.token }} fetch-depth: 0 # Fetch all history for all branches - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: '3.9' - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade 'pip>=23.0,<25' # Add any additional dependencies if needed + pip install 'pyyaml>=6.0,<7' 'ruamel.yaml>=0.18,<0.19' - name: Read branches config id: config @@ -43,77 +104,69 @@ jobs: - name: Cleanup old version folders and assets run: | - echo "🧹 Cleaning up old version folders and assets..." - - # Get current version folders from config - current_folders=$(python3 -c "import json; config=json.load(open('branches-config.json')); folders=[v.get('folder','') for v in config.get('versions',[]) if v.get('folder','')]; print(' '.join(folders))") - - echo "📋 Current version folders: $current_folders" - - # Define asset types that will be regenerated - asset_types=( - "style.css" - "images" - "img" - "logo" - "favicon.ico" - "favicon.png" - "snippets" + echo "🧹 Cleaning up all content except essential files..." + + # Define folders to keep (whitelist) + keep_folders=( + ".github" + ".git" + ".devcontainer" + "scripts" + ".vale" + ".probe" ) - # Remove old assets (they'll be regenerated from current versions) - echo "🎨 Cleaning up old assets..." - for asset in "${asset_types[@]}"; do - if [ -e "$asset" ]; then - echo "🗑️ Removing old asset: $asset" - rm -rf "$asset" - fi - done - - # Remove old docs.json (will be regenerated) - if [ -f "docs.json" ]; then - echo "🗑️ Removing old docs.json" - rm -f "docs.json" - fi + # Define files to keep (whitelist) + keep_files=( + "branches-config.json" + ".gitignore" + "README.md" + ".vale.ini" + ) - # Also clean up the subfolder if it exists (using input parameter) - SUBFOLDER="${{ inputs.subfolder || 'docs3' }}" - if [ -n "$SUBFOLDER" ] && [ -d "$SUBFOLDER" ]; then - echo "🗑️ Removing old $SUBFOLDER subfolder" - rm -rf "$SUBFOLDER" - fi + echo "📋 Folders to keep: ${keep_folders[*]}" + echo "📋 Files to keep: ${keep_files[*]}" - # Remove old version folders that aren't in the current config - # Look for directories that look like version folders (numeric or version-like names) + # Remove all directories except the ones we want to keep + echo "🗑️ Removing old directories..." for dir in */; do if [ -d "$dir" ]; then dir_name=${dir%/} # Remove trailing slash - - # Skip non-version directories - if [[ "$dir_name" =~ ^\..*$ ]] || [ "$dir_name" = ".github" ]; then - continue - fi - - # Check if this folder is in current config - is_current=false - for current in $current_folders; do - if [ "$dir_name" = "$current" ]; then - is_current=true + + should_keep=false + for keep_folder in "${keep_folders[@]}"; do + if [ "$dir_name" = "$keep_folder" ]; then + should_keep=true break fi done + + if [ "$should_keep" = false ]; then + echo "🗑️ Removing directory: $dir_name/" + rm -rf "$dir" + else + echo "📁 Keeping essential directory: $dir_name/" + fi + fi + done - # If it's not in current config and looks like a version folder, remove it - if [ "$is_current" = false ]; then - # Only remove if it looks like a version folder (contains numbers/dots or common version patterns) - if [[ "$dir_name" =~ ^[0-9]+\.[0-9]+$ ]] || [[ "$dir_name" =~ ^v[0-9] ]] || [[ "$dir_name" =~ ^[0-9] ]]; then - echo "🗑️ Removing old version folder: $dir_name" - rm -rf "$dir" - else - echo "📁 Keeping non-version directory: $dir_name" + # Remove all files except the ones we want to keep + echo "🗑️ Removing old files..." + for file in *; do + if [ -f "$file" ]; then + should_keep=false + for keep_file in "${keep_files[@]}"; do + if [ "$file" = "$keep_file" ]; then + should_keep=true + break fi + done + + if [ "$should_keep" = false ]; then + echo "🗑️ Removing file: $file" + rm -f "$file" else - echo "📁 Keeping current version folder: $dir_name (will be refreshed)" + echo "📄 Keeping essential file: $file" fi fi done @@ -129,7 +182,7 @@ jobs: echo "🔄 Starting branch cloning and organization..." # Read the branches config and extract branch information - branches=$(python3 -c "import json; config=json.load(open('branches-config.json')); [print(f\"{v.get('folder','')}:{v.get('branch','main')}\") for v in config.get('versions',[]) if v.get('folder','')]") + branches=$(python3 -c "import json; config=json.load(open('branches-config.json')); [print(f\"{v.get('sourceFolder','')}:{v.get('branch','main')}\") for v in config.get('versions',[]) if v.get('sourceFolder','')]") echo "📋 Branches to process:" echo "$branches" @@ -160,8 +213,18 @@ jobs: mkdir -p "$folder" echo "📁 Moving contents from $temp_dir to $folder..." - # Copy all files except .git directory - find "$temp_dir" -mindepth 1 -maxdepth 1 ! -name '.git' -exec cp -r {} "$folder/" \; + # Copy documentation content only, excluding development/build files + # Excluded: .git, scripts/, branches-config.json, .github/, README.md, .gitignore, .devcontainer/, .probe + find "$temp_dir" -mindepth 1 -maxdepth 1 \ + ! -name '.git' \ + ! -name 'scripts' \ + ! -name 'branches-config.json' \ + ! -name '.github' \ + ! -name 'README.md' \ + ! -name '.gitignore' \ + ! -name '.devcontainer' \ + ! -name '.probe' \ + -exec cp -r {} "$folder/" \; # Clean up temp directory rm -rf "$temp_dir" @@ -180,19 +243,21 @@ jobs: run: | echo "🔄 Running documentation merger..." - # Get subfolder from input (with fallback) - SUBFOLDER="${{ inputs.subfolder || 'docs3' }}" - echo "📁 Using subfolder: $SUBFOLDER" + # Get subfolder from input (no fallback - empty means root deployment) + SUBFOLDER="${{ inputs.subfolder }}" + echo "📁 Using subfolder: '$SUBFOLDER'" # Run the merge script with branches config if [ -n "$SUBFOLDER" ]; then - python3 merge_docs_configs.py \ + echo "📁 Deploying to subfolder: $SUBFOLDER" + python3 scripts/merge_docs_configs.py \ --branches-config branches-config.json \ --base-dir . \ --subfolder "$SUBFOLDER" \ --output docs.json else - python3 merge_docs_configs.py \ + echo "📁 Deploying to root (no subfolder)" + python3 scripts/merge_docs_configs.py \ --branches-config branches-config.json \ --base-dir . \ --output docs.json @@ -205,7 +270,7 @@ jobs: echo "🧹 Cleaning up temporary cloned version folders..." # Get current version folders from config - current_folders=$(python3 -c "import json; config=json.load(open('branches-config.json')); folders=[v.get('folder','') for v in config.get('versions',[]) if v.get('folder','')]; print(' '.join(folders))") + current_folders=$(python3 -c "import json; config=json.load(open('branches-config.json')); folders=[v.get('sourceFolder','') for v in config.get('versions',[]) if v.get('sourceFolder','')]; print(' '.join(folders))") echo "📋 Removing cloned folders: $current_folders" @@ -219,6 +284,12 @@ jobs: echo "✅ Cleanup of cloned folders completed!" + - name: Add canonical URLs to MDX files + run: | + echo "🧩 Running canonical URL update script..." + python3 scripts/add-canonical-urls/index.py || { echo "❌ Canonical script failed"; exit 1; } + echo "✅ Canonical URL update completed." + - name: Verify output run: | echo "📋 Checking generated files..." @@ -237,10 +308,41 @@ jobs: echo "📁 Generated file structure:" find . -name "*.mdx" -o -name "*.md" -o -name "*.json" -o -name "*.css" -o -name "*.png" | head -20 + - name: Close previous deployment PRs + run: | + echo "🔍 Finding and closing previous deployment PRs..." + + # Find PRs that match BOTH criteria: + # 1. Have the "auto-deployment" label + # 2. Branch starts with "docs-merge-" + DEPLOYMENT_PRS=$(gh pr list \ + --state open \ + --label "auto-deployment" \ + --json number,headRefName,labels \ + --jq '.[] | select(.headRefName | startswith("docs-merge-")) | .number') + + if [ -n "$DEPLOYMENT_PRS" ]; then + echo "📋 Found deployment PRs to close: $DEPLOYMENT_PRS" + + for pr_number in $DEPLOYMENT_PRS; do + echo "❌ Closing deployment PR #$pr_number" + gh pr close "$pr_number" \ + --comment "🤖 Superseded by newer deployment (Run #${{ github.run_number }})" \ + || echo "⚠️ Failed to close PR #$pr_number (may already be closed)" + done + + echo "✅ Closed all previous deployment PRs" + else + echo "✅ No deployment PRs found to close" + fi + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 + id: create-pr + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: - token: ${{ secrets.ORG_GH_TOKEN }} + token: ${{ steps.app-token.outputs.token }} branch: docs-merge-${{ github.run_number }} base: production title: "🤖 Auto-merge documentation from branches" @@ -249,9 +351,16 @@ jobs: This PR contains automatically merged documentation from multiple branches. - **Generated from:** `branches-config.json` - **Timestamp:** ${{ github.event.head_commit.timestamp }} - **Run ID:** ${{ github.run_id }} + **Triggered by:** + - **Branch:** ${{ inputs.triggering_branch || 'N/A' }} + - **Commit:** ${{ inputs.triggering_commit_sha || 'N/A' }} + - **PR:** ${{ inputs.triggering_pr_number && format('#{0} - {1}', inputs.triggering_pr_number, inputs.triggering_pr_title) || 'N/A (direct push)' }}${{ inputs.original_pr_number && inputs.original_pr_number != inputs.triggering_pr_number && format(' (cherry-pick of #{0})', inputs.original_pr_number) || '' }} + + **Deployment Details:** + - **Generated from:** `branches-config.json` + - **Run ID:** ${{ github.run_id }} + - **Subfolder:** `${{ inputs.subfolder || '(root deployment)' }}` + - **Timestamp:** $(date) ### Changes Include: - ✅ Merged documentation from multiple branches @@ -259,12 +368,15 @@ jobs: - ✅ Updated assets and content structure - ✅ Cleaned up outdated version folders - ### Subfolder Used: - `${{ inputs.subfolder || 'docs3' }}` - --- - Please review the changes and merge when ready. + 🚦 **This PR will be processed by merge queue to ensure proper validation and ordering.** + + Previous deployment PRs have been automatically closed to prevent conflicts. + labels: | + documentation + auto-deployment + automated commit-message: | 🤖 Auto-merge documentation from branches @@ -279,10 +391,34 @@ jobs: author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + - name: Enable auto-merge on deployment PR + if: steps.create-pr.outputs.pull-request-number + run: | + PR_NUMBER="${{ steps.create-pr.outputs.pull-request-number }}" + echo "� Enabling auto-merge on deployment PR #$PR_NUMBER..." + + gh pr merge --squash --auto "$PR_NUMBER" + + echo "✅ Auto-merge enabled on PR #$PR_NUMBER" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Create deployment summary run: | echo "## 📚 Documentation Deployment Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🚀 Trigger Information" >> $GITHUB_STEP_SUMMARY + echo "- **Branch:** ${{ inputs.triggering_branch || 'N/A' }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit:** ${{ inputs.triggering_commit_sha || 'N/A' }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ inputs.triggering_pr_number }}" ]; then + echo "- **PR:** #${{ inputs.triggering_pr_number }} - ${{ inputs.triggering_pr_title }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ inputs.original_pr_number }}" ] && [ "${{ inputs.original_pr_number }}" != "${{ inputs.triggering_pr_number }}" ]; then + echo "- **Original PR:** #${{ inputs.original_pr_number }} (cherry-picked)" >> $GITHUB_STEP_SUMMARY + fi + else + echo "- **PR:** N/A (direct push)" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY echo "### ✅ Successfully merged documentation" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Generated files:**" >> $GITHUB_STEP_SUMMARY @@ -294,4 +430,4 @@ jobs: fi echo "" >> $GITHUB_STEP_SUMMARY - echo "**Timestamp:** $(date)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "**Timestamp:** $(date)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/eod-report-generator.yaml b/.github/workflows/eod-report-generator.yaml new file mode 100644 index 0000000000..f63ad5b663 --- /dev/null +++ b/.github/workflows/eod-report-generator.yaml @@ -0,0 +1,47 @@ +name: EOD Report Generator + +on: + workflow_dispatch: + inputs: + MAIN_REVIEWER: + description: "The GitHub user ID of the primary reviewer whose approval is required for the PR." + required: true + default: "sharadregoti" + START_DATE: + description: "The start date of report (e.g., 2025-02-21T00:00:00.000Z)." + required: true + END_DATE: + description: "The end date of report. Defaults to the current date (e.g., 2025-02-25T00:00:00.000Z)." + required: false + +permissions: + contents: read + +jobs: + run-script: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' # Change to your required version + + - name: Install dependencies + run: | + cd scripts/eod-report-generator + npm ci --ignore-scripts + + - name: Run script + run: | + cd scripts/eod-report-generator + node index.js + env: + MAIN_REVIEWER: ${{ inputs.MAIN_REVIEWER }} + START_DATE: ${{ inputs.START_DATE }} + END_DATE: ${{ inputs.END_DATE }} + GITHUB_TOKEN: ${{ secrets.TYK_SCRIPTS_TOKEN }} # GitHub Token for API calls + ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_KEY }} # Org key already available diff --git a/.github/workflows/mirror-pr-to-build-deploy.yml b/.github/workflows/mirror-pr-to-build-deploy.yml index 39e8b21702..b3337b42a4 100644 --- a/.github/workflows/mirror-pr-to-build-deploy.yml +++ b/.github/workflows/mirror-pr-to-build-deploy.yml @@ -5,16 +5,28 @@ on: types: [opened, synchronize, closed] branches: [main] +permissions: + contents: read + pull-requests: write + jobs: mirror-pr: runs-on: ubuntu-latest if: github.event.action != 'closed' steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROBE_APP_ID }} + private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} + owner: TykTechnologies + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - token: ${{ secrets.ORG_GH_TOKEN }} + token: ${{ steps.app-token.outputs.token }} - name: Setup GitHub CLI run: gh --version @@ -23,13 +35,33 @@ jobs: run: | # Check if mirror PR already exists MIRROR_PR=$(gh pr list --base production --head ${{ github.head_ref }} --json number --jq '.[0].number // empty') - + if [ -z "$MIRROR_PR" ]; then # Create new mirror PR echo "Creating new mirror PR..." - - # Create PR body content - PR_BODY="**🔗 Auto-generated mirror PR for Mintlify preview** + + # Write PR body to file; template passed via env var to keep YAML valid + printf '%s\n' "$PR_TEMPLATE" "$PR_BODY" > pr_body.txt + + # Escape the title properly to handle special characters and spaces + ESCAPED_TITLE=$(printf '%s' "🔄 Preview: ${{ github.event.pull_request.title }}" | sed 's/"/\\"/g') + + gh pr create \ + --base production \ + --head "${{ github.head_ref }}" \ + --title "$ESCAPED_TITLE" \ + --body-file pr_body.txt \ + --draft + + echo "✅ Mirror PR created successfully" + else + echo "🔄 Mirror PR #$MIRROR_PR already exists and will be auto-updated" + fi + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_TEMPLATE: | + **🔗 Auto-generated mirror PR for Mintlify preview** **Original PR:** #${{ github.event.number }} **Author:** @${{ github.event.pull_request.user.login }} @@ -46,55 +78,30 @@ jobs: - Make all comments and reviews on the original PR #${{ github.event.number }} ## Changes - ${{ github.event.pull_request.body }}" - - gh pr create \ - --base production \ - --head ${{ github.head_ref }} \ - --title "🔄 Preview: ${{ github.event.pull_request.title }}" \ - --body "$PR_BODY" - - echo "✅ Mirror PR created successfully" - - # Get the mirror PR number that was just created - MIRROR_PR=$(gh pr list --base production --head ${{ github.head_ref }} --json number --jq '.[0].number // empty') - - # Comment on the original PR with link to mirror PR - if [ -n "$MIRROR_PR" ]; then - COMMENT_BODY="🔗 **Preview Link Available** - - This PR has an auto-generated mirror for Mintlify preview: - 👉 **[View Preview PR #$MIRROR_PR](https://github.com/${{ github.repository }}/pull/$MIRROR_PR)** - - The Mintlify preview link will appear on the mirror PR once it's processed. - - --- - *This is an automated comment. All discussion should happen on this PR, not the mirror PR.*" - - gh pr comment ${{ github.event.number }} --body "$COMMENT_BODY" - echo "✅ Comment added to original PR with mirror PR link" - fi - else - echo "🔄 Mirror PR #$MIRROR_PR already exists and will be auto-updated" - fi - env: - GH_TOKEN: ${{ secrets.ORG_GH_TOKEN }} cleanup-mirror-pr: runs-on: ubuntu-latest if: github.event.action == 'closed' steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROBE_APP_ID }} + private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} + owner: TykTechnologies + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - token: ${{ secrets.ORG_GH_TOKEN }} + token: ${{ steps.app-token.outputs.token }} - name: Handle mirror PR when original is closed run: | # Find the mirror PR MIRROR_PR=$(gh pr list --base production --head ${{ github.head_ref }} --json number --jq '.[0].number // empty') - + if [ -n "$MIRROR_PR" ]; then if [ "${{ github.event.pull_request.merged }}" = "true" ]; then echo "Original PR was merged, closing mirror PR #$MIRROR_PR (no need to merge)..." @@ -109,4 +116,4 @@ jobs: echo "No mirror PR found for branch ${{ github.head_ref }}" fi env: - GH_TOKEN: ${{ secrets.ORG_GH_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/pr_agent.yaml b/.github/workflows/pr_agent.yaml new file mode 100644 index 0000000000..9c621dfb60 --- /dev/null +++ b/.github/workflows/pr_agent.yaml @@ -0,0 +1,26 @@ +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + issue_comment: + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + pr_agent_job: + runs-on: ubuntu-latest + name: Run pr agent on every pull request, respond to user comments + if: ${{ !github.event.pull_request.draft }} + steps: + - name: PR Agent action step + id: pragent + uses: Codium-ai/pr-agent@d82f7d3e696cd00822694aaa3096265d3889f3f1 # main + env: + OPENAI_KEY: ${{ secrets.OPENAI_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/probe-writer.yaml.disabled b/.github/workflows/probe-writer.yaml.disabled new file mode 100644 index 0000000000..71cdb86c95 --- /dev/null +++ b/.github/workflows/probe-writer.yaml.disabled @@ -0,0 +1,24 @@ +name: Probe Writer handler + +on: + issue_comment: + types: [created] + +# Define permissions needed for the workflow +permissions: + issues: write + pull-requests: write + contents: write + +jobs: + trigger_probe_implement: + uses: buger/probe/.github/workflows/probe.yml@88434b11a7543f6506d60c4e2dd6325d8be80142 # main + with: + command_prefix: "/writer" # Or '/ai', '/ask', etc. + allow_edit: true + prompt: .github/.probe/writer.txt + secrets: + ANTHROPIC_API_KEY: ${{ secrets.PROBE_ANTHROPIC_API_KEY }} + ANTHROPIC_API_URL: ${{ secrets.PROBE_ANTHROPIC_URL }} + APP_ID: ${{ secrets.PROBE_APP_ID }} + APP_PRIVATE_KEY: ${{ secrets.PROBE_APP_PRIVATE_KEY }} \ No newline at end of file diff --git a/.github/workflows/probe.yaml.disabled b/.github/workflows/probe.yaml.disabled new file mode 100644 index 0000000000..5866f2da84 --- /dev/null +++ b/.github/workflows/probe.yaml.disabled @@ -0,0 +1,37 @@ +name: Probe handler + +on: + pull_request: + types: [opened] #[opened , labeled] + issue_comment: + types: [created] + issues: + types: [opened] #[opened, labeled] + +# Define permissions needed for the workflow +permissions: + issues: write + pull-requests: write + contents: read + +jobs: + trigger_probe_chat: + # Uncomment if you want to run on on specific lables, in this example `probe` + # if: | + # (github.event_name == 'pull_request' && github.event.action == 'opened') || + # (github.event_name == 'issues' && github.event.action == 'opened') || + # (github.event_name == 'issue_comment' && github.event.action == 'created') || + # ((github.event_name == 'pull_request' || github.event_name == 'issues') && + # github.event.action == 'labeled' && github.event.label.name == 'probe') + # Use the reusable workflow from your repository (replace and ) + uses: buger/probe/.github/workflows/probe.yml@88434b11a7543f6506d60c4e2dd6325d8be80142 # main + # Pass required inputs + with: + command_prefix: "/probe" # Or '/ai', '/ask', etc. + # Optionally override the default npx command if the secret isn't set + # default_probe_chat_command: 'node path/to/custom/script.js' + # Pass ALL secrets from this repository to the reusable workflow + # This includes GITHUB_TOKEN, PROBE_CHAT_COMMAND (if set), ANTHROPIC_API_KEY, etc. + secrets: + ANTHROPIC_API_KEY: ${{ secrets.PROBE_ANTHROPIC_API_KEY }} + ANTHROPIC_API_URL: ${{ secrets.PROBE_ANTHROPIC_URL }} diff --git a/.github/workflows/release-bot.yaml b/.github/workflows/release-bot.yaml new file mode 100644 index 0000000000..e3c997d1f8 --- /dev/null +++ b/.github/workflows/release-bot.yaml @@ -0,0 +1,214 @@ +name: Cherry-pick to Release Branch + +on: + issue_comment: + types: [created] + workflow_call: + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + cherry_pick: + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROBE_APP_ID }} + private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} + owner: TykTechnologies + + - name: Check for release command + id: check_command + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { issue, comment } = context.payload; + if (!issue || !issue.pull_request || !comment || !comment.body.startsWith('/release to ')) { + core.setOutput('release_valid', 'false'); + return; + } + const releaseBranch = comment.body.split('/release to ')[1].trim(); + core.setOutput('release_valid', 'true'); + core.setOutput('release_branch', releaseBranch); + core.setOutput('pr_number', issue.number); + + - name: Check admin permissions + if: steps.check_command.outputs.release_valid == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const username = context.payload.comment.user.login; + const authorAssociation = context.payload.comment.author_association; + + // Quick check: Repository owner always allowed + if (authorAssociation === 'OWNER') { + console.log(`✅ User ${username} is repository owner`); + return; + } + + // Check for admin permission + try { + const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: username + }); + + if (permission.permission !== 'admin') { + core.setFailed(`❌ Only repository admins can use /release command. User ${username} has: ${permission.permission}`); + return; + } + + console.log(`✅ User ${username} has admin permissions`); + } catch (error) { + core.setFailed(`❌ Permission check failed: ${error.message}`); + } + + - name: Install GitHub CLI (for act/local testing) + if: steps.check_command.outputs.release_valid == 'true' + run: | + if ! command -v gh &>/dev/null; then + GH_VERSION="2.62.0" + curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.deb" -o /tmp/gh.deb + sudo dpkg -i /tmp/gh.deb + rm /tmp/gh.deb + fi + + - name: Checkout repository + if: steps.check_command.outputs.release_valid == 'true' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Set default branch variable + if: steps.check_command.outputs.release_valid == 'true' + run: echo "DEFAULT_BRANCH=${{ github.event.repository.default_branch }}" >> $GITHUB_ENV + + - name: Skip jobs if not a valid release command + if: steps.check_command.outputs.release_valid == 'false' + run: echo "Skipping cherry-pick as the release command is not valid." + continue-on-error: true + + - name: Setup Git + if: steps.check_command.outputs.release_valid == 'true' + run: | + git config --global user.email "bot@tyk.io" + git config --global user.name "Tyk Bot" + + - name: Get PR base and merge SHAs + id: pr_details + if: steps.check_command.outputs.release_valid == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ steps.check_command.outputs.pr_number }} + MERGE_COMMIT=$(gh pr view "$PR_NUMBER" --json mergeCommit --jq '.mergeCommit.oid // empty') + BASE_SHA=$(gh pr view "$PR_NUMBER" --json baseRefOid --jq '.baseRefOid // empty') + echo "MERGE_COMMIT=$MERGE_COMMIT" >> $GITHUB_ENV + echo "BASE_SHA=$BASE_SHA" >> $GITHUB_ENV + echo "MERGE_COMMIT=$MERGE_COMMIT" >> $GITHUB_OUTPUT + echo "BASE_SHA=$BASE_SHA" >> $GITHUB_OUTPUT + + - name: Cherry-pick PR into release branch + id: cherry_pick + if: steps.check_command.outputs.release_valid == 'true' + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + GITHUB_REPO: ${{ github.repository }} + GITHUB_BRANCH: ${{ steps.check_command.outputs.release_branch }} + run: | + export FOLDER=$(basename "$GITHUB_REPO") + rm -rf $FOLDER + git clone https://x-access-token:$GITHUB_TOKEN@github.com/$GITHUB_REPO + cd $FOLDER + + git checkout $GITHUB_BRANCH + git pull + + NEW_BRANCH=merge/$GITHUB_BRANCH/$MERGE_COMMIT + git branch -D $NEW_BRANCH 2>/dev/null || true + REMOTE_EXISTS=$(git ls-remote --heads origin $NEW_BRANCH | wc -l) + [ "$REMOTE_EXISTS" -gt 0 ] && git push origin --delete $NEW_BRANCH || true + + git checkout -b $NEW_BRANCH + + MERGE_FAILED=0 + git cherry-pick -x $BASE_SHA..$MERGE_COMMIT || MERGE_FAILED=$? + + if [ "$MERGE_FAILED" -ne 0 ]; then + git add . + git commit -m "Cherry-pick conflicts for $MERGE_COMMIT" || true + fi + + git diff --quiet origin/$GITHUB_BRANCH HEAD && { + echo "No changes to cherry-pick" + echo "PR_URL=" >> $GITHUB_OUTPUT + echo "MERGE_FAILED=0" >> $GITHUB_OUTPUT + exit 0 + } + + git push origin $NEW_BRANCH --force + + TITLE=$(git log --format=%s -n 1 $MERGE_COMMIT) + BODY=$(git log --format=%B -n 1 $MERGE_COMMIT) + + PR_URL=$(gh pr create \ + --title "Merging to $GITHUB_BRANCH: $TITLE" \ + --body "$BODY" \ + --repo $GITHUB_REPO \ + --base $GITHUB_BRANCH \ + --head $NEW_BRANCH \ + $( [ "$MERGE_FAILED" -ne 0 ] && echo "--draft" )) + + echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT + echo "MERGE_FAILED=$MERGE_FAILED" >> $GITHUB_OUTPUT + + if [ "$MERGE_FAILED" -eq 0 ]; then + if [[ "$PR_URL" =~ /pull/([0-9]+) ]]; then + PR_NUMBER="${BASH_REMATCH[1]}" + gh pr merge --squash "$PR_NUMBER" --auto --subject "Merging to $GITHUB_BRANCH: $TITLE" --body "$BODY" || echo "Auto-merge failed" + fi + fi + + - name: Comment back on original PR + if: steps.check_command.outputs.release_valid == 'true' && always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prUrl = '${{ steps.cherry_pick.outputs.PR_URL }}'; + const mergeFailed = '${{ steps.cherry_pick.outputs.MERGE_FAILED }}' === '1'; + let body; + + if ('${{ job.status }}' === 'success') { + if (mergeFailed) { + body = `⚠️ Cherry-pick completed with conflicts. A draft PR was created: ${prUrl}`; + } else if (prUrl) { + body = `✅ Cherry-pick successful. A PR was created and auto-merged (if allowed): ${prUrl}`; + } else { + body = `ℹ️ Cherry-pick skipped: no changes needed in target branch.`; + } + } else { + let failedReason = 'Unknown error'; + if ('${{ steps.pr_details.outcome }}' === 'failure') { + failedReason = 'Failed to get PR details (e.g., GitHub API timeout)'; + } else if ('${{ steps.cherry_pick.outcome }}' === 'failure') { + failedReason = 'Failed during git cherry-pick or PR creation'; + } + + body = `❌ Cherry-pick failed. Reason: **${failedReason}**. Please check the [workflow logs](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`; + } + + github.rest.issues.createComment({ + issue_number: ${{ steps.check_command.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }); diff --git a/.github/workflows/release-to-branches-with-label.yml b/.github/workflows/release-to-branches-with-label.yml new file mode 100644 index 0000000000..0c36652924 --- /dev/null +++ b/.github/workflows/release-to-branches-with-label.yml @@ -0,0 +1,131 @@ +name: On Pull Request Merged to Master + +on: + pull_request: + # Only trigger on pull requests targeting main/master + branches: + - master + - main + types: + - closed + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to process (for manual testing)' + required: true + type: string + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + run-on-pr-merged: + runs-on: ubuntu-latest + + # For pull_request events: only run if the PR was actually merged + if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true }} + + steps: + - name: Add a comment to the merged PR (only if labeler is in the org) + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.ORG_GH_TOKEN }} + script: | + // 1. The label format: e.g., "release-1", "release-1.0" + const labelRegex = /^release-[0-9]+(\.[0-9]+)?$/; + + // 2. Get PR info — from event payload or workflow_dispatch input + let pullRequestNumber, labelsOnPR; + if (context.eventName === 'workflow_dispatch') { + const prNum = parseInt(context.payload.inputs.pr_number); + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNum + }); + pullRequestNumber = prNum; + labelsOnPR = pr.labels || []; + } else { + pullRequestNumber = context.payload.pull_request.number; + labelsOnPR = context.payload.pull_request.labels || []; + } + + console.log("PR number:", pullRequestNumber); + console.log("Labels on the Pull Request:", labelsOnPR.map(label => label.name)); + + // 3. Get all timeline events to see who labeled the PR + const { data: prEvents } = await github.rest.issues.listEvents({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pullRequestNumber + }); + + // 4. Filter down to "labeled" events + const labeledEvents = prEvents.filter(ev => ev.event === 'labeled'); + console.log("Labeled Events:", labeledEvents.map(event => ({ + label: event.label?.name, + user: event.actor?.login, + timestamp: event.created_at + }))); + + // 5. Build a map: labelName -> last user who added it + // (We reverse to get the *most recent* labeler, if a label was added multiple times) + const labelToLastLabeler = {}; + for (const event of labeledEvents.reverse()) { + const labelName = event.label?.name; + const userName = event.actor?.login; + if (labelName && userName && !labelToLastLabeler[labelName]) { + labelToLastLabeler[labelName] = userName; + } + } + + // 6. For each label on the PR, check if it matches "release-.." + // If yes, we see who labeled it last and check their membership + for (const label of labelsOnPR) { + if (labelRegex.test(label.name)) { + const userWhoAddedLabel = labelToLastLabeler[label.name]; + + // If there's no recorded user (edge case), skip + if (!userWhoAddedLabel) { + console.log(`User not found for label: ${label.name}`); + continue; + } + + // 7. Check if the user is in the org + let isMember = false; + try { + await github.rest.orgs.checkMembershipForUser({ + org: 'TykTechnologies', + username: userWhoAddedLabel + }); + // If this call succeeds, they're a member + isMember = true; + console.log(`User '${userWhoAddedLabel}' is a member of the organization 'TykTechnologies'.`); + } catch (error) { + // If 404, user is not a member. Anything else is an unexpected error. + if (error.status === 404) { + console.log(`User '${userWhoAddedLabel}' is NOT a member of the organization 'TykTechnologies'.`); + } else { + console.error(`An error occurred while checking membership for user '${userWhoAddedLabel}':`, error); + throw error; + } + } + + // 8. Comment only if user is in the org + if (isMember) { + console.log(`Creating comment for label '${label.name}' on PR #${pullRequestNumber} by user '${userWhoAddedLabel}'.`); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pullRequestNumber, + body: `/release to ${label.name}` + }); + } else { + console.log(`No comment created for label '${label.name}' on PR #${pullRequestNumber} because the user '${userWhoAddedLabel}' is not a member of the organization 'TykTechnologies'.`); + } + } else { + console.log(`Label '${label.name}' does not match the expected format.`); + } + } diff --git a/.github/workflows/site-content-analysis.yml b/.github/workflows/site-content-analysis.yml new file mode 100644 index 0000000000..e7f98a6932 --- /dev/null +++ b/.github/workflows/site-content-analysis.yml @@ -0,0 +1,165 @@ +name: Site Content Analysis + +on: + workflow_dispatch: + inputs: + wait_time: + description: 'Wait time per page (seconds)' + required: false + default: '3' + type: string + timeout: + description: 'Timeout per page (seconds)' + required: false + default: '30' + type: string + +permissions: + contents: read + +jobs: + analyze-site-content: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: production + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.9' + + - name: Install system dependencies + run: | + sudo apt-get update + # Install essential packages for headless Chrome + sudo apt-get install -y \ + ca-certificates \ + fonts-liberation \ + libnss3 \ + lsb-release \ + xdg-utils \ + wget \ + gnupg + + # Install Chrome dependencies (with fallbacks for different Ubuntu versions) + sudo apt-get install -y \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libcups2 \ + libdrm2 \ + libgtk-3-0 \ + libgtk-4-1 \ + libxcomposite1 \ + libxdamage1 \ + libxrandr2 \ + libgbm1 \ + libxss1 \ + libasound2 || \ + sudo apt-get install -y \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libcups2 \ + libdrm2 \ + libgtk-3-0 \ + libxcomposite1 \ + libxdamage1 \ + libxrandr2 \ + libgbm1 \ + libxss1 \ + libasound2t64 + + - name: Install Python dependencies + run: | + python -m pip install --upgrade 'pip>=23.0,<25' + pip install --no-deps 'pyppeteer>=2.0.0,<3' 'beautifulsoup4>=4.12.0,<5' + + - name: Download Chromium for Pyppeteer + run: | + python -c "import asyncio; from pyppeteer import launch; asyncio.get_event_loop().run_until_complete(launch())" + + - name: Run site content analysis + run: | + python scripts/browser_site_analyzer.py \ + --base-url https://tyk.mintlify.app \ + --docs-json docs.json \ + --output-dir site_analysis_output \ + --report-file site_analysis_report.json \ + --wait-time ${{ github.event.inputs.wait_time || '3' }} \ + --timeout ${{ github.event.inputs.timeout || '30' }} + + - name: Create summary comment + if: always() + run: | + echo "## 🔍 Site Content Analysis Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Analysis completed for:** https://tyk.mintlify.app" >> $GITHUB_STEP_SUMMARY + echo "**Wait time:** ${{ github.event.inputs.wait_time || '3' }} seconds" >> $GITHUB_STEP_SUMMARY + echo "**Timeout:** ${{ github.event.inputs.timeout || '30' }} seconds" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -f site_analysis_report.json ]; then + echo "### Summary Statistics" >> $GITHUB_STEP_SUMMARY + echo '```json' >> $GITHUB_STEP_SUMMARY + python -c " + import json + with open('site_analysis_report.json', 'r') as f: + data = json.load(f) + summary = data['summary'] + print(f'Total pages analyzed: {summary[\"total_pages_analyzed\"]}') + print(f'Pages with sufficient content: {summary[\"pages_with_sufficient_content\"]}') + print(f'Pages with empty/insufficient content: {summary[\"pages_with_empty_content\"]}') + print(f'Browser failures: {summary[\"browser_failures\"]}') + " >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Show problematic pages if any + python -c " + import json + with open('site_analysis_report.json', 'r') as f: + data = json.load(f) + if data['empty_pages']: + print('### ❌ Pages with Content Issues') + for page in data['empty_pages'][:10]: # Show first 10 + print(f'- **{page[\"url\"]}**: {page[\"issues\"][0] if page[\"issues\"] else \"Unknown issue\"}') + if len(data['empty_pages']) > 10: + print(f'- ... and {len(data[\"empty_pages\"]) - 10} more pages') + " >> $GITHUB_STEP_SUMMARY + else + echo "❌ Analysis failed - no report generated" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "📄 **All analysis results are displayed above**" >> $GITHUB_STEP_SUMMARY + + - name: Fail job if critical issues found + if: always() + run: | + if [ -f site_analysis_report.json ]; then + python -c " + import json, sys + with open('site_analysis_report.json', 'r') as f: + data = json.load(f) + summary = data['summary'] + total = summary['total_pages_analyzed'] + empty = summary['pages_with_empty_content'] + + if total > 0: + empty_percentage = (empty / total) * 100 + print(f'Empty content percentage: {empty_percentage:.1f}%') + + # Fail if more than 20% of pages have empty content + if empty_percentage > 20: + print(f'❌ CRITICAL: {empty_percentage:.1f}% of pages have empty content (threshold: 20%)') + sys.exit(1) + else: + print(f'✅ Content quality acceptable: {empty_percentage:.1f}% empty pages') + else: + print('❌ CRITICAL: No pages were analyzed') + sys.exit(1) + " + fi diff --git a/.github/workflows/sync-versions-to-tyk-install.yml b/.github/workflows/sync-versions-to-tyk-install.yml new file mode 100644 index 0000000000..eeaa52012d --- /dev/null +++ b/.github/workflows/sync-versions-to-tyk-install.yml @@ -0,0 +1,95 @@ +name: Sync Component Versions to tyk-install + +on: + push: + branches: + - main + paths: + - 'developer-support/release-notes/overview.mdx' + workflow_dispatch: + +jobs: + sync-versions: + runs-on: ubuntu-latest + steps: + - name: Checkout tyk-docs + uses: actions/checkout@v7 + + - name: Checkout tyk-install + uses: actions/checkout@v7 + with: + repository: TykTechnologies/tyk-install + token: ${{ secrets.ORG_GH_TOKEN }} + path: tyk-install + + - name: Run version updater + id: updater + run: | + mkdir -p tyk-install/scripts + cp .github/scripts/sync-versions-updater.py tyk-install/scripts/updater.py + + python3 tyk-install/scripts/updater.py | tee updater-output.txt + + rm -rf tyk-install/scripts + + { + echo 'RESOLVED_VERSIONS<> "$GITHUB_OUTPUT" + + - name: Create or update PR in tyk-install + run: | + cd tyk-install + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + BRANCH="chore/sync-component-versions" + + # Reset the branch to the current main tip on every run so there is + # never more than one open PR for this sync: a rerun updates the + # existing branch/PR in place instead of creating a new one. + EXISTING_PR=$(gh pr list --repo TykTechnologies/tyk-install --head "$BRANCH" --state open --json number --jq '.[0].number') + + git checkout -b "$BRANCH" + + # Stage whatever updater.py actually touched instead of a hardcoded + # file list, so this step can't drift out of sync with the script. + git add -A + + if git diff --cached --quiet; then + echo "No version changes detected - files are already up to date." + exit 0 + fi + + git commit -m "chore: sync component versions" + git push origin "$BRANCH" --force + + PR_BODY=$(cat <<'BODY' + ## Summary + + Auto-generated by the [Sync Component Versions](https://github.com/TykTechnologies/tyk-docs/actions/workflows/sync-versions-to-tyk-install.yml) workflow in tyk-docs. + + Updates the pinned Tyk component versions across the self-managed, hybrid, and AI Studio Docker/Helm assets, resolved directly from the latest Docker Hub tags. + + **Resolved versions:** + \`\`\` + ${{ steps.updater.outputs.RESOLVED_VERSIONS }} + \`\`\` + BODY + ) + + if [ -n "$EXISTING_PR" ]; then + echo "Updated existing PR #$EXISTING_PR with the latest versions." + gh pr comment "$EXISTING_PR" --repo TykTechnologies/tyk-install --body "$PR_BODY" + gh pr edit "$EXISTING_PR" --repo TykTechnologies/tyk-install --add-reviewer MohammedELKheir + else + gh pr create \ + --repo TykTechnologies/tyk-install \ + --title "chore: sync component versions" \ + --body "$PR_BODY" \ + --base main \ + --head "$BRANCH" + fi + env: + GH_TOKEN: ${{ secrets.ORG_GH_TOKEN }} diff --git a/.github/workflows/trigger-docs-deploy.yml b/.github/workflows/trigger-docs-deploy.yml new file mode 100644 index 0000000000..3e0bc26b33 --- /dev/null +++ b/.github/workflows/trigger-docs-deploy.yml @@ -0,0 +1,152 @@ +name: Trigger Documentation Deployment + +on: + push: + branches: + - main + - 'release-*' + +permissions: + contents: read + actions: write + +jobs: + trigger-deploy: + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROBE_APP_ID }} + private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} + owner: TykTechnologies + + - name: Find the actual merged PR + id: get-pr + run: | + COMMIT_SHA="${{ github.sha }}" + COMMIT_MESSAGE="${{ github.event.head_commit.message }}" + echo "Looking for PR that was merged with commit: $COMMIT_SHA" + echo "Commit message: $COMMIT_MESSAGE" + + # Search for PRs that were merged with this exact commit SHA + echo "Searching for merged PR with commit SHA: $COMMIT_SHA" + + # Use a temporary file to avoid shell parsing issues + curl -s \ + -H "Authorization: token ${{ steps.app-token.outputs.token }}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${{ github.repository }}/pulls?state=closed&sort=updated&direction=desc&per_page=50" > /tmp/prs.json + + # Find PR with matching merge commit SHA + ACTUAL_PR_NUMBER=$(jq -r --arg commit "$COMMIT_SHA" '.[] | select(.merge_commit_sha == $commit) | .number' /tmp/prs.json | head -1) + + if [ -n "$ACTUAL_PR_NUMBER" ] && [ "$ACTUAL_PR_NUMBER" != "null" ]; then + # Get PR details + ACTUAL_PR_TITLE=$(jq -r --arg commit "$COMMIT_SHA" '.[] | select(.merge_commit_sha == $commit) | .title' /tmp/prs.json | head -1) + PR_AUTHOR=$(jq -r --arg commit "$COMMIT_SHA" '.[] | select(.merge_commit_sha == $commit) | .user.login' /tmp/prs.json | head -1) + + echo "Found actual merged PR: #$ACTUAL_PR_NUMBER - $ACTUAL_PR_TITLE by $PR_AUTHOR" + echo "pr_number=$ACTUAL_PR_NUMBER" >> $GITHUB_OUTPUT + echo "pr_title=$ACTUAL_PR_TITLE" >> $GITHUB_OUTPUT + echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT + echo "has_pr=true" >> $GITHUB_OUTPUT + + # Also try to extract original PR from commit message for context + ORIGINAL_PR=$(echo "$COMMIT_MESSAGE" | grep -oE '#[0-9]+' | head -1 | sed 's/#//') + if [ -n "$ORIGINAL_PR" ] && [ "$ORIGINAL_PR" != "$ACTUAL_PR_NUMBER" ]; then + echo "Found original PR reference in commit: #$ORIGINAL_PR" + echo "original_pr_number=$ORIGINAL_PR" >> $GITHUB_OUTPUT + echo "has_original_pr=true" >> $GITHUB_OUTPUT + else + echo "has_original_pr=false" >> $GITHUB_OUTPUT + fi + else + echo "No merged PR found via API, falling back to commit message parsing..." + + # Fallback to original method + PR_NUMBER=$(echo "$COMMIT_MESSAGE" | grep -oE '#[0-9]+' | head -1 | sed 's/#//') + + if [ -n "$PR_NUMBER" ]; then + echo "Found PR number in commit message: #$PR_NUMBER" + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "has_pr=true" >> $GITHUB_OUTPUT + + # Extract PR title (everything before the PR number reference) + PR_TITLE=$(echo "$COMMIT_MESSAGE" | sed 's/ (#[0-9]\+).*//' | head -1) + echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT + echo "has_original_pr=false" >> $GITHUB_OUTPUT + else + echo "No PR number found in commit message (direct push)" + echo "has_pr=false" >> $GITHUB_OUTPUT + echo "has_original_pr=false" >> $GITHUB_OUTPUT + fi + fi + + # Clean up temp file + rm -f /tmp/prs.json + + - name: Trigger production deployment + run: | + # Prepare the dispatch payload + if [ "${{ steps.get-pr.outputs.has_pr }}" = "true" ]; then + if [ "${{ steps.get-pr.outputs.has_original_pr }}" = "true" ]; then + # Include both actual PR and original PR + PAYLOAD=$(jq -n \ + --arg ref "production" \ + --arg pr_number "${{ steps.get-pr.outputs.pr_number }}" \ + --arg pr_title "${{ steps.get-pr.outputs.pr_title }}" \ + --arg original_pr_number "${{ steps.get-pr.outputs.original_pr_number }}" \ + --arg commit_sha "${{ github.sha }}" \ + --arg branch "${{ github.ref_name }}" \ + '{ + ref: $ref, + inputs: { + triggering_pr_number: $pr_number, + triggering_pr_title: $pr_title, + original_pr_number: $original_pr_number, + triggering_commit_sha: $commit_sha, + triggering_branch: $branch + } + }') + echo "Triggering deployment with PR #${{ steps.get-pr.outputs.pr_number }} (original: #${{ steps.get-pr.outputs.original_pr_number }})" + else + # Only actual PR + PAYLOAD=$(jq -n \ + --arg ref "production" \ + --arg pr_number "${{ steps.get-pr.outputs.pr_number }}" \ + --arg pr_title "${{ steps.get-pr.outputs.pr_title }}" \ + --arg commit_sha "${{ github.sha }}" \ + --arg branch "${{ github.ref_name }}" \ + '{ + ref: $ref, + inputs: { + triggering_pr_number: $pr_number, + triggering_pr_title: $pr_title, + triggering_commit_sha: $commit_sha, + triggering_branch: $branch + } + }') + echo "Triggering deployment with PR #${{ steps.get-pr.outputs.pr_number }}" + fi + else + PAYLOAD=$(jq -n \ + --arg ref "production" \ + --arg commit_sha "${{ github.sha }}" \ + --arg branch "${{ github.ref_name }}" \ + '{ + ref: $ref, + inputs: { + triggering_commit_sha: $commit_sha, + triggering_branch: $branch + } + }') + echo "Triggering deployment (direct push, no PR)" + fi + + curl -X POST \ + -H "Authorization: token ${{ steps.app-token.outputs.token }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/actions/workflows/deploy-docs.yml/dispatches \ + -d "$PAYLOAD" diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml new file mode 100644 index 0000000000..4cea719516 --- /dev/null +++ b/.github/workflows/validate-docs.yml @@ -0,0 +1,73 @@ +name: Validate Documentation + +on: + pull_request: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3.9' + + - name: Install dependencies + run: | + python -m pip install --upgrade 'pip>=23.0,<25' + pip install 'requests>=2.31.0,<3' + pip install 'pyyaml>=6.0,<7' + + - name: Validate documentation + run: | + echo "🔍 Running documentation validation..." + python scripts/validate_mintlify_docs.py . --validate-redirects --verbose + + - name: Check anchor fragments + run: | + echo "⚓ Checking internal anchor fragments..." + python scripts/validate_mintlify_docs.py . --check-anchors --links-only + + - name: Check for links that redirect + run: | + echo "↪️ Checking for internal links that hit a 3xx redirect..." + python scripts/validate_mintlify_docs.py . --check-redirecting-links --links-only + + - name: Check for insecure first-party links + run: | + echo "🔒 Checking for insecure http:// links to tyk.io..." + python scripts/validate_mintlify_docs.py . --check-insecure-links --links-only + + - name: Check for multiple H1 tags + run: | + echo "🔠 Checking for pages with multiple H1 tags..." + python scripts/validate_mintlify_docs.py . --check-multiple-h1 --links-only + + - name: Check for relative markdown links + run: | + echo "Checking for relative markdown links (must start with /)..." + RELATIVE_LINKS=$(grep -rEn --include="*.md" --include="*.mdx" '\]\([^/\)\s#][^)]*\)' . \ + | grep -vE '\]\((https?://|mailto:|ftp://|\{| +`flush_interval` does not need to be configured for SSE APIs. The Gateway automatically handles immediate flushing for `text/event-stream` responses. + + +### Example using Tyk as an SSE proxy + +For this we will need: + +* An SSE server. For this example we will use [Golang HTML 5 SSE example](https://github.com/kljensen/golang-html5-sse-example) +* An instance of the Tyk Gateway and optionally the Tyk Dashboard + +**Steps for Configuration:** +* Ensure the Gateway configurations detailed in the Setup section are set. +* Run the SSE server as per the example instructions. By default this runs on port `8000`. +``` +go run ./server.go +``` +* Publish an API with the following configuration: + 1. Set an appropriate listen path, e.g. `"listen_path": "/sse"` + 2. Strip the listen path, e.g. `"strip_listen_path": true,` + 3. Set the target url as the SSE server, e.g. the example SSE server:`"target_url": "http://host.docker.internal:8000"` + 4. Click Save, and wait for the Gateway to reload the API before testing it +* To test the protected SSE service via the API in the Tyk Gateway run: +```bash +curl http://localhost:8080/sse/events/ +``` +You should see a stream of updates from the server. In this example, you will see: + +```bash +Message: 20 - the time is 2013-03-08 21:08:01.260967 -0500 EST +Message: 21 - the time is 2013-03-08 21:08:06.262034 -0500 EST +Message: 22 - the time is 2013-03-08 21:08:11.262608 -0500 EST +``` + diff --git a/advanced-configuration/transform-traffic/looping.mdx b/advanced-configuration/transform-traffic/looping.mdx new file mode 100644 index 0000000000..4f1bb82563 --- /dev/null +++ b/advanced-configuration/transform-traffic/looping.mdx @@ -0,0 +1,308 @@ +--- +title: "Internal Routing" +description: "Learn how to route requests to other endpoints or APIs within Tyk Gateway without network calls, using the tyk:// URL scheme in a URL rewrite target" +keywords: "internal routing, looping, tyk:// scheme, URL rewrite, self-referential routing, internal API" +sidebarTitle: "Internal Routing" +--- + +Internal routing redirects a matched request to a different endpoint or API hosted on the same Tyk Gateway instance, without making an outbound network call. Tyk dispatches the request directly to the target API's middleware chain in memory, so the request is processed a second time without any network overhead. + +Internal routing is configured through the **URL rewrite** middleware. See [Path Modification](/transform-traffic/url-rewriting) for how to configure rewrite rules. + +## How It Works + +When the URL rewrite middleware produces a target beginning with `tyk://`, Tyk resolves the target to an API definition and dispatches the request directly to that API's middleware chain. Listen path matching is bypassed entirely - the request does not re-enter the HTTP router. + +**What is re-evaluated on each loop:** +- The target API's full middleware chain, including endpoint-level middleware for the matched path. + +**What is not re-evaluated:** +- **Listen path matching** is bypassed; the target API is resolved by identifier, not by URL. +- **Original query parameters** are dropped; only query parameters explicitly included in the rewrite target survive into the loop. +- **Authentication** depends on the auth method in use and the method used to address the target. See [Authentication on Looped Requests](#authentication-on-looped-requests). +- **Rate limits and quotas** are skipped by default. See [Rate Limits and Quotas](#rate-limits-and-quotas). + + +## Addressing the Target + +A `tyk://` URL takes one of two forms depending on whether the destination is within the same API or on a different API. + +### Same API + +``` +tyk://self/ +``` + +The hostname `self` resolves to the same API definition handling the current request. Use this when the routing logic and the destination endpoint are in the same API definition. + +For example, a rewrite target of `tyk://self/internal/transform` routes to the `/internal/transform` endpoint on the same API, running it through that endpoint's configured middleware. + +### Different API + +``` +tyk:/// +``` + +Tyk resolves the identifier against each registered API in the following order: + +1. The API ID (`info.id`) - exact match +2. The API's database ID (`info.dbId`) - exact match +3. The API's display name (`info.name`), slugified: non-alphanumeric characters are replaced with `-`, match is case-insensitive + +For example, an API named `"Books Backend"` can be addressed as `tyk://books-backend/endpoint` or `tyk://Books-Backend/endpoint`. Using the API ID directly, such as `tyk://4e3c8b9f2a1d4f67b8e3c1a2f5d9e0c7/endpoint`, is more robust in automated environments where the API name may change. + + +The listen path is not a valid identifier. If the identifier does not match any registered API, Tyk returns `HTTP 500` with the error `"Can't detect loop target"`. + + +If using Tyk Classic, the API ID is [`api_id`](/api-management/gateway-config-tyk-classic#param-api-id), the database ID is [`id`](/api-management/gateway-config-tyk-classic#param-id), and the display name is [`name`](/api-management/gateway-config-tyk-classic#param-name), all in the root of the API definition. In Tyk Classic, API names can include category tags in the form `#tag`; these are stripped before name matching, so an API named `"Books Backend #catalog"` can be addressed as `tyk://books-backend/endpoint`. + +## Loop Behavior + +### Authentication on Looped Requests + +Authentication behavior on a looped request depends on the auth method in use and whether the loop targets the same API or a different one. This should be considered carefully when designing an internal routing chain. + +**Same API (`tyk://self/...`)** + +When looping to an endpoint in the same API, the behavior depends on the API's auth method as follows: + +- **[Auth Token](/api-management/authentication/bearer-token)**: the auth check is only performed on the original request. +- **[Basic Auth](/api-management/authentication/basic-authentication) and [Certificate Auth](/api-management/authentication/certificate-auth)**: the auth check is run on each loop, but succeeds because Tyk forwards the original request headers into the loop, so the credential is still present and unchanged. +- **[JWT](/basic-config-and-security/security/authentication-authorization/json-web-tokens), [OAuth 2.0](/api-management/authentication/oauth-2), [HMAC](/basic-config-and-security/security/authentication-authorization/hmac-signatures)**: the auth check is run on every loop and must succeed. + +For example, an API using JWT authentication that rewrites to `tyk://self/internal-endpoint` will validate the JWT again on the looped request. The token must still be present and valid. + +**Different API (`tyk:///...`)** + +Each API definition is an independent security boundary. Tyk runs the target API's full authentication middleware chain as it would for any other incoming request, validating the request fresh against the target API's own credentials. + +The request must carry credentials that are valid for the target API. If those credentials differ from the ones used on the first hop, for example when the target API uses a different API key or a different header, add the required credential to the request with a [request header transformation](/api-management/traffic-transformation/request-headers) before the rewrite target is reached. + + +APIs secured with [Certificate Auth](/api-management/authentication/certificate-auth) cannot be used as targets for internal looping. The internal transport used for different-API routing does not carry TLS connection state, so when the request is presented to the target API there is no client certificate to validate and Tyk will reject the request with `HTTP 403`. + + +### Rate Limits and Quotas + +The Gateway's Session contains details of access and usage permissions, such as rate limits and quotas, for the requesting client. Tyk's standard rate limiting middleware increments the counters for each API request and applies limits to prevent overuse. + +A loop represents an internal leg of a single external client request: if Tyk incremented the counters and applied limits at each hop, a single request would be double-counted and clients could be charged multiple times for one request. + +By default, rate limit and quota increments and checks are performed only on the original API, not in the looped requests. + +**Same API (`tyk://self/...`)** + +Authentication is not re-run on a self-loop, so the originating Session is used throughout the entire chain. There is one set of rate limit and quota counters, decremented once on the original request. All subsequent loop legs within the same API skip rate limit and quota checks by default. + +**Different API (`tyk:///...`)** + +If the target API [performs authentication](#authentication-on-looped-requests) it loads its own Session from Redis during authentication. That Session may carry rate limit and quota counters independent of the originating API. These are skipped by default for looped requests. + +**Checking Rate Limits on Looped APIs** + +An [optional control](#loop-controls) can be configured when looping that will run the rate limiting middleware in the target API. If this is enabled for a loop to the same API, this will lead to double counting and is not recommended. If it is enabled for a loop to a different API, the counters in the target API's Session will be incremented and checked against the limits configured for that API. + +Rate limit and quota deductions happen in memory during middleware processing. The updated Session is written back to Redis only after the full request chain - including all loop legs - has completed. + +### Loop Depth + +To guard against infinite loops, Tyk enforces a configurable maximum of five loops per request. When this limit is exceeded, Tyk returns `HTTP 500`: + +``` +Loop level too deep. Found more than 5 loops in single request +``` + + +## Loop Controls + +The behavior of a looped request can be modified by appending control parameters to the `tyk://` URL. These parameters are consumed by Tyk and stripped before the looped request is dispatched, so they do not reach the target upstream. + +| Parameter | Description | +| :--- | :--- | +| `check_limits=true` | Enforce rate limits and quotas on the looped request. For same API loops, this double-counts the request against the client's allowance and is not recommended. For different API loops, limits are enforced against the target API's own session. | +| `method=` | Override the HTTP method of the looped request, for example `method=POST`. | +| `loop_limit=` | Set the maximum number of loops permitted in this request chain. Default is `5`. Can only be set in the original loop - subsequent loops in the same request chain cannot change it. | + +For example, to loop to the Books Backend API enforcing its rate limits, use the rewrite target `tyk://books-backend/fiction/9780?check_limits=true`. + +## Internal-Only APIs + +An API or an individual endpoint can be restricted so it is only accessible via internal routing, not from external HTTP requests. + +### API-Level + +Mark an entire API as internal to exclude it from the HTTP router. External requests return `HTTP 404`. The API is only reachable via `tyk://`. + +**Tyk OAS** - set `internal: true` in the `info.state` section of the Tyk Vendor Extension: + +```json +"x-tyk-api-gateway": { + "info": { + "state": { + "active": true, + "internal": true + } + } +} +``` + +If using Tyk Classic, set `"internal": true` at the root of the API definition. + +### Endpoint-Level + +Mark individual endpoints as internal to allow the rest of the API to remain externally accessible while restricting specific paths to loop context only. External requests to a marked endpoint are rejected with `HTTP 403`. + +**Tyk OAS** - add `internal` to the operation in the `middleware.operations` section: + +```json +"operations": { + "getContract": { + "internal": { + "enabled": true + } + } +} +``` + +If using Tyk Classic, add entries to `extended_paths.internal`: + +```json +"extended_paths": { + "internal": [ + { + "path": "/contract", + "method": "GET" + } + ] +} +``` + +## Worked Example + +This example demonstrates three internal routing capabilities together: authentication on a looped API, rate limit enforcement via loop controls, and routing to a different upstream service. + +Acme Publishing's Books API serves free previews to all clients without authentication. Clients with a download subscription can retrieve the full book by adding `?download=true` and providing a valid access token. Download requests are routed internally to a separate Download API that validates the subscriber's token, enforces their download allowance, and proxies to a dedicated download service. + +The setup uses two APIs: + +- **Books API** (external, keyless, listen path `/books`, upstream `http://books-service`): Receives all client requests. Default requests are rewritten to the upstream's `preview` path. Requests with `?download=true` are looped internally to the Download API with rate limit enforcement enabled. +- **Download API** (internal, auth token required, upstream `http://download-service`): Validates the subscriber's access token, enforces their download allowance, and proxies to the download service. Unreachable directly from outside Tyk Gateway. + + +{/* DIAGRAM PLACEHOLDER: Book Download Example */} +{/* + Diagram: Book Download Pipeline + + Purpose: Show two request paths through the Books API - one that loops internally + to the Download API for authenticated download, one that proxies directly to the + upstream preview path. The key insight is that the same external URL serves two + different processing paths based on a query parameter, with the download path + enforcing authentication and rate limiting through an internal-only API. + + Structure: Two parallel vertical flows, sharing the same Books API entry point. + + Left flow (default preview): + 1. Client: GET /books/fiction/9780 + 2. Books API (rounded rectangle) + 3. URL rewrite: no advanced trigger fires + 4. Arrow to books-service/preview/fiction/9780 (upstream, grey rectangle) + 5. Book preview response returned to client + + Right flow (authenticated download): + 1. Client: GET /books/fiction/9780?download=true + Authorization header + 2. Books API (same entry box as left flow) + 3. URL rewrite: advanced trigger fires (download=true); ?download=true dropped + 4. Dashed arrow labelled "tyk://download-api/fiction/9780?check_limits=true (internal loop)" + 5. Download API (dashed border, labelled "internal only - HTTP 404 externally"): + - Auth token validation shown as a sub-box (HTTP 401 exit if invalid) + - Rate limit enforcement shown as a sub-box (check_limits=true) + 6. Arrow to download-service/fiction/9780 (separate upstream box, distinct from books-service) + 7. Full book response returned to client + + Key visual elements: + - The Download API box must use a dashed border and "internal only" label to + reinforce it is not reachable externally + - The tyk:// arrow must be labelled as an in-memory dispatch with "?download=true + dropped" annotated at the departure point to show the query param is stripped + - The two sub-boxes inside the Download API (auth check, rate limit check) are + important - they are what justifies having a separate internal API + - The two upstream boxes (books-service, download-service) must be visually + distinct to show the loop routes to a different backend entirely + - Show the HTTP 401 exit path from the auth check sub-box + + Design notes: + - Keep the two flows visually parallel so the contrast is obvious at a glance + - The decision point (advanced trigger fires / does not fire) should be clearly + labelled on the Books API box, not a separate diamond node + - Colour scheme suggestion: grey for the default preview flow, blue for the + download flow, red for the HTTP 401 rejection exit +*/} + + +**Tyk OAS Configuration** + +Books API - the `GET /{category}/{id}` endpoint with URL rewrite: + +```json +"operations": { + "getBook": { + "urlRewrite": { + "enabled": true, + "pattern": "/([^/]+)/([^/]+)", + "rewriteTo": "preview/$1/$2", + "triggers": [ + { + "condition": "any", + "rewriteTo": "tyk://download-api/$1/$2?check_limits=true", + "rules": [ + { "in": "query", "name": "download", "pattern": "true", "negate": false } + ] + } + ] + } + } +} +``` + +Download API - marked internal at the API level, with auth token authentication and a separate upstream: + +```json +"x-tyk-api-gateway": { + "info": { + "name": "Download API", + "state": { "active": true, "internal": true } + }, + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "authToken": { + "enabled": true + } + } + } + }, + "upstream": { "url": "http://download-service" } +} +``` + +The `securitySchemes` key `authToken` must match a scheme defined in the OpenAPI `components.securitySchemes` section of the Download API's OAS document. See [Auth Token](/api-management/authentication/bearer-token) for full authentication configuration detail. + +If using Tyk Classic, mark the Download API internal with `"internal": true` at the root of its API definition. The URL rewrite on the Books API is configured in `extended_paths.url_rewrites` using the same trigger structure described in [Path Modification](/transform-traffic/url-rewriting). + +**Outcomes** + +| Request | Processed by | Response | +| :--- | :--- | :--- | +| `GET /books/fiction/9780` | Books API → `books-service/preview/fiction/9780` | Book preview | +| `GET /books/fiction/9780?download=true` with valid token | Books API → Download API → `download-service/fiction/9780` | Full book | +| `GET /books/fiction/9780?download=true` with missing or invalid token | Books API → Download API → rejected | `HTTP 401` | + +For the default request, no advanced trigger fires. The basic trigger captures `fiction` as `$1` and `9780` as `$2`, and the request is proxied to `books-service/preview/fiction/9780`. + +For the download request with a valid token, the advanced trigger fires on `download=true` and rewrites to `tyk://download-api/fiction/9780?check_limits=true`. The `?download=true` query parameter is dropped at the loop boundary. It is not included in the rewrite target and does not reach the Download API. The loop control parameter `check_limits=true` is consumed by Tyk and stripped before dispatch. The Download API validates the token, decrements the subscriber's download allowance against their Session, and proxies to `download-service/fiction/9780`. + +For the download request with a missing or invalid token, the Download API's authentication middleware rejects the request with `HTTP 401` before it reaches the upstream. + +A direct external request to the Download API's listen path returns `HTTP 404`. It is excluded from the HTTP router entirely. diff --git a/advanced-configuration/transform-traffic/soap-rest.mdx b/advanced-configuration/transform-traffic/soap-rest.mdx new file mode 100644 index 0000000000..0e292f6b8f --- /dev/null +++ b/advanced-configuration/transform-traffic/soap-rest.mdx @@ -0,0 +1,194 @@ +--- +title: "Transformation Use Case: SOAP To REST" +description: "How to transform SOAP API to REST API in Tyk" +keywords: "Traffic Transformation, SOAP, REST, SOAP to REST" +sidebarTitle: "SOAP To REST" +--- + +You can transform an existing SOAP service to a JSON REST service. This can be done from the Tyk Dashboard with no coding involved and should take around 10 minutes to perform the transform. + +We also have a video which walks you through the SOAP to REST transform. + + + +## Prerequisites + +An existing SOAP service and the WSDL definition. For this example, we will use: + +- Upstream Target - [https://www.dataaccess.com/webservicesserver/numberconversion.wso](https://www.dataaccess.com/webservicesserver/numberconversion.wso) +- The WSDL definition from - [https://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL](https://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL) +- Postman Client (or other endpoint testing tool) + +## Steps for Configuration + +1. **Import the WSDL API** + + 1. Select APIs from the System Management menu + + APIs Menu + + 2. Click Import API + + Import API + + 3. Select **From WSDL** from the Import an API Definition window + 4. In the **Upstream Target** field, enter `https://www.dataaccess.com/webservicesserver/numberconversion.wso` as listed in the Prerequisites. + 5. Paste the WSDL definition from the link in Prerequisites + 6. Click **Generate API**. You should now have an API named `NumberConversion` in your API list + + NumberService API + +2. **Add the transforms to an Endpoint** + + 1. From the API list, select Edit from the Actions menu for the `NumberConversion` API + 2. Select the **Endpoint Designer** tab. You should see 2 POST endpoints that were imported. We will apply the transforms to the `NumberToWords` endpoint. + + Endpoints + + 3. Expand the `NumberToWords` endpoint. The following plugins should have been added as part of the import process. + + - URL rewrite + - Track endpoint + + + + + To make the URL a little friendlier, we're going to amend the Relative Path to just `/NumberToWords`. Update your API after doing this. + + + + 4. Add the following plugins from the **Plugins** drop-down list: + + - Body transform + - Modify headers + +3. **Modify the Body Transform Plugin** + + **Set up the Request** + + We use the `{{.FieldName}}` Golang template syntax to access the JSON request. For this template we will use `{{.numberToConvert}}`. + + 1. Expand the Body transform plugin. From the Request tab, copy the following into the Template section: + + ```xml + + + + + {{.numberToConvert}} + + + + ``` + + 2. In the Input field, enter the following: + + ```json + { + "numberToConvert": 35 + } + ``` + + + + The '35' integer can be any number you want to convert + + + + 3. Click **Test**. You should get the following in the Output field: + + ```xml + + + + + 35 + + + + ``` + + **Set up the Response** + + Again, for the response, we will be using the `{{.FieldName}}` syntax as the following `{{.Envelope.Body.NumberToDollarsResponse.NumberToDollarsResult}}` + + 1. For the Input Type, select XML + + Response Input Type + + 2. In the Template section enter: + + ```yaml + { + "convertedNumber": "{{.Envelope.Body.NumberToDollarsResponse.NumberToDollarsResult}}" + } + ``` + 3. Enter the following into the input field: + + ```xml + + + + thirty five dollars + + + + ``` + 4. Click Test. You should get the following in the Output field: + + ```json + { + "convertedNumber": "thirty five dollars" + } + ``` + +5. **Change the Content-Type Header** + + We now need to change the `content-type` header to allow the SOAP service to receive the payload in XML. We do this by using the **Modify header** plugin + + 1. Expand the Modify Header plugin + 2. From the **Request** tab enter the following in the **Add this header** section + + - Header Name: `content-type` + - Header Value: `text/xml` + + 3. Click Add + + Modify Header Request + + 4. From the **Response** tab enter the following in the **Add this header** section + + - Header Name: `content-type` + - Header Value: `application/json` + + Modify Header Response + + 5. Click **Add** + 6. Click **Update** + + Update API + +## Testing the Endpoint + +You now need to test the endpoint. We are going to use Postman. + + + +We have not set up any Authentication for this API, it has defaulted to `Open (Keyless)`. + + + + +1. Copy the URL for your NumberConversion API with the NumberToWords endpoint - `https://tyk-url/numberconversion/NumberToWords/` +2. Paste it as a POST URL in the Postman URL Request field +3. Enter the following as a raw Body request + +```json +{ + "numberToConvert": 35 +} +``` +Your Postman request should look similar to below (apart from the URL used) + +Postman + diff --git a/advanced-configuration/websockets.mdx b/advanced-configuration/websockets.mdx new file mode 100644 index 0000000000..c0d66daedd --- /dev/null +++ b/advanced-configuration/websockets.mdx @@ -0,0 +1,58 @@ +--- +title: "Websockets" +description: "Learn how to configure and use WebSockets with Tyk" +keywords: "websockets, Other Protocol" +sidebarTitle: "WebSockets" +--- + +As from Tyk gateway v2.2, Tyk supports transparent WebSocket connection upgrades. To enable this feature, set the `enable_websockets` value to `true` in your `tyk.conf` file. + +WebSocket proxying is transparent, Tyk will not modify the frames that are sent between client and host, and rate limits are on a per-connection, not per-frame basis. + +The WebSocket upgrade is the last middleware to fire in a Tyk request cycle, and so can make use of HA capabilities such as circuit breakers and enforced timeouts. + +Tyk needs to decrypt the inbound and re-encrypt the outbound for the copy operations to work, Tyk does not just pass through the WebSocket. When the target is on default SSL port you must explicitly specify the target url for the API: + +```{.copyWrapper} +https://target:443/ +``` + +## WebSocket Example + +We are going to set up Tyk with a WebSocket proxy. Set up Tyk using our Docker [guide](/tyk-self-managed/install/docker). + +We will be using the [Postman WebSocket Echo Service](https://blog.postman.com/introducing-postman-websocket-echo-service/) to test the connection. + +**Steps for Configuration** + +1. **Setup the API in Tyk** + + Create a new API in Tyk. For this demo we are going to select Open (Keyless) as the **Authentication mode**. + + Set the **Target URL** to `wss://ws.postman-echo.com/raw` + +2. **Test the Connection** + + 1. From Postman, select **File > New > WebSocket Request** (or from **Workspace > New > WebSocket Request** if using the web based version). + + Postman WebSocket Request + + 2. Enter your Tyk API URL in the **Enter server URL** field (minus the protocol). + 3. Enter some text in the **New Message** field and click **Send**. + 4. You will see a successful connection. + + Postman WebSocket Connection Result + + + + + If your API uses an Authentication mode other than Open (Keyless), add the details in the Header tab. + + + +An example Header configuration for using an Authentication Token with an API: + +Postman WebSocket Connection Result with Authorization token + +See the [Access an API](/api-management/gateway-config-managing-classic#access-an-api) tutorial for details on adding an Authentication Token to your APIs. + diff --git a/ai-management/ai-studio/admin-apps.mdx b/ai-management/ai-studio/admin-apps.mdx new file mode 100644 index 0000000000..bb6ce56d63 --- /dev/null +++ b/ai-management/ai-studio/admin-apps.mdx @@ -0,0 +1,57 @@ +--- +title: "Manage Apps in Tyk AI Studio" +description: "How to manage user-created applications in Tyk AI Studio as a Studio Administrator." +keywords: "AI Studio, AI Management, Apps, Admin" +sidebarTitle: "App Management" +--- + +As a Studio Administrator, you are responsible for managing user-created applications (Apps) that interact with Large Language Models (LLMs) and other resources through the Tyk AI Gateway. This page provides an overview of the App lifecycle and the administrative actions you can perform. + +## App Lifecycle + +The lifecycle of an App in Tyk AI Studio follows several distinct stages, from creation to deactivation. As an administrator, you play a key role in this process, particularly in the review and activation stages. + +### 1. Creation +An AI Developer creates an [App](/ai-management/ai-studio/ai-portal-app), defining its name, description, and selecting the necessary resources such as LLMs, Tools, and Data Sources. At this stage, the App's credentials are created but are inactive. + +### 2. Review +Once an App is created, it appears in the Apps list in the Admin section. As an administrator, you should review the App's configuration to ensure it complies with your organization's policies. This includes checking the selected LLMs, Data Sources, and Tools for appropriateness and security. + +### 3. Activation / Rejection +After reviewing the App, you can either activate or reject it. +- **Activation:** By toggling the "Active" switch for the App's credentials, you approve the App. It can now be used to make authenticated requests to the [AI Gateway](/ai-management/ai-studio/proxy). +- **Rejection:** If the App does not meet your organization's standards, you can leave its credentials inactive. It's recommended to communicate with the user who created the App to explain why it was not approved. + +### 4. Monitoring +Once an App is active, you should monitor its usage and performance. The App Details view provides insights into token usage, costs, and detailed request/response logs. This allows you to track resource consumption and identify any unusual activity. + +### 5. Deactivation +If an App is no longer needed, is misbehaving, or violates policies, you can deactivate it at any time by toggling its credentials to "Inactive". This immediately revokes its access to the AI Gateway. + +## Managing Apps + +The **Apps View** in the AI Studio dashboard is your central hub for managing all user-created applications. + +### Viewing App Details +From the Apps list, you can click on any App to see its detailed view. This view provides a comprehensive overview of the App, including: +- **App Information:** Name, description, owner, and associated LLMs, Data Sources, and Tools. +- **Credential Information:** The App's Key ID and its active status. +- **Token Usage and Cost:** A graph displaying token consumption and associated costs over time. +- **Proxy Logs:** Detailed logs of inbound and outbound requests, essential for troubleshooting and auditing. + +### Editing Apps and Approving Credentials +In the App Edit View, you can: +- Modify the App's name and description. +- Change the associated LLMs, Data Sources, and Tools. +- **Approve or reject the App** by toggling the **Active** switch for its credentials. This is a critical governance control. + +### Privacy Level Validation +The system enforces data governance by validating the privacy levels of resources. When a user creates or an admin updates an App, the privacy levels of the selected Data Sources cannot be higher than the privacy levels of the selected LLMs. + +Privacy levels are categorized from low to high: +- **Public** +- **Internal** +- **Confidential** +- **Restricted (PII)** + +If a user attempts to associate a "Restricted" Data Source with an LLM that only has an "Internal" privacy level, the system will prevent this and display an error. This ensures that sensitive data is not inadvertently exposed to less secure models. As an admin, you should be aware of this when reviewing and approving Apps. diff --git a/ai-management/ai-studio/ai-cli-app.mdx b/ai-management/ai-studio/ai-cli-app.mdx new file mode 100644 index 0000000000..937afc355c --- /dev/null +++ b/ai-management/ai-studio/ai-cli-app.mdx @@ -0,0 +1,126 @@ +--- +title: "Tutorial: Create an AI CLI App with NodeJS and Tyk AI Studio" +description: "Learn how to use your Tyk AI Studio App credentials to build a simple command-line interface (CLI) application with NodeJS." +keywords: "AI Studio, Tutorial, App, NodeJS, CLI" +sidebarTitle: "Tutorial: AI CLI App" +--- + +In this tutorial, you will learn how to use the credentials from an App you've created in Tyk AI Studio to build a simple command-line interface (CLI) application with NodeJS. + +This application will take a text prompt from you, send it to an LLM via the Tyk AI Gateway, and print the response. + +## Prerequisites + +Before you begin, you will need: +- **An approved App in Tyk AI Studio:** You should have already created an App and had it approved by a Studio Administrator. + + If you haven't, please follow the guide on [App Management for Consumers](/ai-management/ai-studio/ai-portal-app). +- **Your App's Secret:** You will need these to authenticate your application. +- **Node.js and npm installed:** This tutorial uses NodeJS. You can download it from [nodejs.org](https://nodejs.org/). +- **An LLM configured in AI Studio:** Your App must be configured to use at least one LLM. This tutorial will use OpenAI LLM models. + +## Instructions + +### Step 1: Set up your NodeJS project + +First, create a new folder for your project and initialize a new NodeJS project within it. + +```bash +mkdir tyk-ai-cli-app +cd tyk-ai-cli-app +npm init -y +npm install ai @ai-sdk/openai +``` + +### Step 2: Create the CLI application file + +Create a new file named `index.js` in your project folder. This is where you will write the code for your CLI application. + +```bash +touch index.js +``` + +### Step 3: Write the application code + +Open `index.js` in your favorite code editor and add the following code. Be sure to replace the placeholder values with your actual App credentials and AI Gateway endpoint. + +```javascript Expandable +import readline from "node:readline"; +import { generateText } from "ai"; +import { createOpenAI } from "@ai-sdk/openai"; + +// ---- CONFIG ---- +const apiKey = "YOUR_API_KEY"; +const baseURL = "YOUR_TYK_AI_STUDIO_GATEWAY_URL"; + +// Create OpenAI-compatible client +const openai = createOpenAI({ + apiKey, + baseURL +}); + +// CLI interface +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: "You> " +}); + +console.log("LLM CLI started. Type a prompt and press enter.\n"); +rl.prompt(); + +rl.on("line", async (line) => { + const prompt = line.trim(); + + if (!prompt) { + rl.prompt(); + return; + } + + try { + const { text } = await generateText({ + model: openai("gpt-4o-mini"), + prompt + }); + + console.log("\nLLM>", text, "\n"); + } catch (err) { + console.error("Error:", err.message); + } + + rl.prompt(); +}); +``` + +### Step 4: Run your CLI App + +Now you can run your application from your terminal. + +```bash +node index.js +``` + +Example: + +``` +LLM CLI started. Type a prompt and press enter. + +You> explain kubernetes in simple terms + +LLM> Kubernetes is a system that helps run and manage containers... +``` + +Congratulations! You have successfully used your Tyk AI Studio App credentials to build a working AI-powered CLI application. + +## Frequently Asked Questions + + + +A 401 error means your request is not properly authenticated. Check the following: +- Make sure your App has been approved by an administrator and its credentials are active. +- Double-check that you have correctly copied your Secret into the `index.js` file. + + +Yes, you can use any LLM that you have configured in your App. + + diff --git a/ai-management/ai-studio/ai-portal-app.mdx b/ai-management/ai-studio/ai-portal-app.mdx new file mode 100644 index 0000000000..62bf4e74e3 --- /dev/null +++ b/ai-management/ai-studio/ai-portal-app.mdx @@ -0,0 +1,68 @@ +--- +title: "Create an App in Tyk AI Studio" +description: "How to create and manage your applications in Tyk AI Studio as an AI Developer." +keywords: "AI Studio, AI Management, Apps, AI Builder, AI Developer" +sidebarTitle: "Create App" +--- + +As an AI Developer, you can create and manage applications (Apps) to interact with the AI services offered through Tyk AI Studio. An App bundles the resources you need (like specific LLMs, Tools, or Data Sources) and provides you with credentials to make authenticated API requests. + +## Prerequisites + +Before creating an App, ensure that an LLM provider is already configured and activated in the proxy. If not, a Studio Administrator must first [create and enable an LLM provider](/ai-management/ai-studio/llm-management#how-to-create-a-llm-provider). + +## Creating an App + +To create a new App, follow these steps: + +1. Go to the **AI Portal** tab, navigate to the **Apps** section in the side navigation. +2. Click the **+ ADD APP** button. +3. Fill in the required details for your App: + * **Name:** A descriptive name for your application. + * **Description:** A brief summary of what your application does. + * **LLMs:** Select the Large Language Models you want and click the **add** button to include them in your App configuration. + * **Data Sources:** If your application needs to use Retrieval-Augmented Generation (RAG), select the required Data Sources. + * **Tools:** Select any Tools (external APIs) your application needs to use. +4. Click **Create App**. + + AI Portal Create App + +Your App will be created, and you will be taken to the App Details view. + + +**App Approval:** After you create an App, its credentials are not yet active. A Studio Administrator needs to review and approve your App before you can use it. + +For more information on the approval process, see the [Admin App Management documentation](/ai-management/ai-studio/admin-apps). + + +## Getting Your Credentials + +Once your App has been approved by an administrator, you can retrieve the credentials needed to make API requests. + +From the App Details view, you will find your **Credential Information**: +- **Key ID:** This is the public identifier for your credentials. +- **Secret:** This is your secret API key. Treat it like a password and do not expose it in client-side code. + + AI Portal View App Credentials + +You will use this Secret to authenticate your requests to the Tyk AI Gateway. + +## Getting LLM Access Details + +In addition to your credentials, you can also find the **LLM Access Details** in the **App Details** view. + +AI Portal View LLM Access Details + +## Monitoring Your App + +The App Details view also allows you to monitor the usage and performance of your application. + +- **Token Usage and Cost:** A graph shows you how many tokens your App is consuming and the associated costs, which can be filtered by date. +- **App Interaction:** You can see number of interactions over time, which can help you understand usage patterns. + +## Next Steps + +Now that you know how to create an App and get your credentials, you're ready to start building! + +Follow our tutorial to learn how to use your App's API key in a simple command-line application: +- **[Tutorial: Create an AI CLI App](/ai-management/ai-studio/ai-cli-app)** diff --git a/ai-management/ai-studio/ai-portal.mdx b/ai-management/ai-studio/ai-portal.mdx new file mode 100644 index 0000000000..a067b88b08 --- /dev/null +++ b/ai-management/ai-studio/ai-portal.mdx @@ -0,0 +1,198 @@ +--- +title: "AI Portal in Tyk AI Studio" +description: "How AI Portal works?" +keywords: "AI Studio, AI Management, AI Portal" +sidebarTitle: "AI Portal" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +The Tyk AI Studio's AI Portal provides a user-friendly web interface where end users can interact with configured AI capabilities. It serves as the primary access point for users to engage with Chat Experiences, view documentation, and manage their account settings. + +## Purpose + +The main goals of the AI Portal are: + +* **Unified User Experience:** Offer a cohesive interface for accessing all AI capabilities configured within Tyk AI Studio. +* **Self-Service Access:** Enable users to independently access and utilize AI features without administrator intervention. +* **Contextual Documentation:** Provide integrated documentation and guidance for available AI services. +* **Account Management:** Allow users to manage their own profile settings and view usage information. +* **Secure Access Control:** Enforce permissions based on teams and organizational policies. + +## Key Features + +* **Chat Interface:** Access to all [Chat Experiences](/ai-management/ai-studio/chat-interface) the user has permission to use, with a clean, intuitive UI for conversational interactions. +* **Resource Catalogues:** Browse and subscribe to available LLMs, Data Sources, and [Tools](/ai-management/ai-studio/tools) through dedicated catalogue interfaces. +* **Application Management:** Create and manage [Apps](/ai-management/ai-studio/admin-apps) that integrate LLMs, tools, and data sources for API access. +* **Documentation Hub:** Integrated documentation for available AI services, tools, and data sources. +* **User Profile Management:** Self-service capabilities for updating profile information and preferences. +* **History & Favorites:** Access to past chat sessions and ability to bookmark favorite conversations. +* **Responsive Design:** Optimized for both desktop and mobile devices for consistent access across platforms. +* **Customizable Themes:** Support for light/dark mode and potentially organization-specific branding. +* **Notifications:** System alerts and updates relevant to the user's AI interactions. + +## Using the AI Portal + +Users access the AI Portal through a web browser at the configured URL for their Tyk AI Studio installation. + +1. **Authentication:** Users log in using their credentials (username/password, SSO, or other configured authentication methods). +2. **Home Dashboard:** Upon login, users see a dashboard with available Chat Experiences and recent activity. +3. **Resource Discovery:** Users can browse catalogues of available LLMs, Data Sources, and Tools to which they have access. +4. **Application Creation:** Users can create Apps by selecting and subscribing to the LLMs, tools, and data sources they need. +5. **Chat Selection:** Users can select from available Chat Experiences to start or continue conversations. +6. **Documentation Access:** Users can browse integrated documentation to learn about available capabilities. +7. **Profile Management:** Users can update their profile settings, preferences, and view usage statistics. + + AI Portal Dashboard + +## Configuration (Admin) + +Administrators configure the AI Portal through the Tyk AI Studio admin interface: + +* **Portal Branding:** Customize logos, colors, and themes to match organizational branding. +* **Available Features:** Enable or disable specific portal features (chat, documentation, etc.). +* **Authentication Methods:** Configure login options (local accounts, SSO integration, etc.). +* **Default Settings:** Set system-wide defaults for user experiences. +* **Access Control:** Manage which teams can access the portal and specific features within it. +* **Custom Content:** Add organization-specific documentation, welcome messages, or announcements. + + Portal Configuration + +## API Access + +While the AI Portal primarily provides a web-based user interface, it is built on top of the same APIs that power the rest of Tyk AI Studio. Developers can access these APIs directly for custom integrations: + +* **Authentication API:** `/api/v1/auth/...` endpoints for managing user sessions. +* **Chat API:** `/api/v1/chat/...` endpoints for programmatic access to chat functionality. +* **User Profile API:** `/api/v1/users/...` endpoints for managing user information. +* **Datasource API:** `/datasource/{dsSlug}` endpoints for querying configured data sources, generating embeddings, and metadata filtering. + +### Datasource API + +The Datasource API provides direct access to configured vector stores for semantic search, vector-based search, metadata filtering, and embedding generation. + +#### Text Search + +Perform semantic search using a natural language query. The query text is automatically converted to an embedding vector. + +* **Endpoint:** `POST /datasource/{dsSlug}` +* **Authentication:** Bearer token required +* **Request Format:** + ```json + { + "query": "your search query here", + "n": 5 // optional, number of results to return (default: 3) + } + ``` +* **Response Format:** + ```json + { + "documents": [ + { + "PageContent": "text content of the document chunk", + "Metadata": { + "source": "filename.pdf", + "page": 42 + }, + "Score": 0.92 + } + ] + } + ``` + +#### Vector Search + +Perform similarity search using a pre-computed embedding vector. Useful when you have already generated embeddings or want to use a custom embedding strategy. + +* **Endpoint:** `POST /datasource/{dsSlug}/vector` +* **Authentication:** Bearer token required +* **Request Format:** + ```json + { + "embedding": [0.1, 0.2, 0.3, ...], + "n": 10, // optional, max results (default: 10) + "similarity_threshold": 0.7 // optional, minimum score filter (default: 0.0) + } + ``` +* **Response Format:** + ```json + { + "documents": [ + { + "PageContent": "text content of the document chunk", + "Metadata": { + "source": "filename.pdf", + "page": 42 + }, + "Score": 0.92 + } + ] + } + ``` + +#### Metadata Query + +Query documents using metadata filters only (no vector similarity search). Supports pagination. + +* **Endpoint:** `POST /datasource/{dsSlug}/metadata` +* **Authentication:** Bearer token required +* **Request Format:** + ```json + { + "filter": { + "source": "filename.pdf", + "category": "technical" + }, + "filter_mode": "AND", // optional, "AND" or "OR" (default: "AND") + "limit": 10, // optional, max results (default: 10, max: 100) + "offset": 0 // optional, pagination offset (default: 0) + } + ``` +* **Response Format:** + ```json + { + "documents": [ + { + "PageContent": "text content of the document chunk", + "Metadata": { + "source": "filename.pdf", + "category": "technical" + }, + "Score": 0.0 + } + ], + "total_count": 42 + } + ``` + +#### Generate Embeddings + +Generate embedding vectors for text chunks without storing them. The datasource must have an embedder configured. + +* **Endpoint:** `POST /datasource/{dsSlug}/embeddings` +* **Authentication:** Bearer token required +* **Request Format:** + ```json + { + "texts": ["first text chunk", "second text chunk"] // max 100 items + } + ``` +* **Response Format:** + ```json + { + "vectors": [ + [0.1, 0.2, 0.3, ...], + [0.4, 0.5, 0.6, ...] + ] + } + ``` + +**Important Note:** Datasource endpoints do not accept a trailing slash. Use `/datasource/{dsSlug}` not `/datasource/{dsSlug}/`. + +This API-first approach ensures that all functionality available through the AI Portal can also be accessed programmatically for custom applications or integrations. + +The AI Portal serves as the primary touchpoint for end users interacting with AI capabilities managed by Tyk AI Studio, providing a secure, intuitive, and feature-rich experience. diff --git a/ai-management/ai-studio/ai-studio-env.mdx b/ai-management/ai-studio/ai-studio-env.mdx new file mode 100644 index 0000000000..f4fbf56044 --- /dev/null +++ b/ai-management/ai-studio/ai-studio-env.mdx @@ -0,0 +1,47 @@ +--- +title: "Tyk AI Studio Environment Variables" +description: "Environment variables and configuration options for Tyk AI Studio." +order: 1 +sidebarTitle: "AI Studio" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +import AIStudioConfig from '/snippets/ai-studio-config.mdx'; +import EnvTypeMapping from '/snippets/env-type-mapping.mdx'; + +This page details the environment variables that can be used to configure Tyk AI Studio. + +## Configuration + +Tyk AI Studio is configured primarily using environment variables. + +### Configuration Precedence + +The application loads configuration in the following order of precedence (highest to lowest): + +1. **Shell Environment Variables**: Variables set in the OS/Shell (e.g., `export SERVER_PORT=9090`) always override everything else. +2. **`.env` File**: Variables loaded from the file specified by the `-env` flag. + * *Note:* The application checks if a variable is already set in the environment before loading it from the file, ensuring shell variables are preserved. + +### Command Line Flags + +You can specify a `.env` file using the `-env` flag when starting the binary: + +```bash +./ai-studio -env /path/to/my.env +``` + +### Supported Formats + +Only the `.env` format (key=value pairs) is supported via the `-env` flag. + + + +## Variables + + diff --git a/ai-management/ai-studio/ai-studio-swagger.mdx b/ai-management/ai-studio/ai-studio-swagger.mdx new file mode 100644 index 0000000000..e045b269ca --- /dev/null +++ b/ai-management/ai-studio/ai-studio-swagger.mdx @@ -0,0 +1,18 @@ +--- +title: "Tyk AI Studio API" +description: "Tyk AI Studio API" +keywords: "OpenAPI Spec for AI Studio, Tyk AI Studio OAS, Tyk AI Portal REST" +sidebarTitle: "Overview" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +This is the API for the AI Studio user and group management system. + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + + diff --git a/ai-management/ai-studio/ai-studio-user.mdx b/ai-management/ai-studio/ai-studio-user.mdx new file mode 100644 index 0000000000..eb48206b42 --- /dev/null +++ b/ai-management/ai-studio/ai-studio-user.mdx @@ -0,0 +1,53 @@ +--- +title: "AI Studio User Personas" +description: "Overview of the user personas in Tyk AI Studio: Technical and Non-technical users." +keywords: "AI Studio, Developer, Consumer, Playground, API Keys, Business User, Chat" +sidebarTitle: "Overview" +--- + +Tyk AI Studio caters to two primary user personas, each with different needs and workflows: the **Technical User** (AI Developer/Consumer) and the **Non-technical User** (Business User). + +An administrator can configure what each user can access. During user onboarding, an admin can assign permissions to view the AI Portal, the Chat interface, or both, tailoring the experience to the user's role. + +## Technical User (AI Developer) + +The **Technical User**, also known as the AI Developer or AI Consumer, is the primary user of the capabilities exposed by Tyk AI Studio. This persona builds end-user applications, chatbots, and internal tools that leverage the LLMs and data sources configured by the Admin. The rest of this page is written for this technical user. + +### Lifecycle + +The workflow for a Technical User typically follows this path: + +1. **Discover**: Browsing the [AI Portal](/ai-management/ai-studio/ai-portal) to see available models, tools, and data sources they have access to. +2. **Experiment**: Using the **Model Playground** (Chat Interface) to test prompts, compare model responses, and validate RAG retrieval relevance. +3. **Build**: Creating **Apps**, generating [API Keys](/ai-management/ai-studio/user-management#core-concepts), and integrating the AI capabilities into their code via the Tyk AI Gateway. +4. **Optimize**: Analyzing app-specific metrics (latency, token usage) to refine prompts and improve performance. + +### Core Features + +#### Model Playground +An interactive sandbox environment for rapid experimentation. +* **Prompt Testing**: Iteratively refine system prompts and user messages. +* **Model Comparison**: Switch between different models (e.g., GPT-4 vs. Claude 3) to compare quality and latency. +* **Tool Testing**: Verify that [Tools](/ai-management/ai-studio/tools) (e.g., "Get Weather") are correctly invoked by the model. + +#### API Management +Self-service management of credentials for applications. +* **API Keys**: Generate and revoke long-lived API keys for application access. +* **SDK Integration**: Use standard OpenAI-compatible SDKs, pointing the base URL to Tyk AI Studio. + +#### Prompt Library +A repository for saving and sharing effective prompts. +* **Versioning**: Track changes to prompts over time. +* **Templates**: Use parameterized prompts to standardize interactions across applications. + +#### App Analytics +Granular visibility into application performance. +* **Usage Metrics**: View token consumption, request count, and error rates for specific apps. +* **Latency Tracking**: Monitor the end-to-end response time of LLM interactions. +* **Debug Traces**: Inspect individual request/response pairs to diagnose issues with model outputs or tool execution. + +## Non-technical User (Business User) + +The **Non-technical User**, such as product managers or sales staff, can leverage the power of LLMs without any coding. Administrators can configure a powerful, ready-to-use LLM chat interface complete with all the necessary tools and data sources. + +This allows business users to directly query the system and get answers to their questions, for example, "Summarise customer feedback for product X in the last quarter" or "What are the key features of our competitor Y?". They can do this without needing to create an application or manage API keys, making AI accessible to everyone in the organization. \ No newline at end of file diff --git a/ai-management/ai-studio/ai-studio.mdx b/ai-management/ai-studio/ai-studio.mdx new file mode 100644 index 0000000000..4539794b09 --- /dev/null +++ b/ai-management/ai-studio/ai-studio.mdx @@ -0,0 +1,479 @@ +--- +title: "AI Studio (Control Plane) Component" +description: "Overview of the AI Studio component in Tyk AI Studio's architecture, its features, and its role in the hub-and-spoke design" +keywords: "AI Studio, AI Management, Edge Gateway" +sidebarTitle: "AI Studio" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +AI Studio is the **central management hub** of the Tyk AI platform. It is the brain of the system — where administrators configure LLM providers, manage users, monitor usage, and extend the platform with plugins. When deployed in a hub-and-spoke topology, it also acts as the **control plane** that governs all connected Edge Gateways. + +## High-Level Architecture + +```mermaid +graph TB + subgraph AI_Studio["AI Studio (Control Plane)"] + direction TB + UI["🖥️ Admin Web UI"] + API["🔌 REST API"] + PROXY["⚡ Embedded Gateway"] + GRPC["📡 gRPC Control Server (Port 50051 — control mode only)"] + DB[("🗄️ Database SQLite / PostgreSQL")] + + UI --> API + API --> DB + PROXY --> DB + GRPC --> DB + end + + ADMIN["👤 Admin User"] --> UI + DEVUSER["👤 Developer / App"] --> PROXY + EDGE["🌐 Edge Gateways"] <-->|gRPC Streaming| GRPC + +``` + +AI Studio runs as a **single binary** that starts multiple servers: + +| Server | Port | Purpose | +|---|---|---| +| REST API + Admin UI | `8080` | Web interface and programmatic management | +| Embedded Gateway | `9090` | Proxies LLM requests directly (standalone mode) | +| gRPC Control Server | `50051` | Hub-and-spoke control plane (only in `control` mode) | + + +The gRPC Control Server only starts when `GATEWAY_MODE=control` is set. In `standalone` mode (the default), AI Studio handles everything locally without Edge Gateways. + + +## Core Features + +AI Studio provides the following capabilities out of the box: + +| Feature | Description | +|---|---| +| **LLM Management** | Configure and manage connections to LLM providers | +| **Application Management** | Create apps with credentials, budgets, and LLM access | +| **User Management & RBAC** | Users, groups, roles, and access control | +| **Analytics & Monitoring** | Token usage, cost tracking, and dashboards | +| **Plugin System** | Extend AI Studio with UI, Agent, and Gateway plugins | +| **Secrets Management** | Secure storage and reference for API keys | +| **Embedded Gateway** | Built-in LLM proxy (standalone mode) | +| **Edge Gateway Management** | Register, monitor, and reload Edge Gateways (control mode) | +| **Plugin Marketplace** | Discover and install community plugins | +| **Documentation Server** | Built-in docs site served at port 8989 | + +### Configuration Management + +Configuration Management is the heart of AI Studio. It is where administrators define **what LLMs are available**, **how they are accessed**, and **what rules govern their use**. + +#### LLM Provider Configuration + +AI Studio supports multiple LLM vendors through a unified configuration model: + +{/* ```mermaid +graph TD + ADMIN["Admin"] -->|Configure| LLM_CONFIG["LLM Configuration"] + LLM_CONFIG --> VENDOR["Vendor\n(openai / anthropic / vertex\ngoogle_ai / huggingface / ollama)"] + LLM_CONFIG --> APIKEY["API Key / Credentials\n(stored securely or via $SECRET/name)"] + LLM_CONFIG --> MODELS["Allowed Models\n(e.g. gpt-4, claude-3-opus)"] + LLM_CONFIG --> BUDGET["Monthly Budget\n(optional spending cap)"] + LLM_CONFIG --> PRIVACY["Privacy Score\n(0=Public → 100=Restricted/PII)"] + LLM_CONFIG --> FILTERS["Content Filters\n(associated filter plugins)"] + LLM_CONFIG --> NAMESPACE["Namespace\n(CE: default / ENT: custom)"] +``` + +Each LLM configuration gets a **slug** (auto-generated from its name) used in proxy endpoints: + +``` +/llm/call/{llmSlug}/... ← Unified (streaming + non-streaming) +/llm/rest/{llmSlug}/... ← REST only +/llm/stream/{llmSlug}/... ← Streaming only +``` */} + +##### Supported Vendors: + +| Vendor | Key | Notes | +|---|---|---| +| OpenAI | `openai` | GPT-4, GPT-3.5, etc. | +| Anthropic | `anthropic` | Claude models | +| Google Vertex AI | `vertex` | Gemini via gcloud | +| Google AI | `google_ai` | Gemini via API key | +| Hugging Face | `huggingface` | Open-source models | +| Ollama | `ollama` | Self-hosted models | + +#### Model Pricing + +To enable cost tracking, administrators define per-token prices for each model: + +{/* TODO: Add correct diagram and more text */} +{/* ```mermaid +graph LR + MODEL_PRICE["Model Price Record"] --> VENDOR_FIELD["Vendor"] + MODEL_PRICE --> MODEL_NAME["Model Name\n(e.g. gpt-4-turbo)"] + MODEL_PRICE --> INPUT_PRICE["Input Token Price\n(CPIT)"] + MODEL_PRICE --> OUTPUT_PRICE["Output Token Price\n(CPT)"] + MODEL_PRICE --> CACHE_WRITE["Cache Write Token Price"] + MODEL_PRICE --> CACHE_READ["Cache Read Token Price"] + MODEL_PRICE --> CURRENCY["Currency\n(e.g. USD)"] +``` */} + +The Analytics Engine uses these prices to calculate the cost of every LLM interaction automatically. + +#### Application (App) Management + +Applications are the **access credentials** that developers and systems use to interact with LLMs through the proxy: + +{/* TODO: Add correct diagram and more text */} + +{/* ```mermaid +graph TD + APP["Application"] --> CRED["API Credentials\n(token for proxy auth)"] + APP --> LLM_ACCESS["LLM Access\n(which LLMs this app can use)"] + APP --> APP_BUDGET["Monthly Budget\n(per-app spending cap)"] + APP --> TOOLS["Tool Access\n(which tools are available)"] + APP --> DATASOURCES["Data Source Access\n(RAG sources)"] + APP --> OWNER["Owner User"] + APP --> NAMESPACE["Namespace\n(CE: default / ENT: custom)"] +``` */} + +#### Secrets Management + +API keys and sensitive values can be stored securely and referenced by name: + +``` +$SECRET/MyOpenAIKey ← Reference in LLM config instead of raw key +``` + +This prevents sensitive credentials from being exposed in configuration exports or logs. + +#### Content Filters + +Filters are rules attached to LLMs that can block or modify requests/responses. They are implemented as plugins with the `pre_auth`, `auth`, or `post_auth` hook types and are associated with specific LLM configurations. + +### User Management & RBAC + +AI Studio uses a **group-based access control** model. Access to resources is granted through group membership, not individual user permissions. + +{/* #### Role Hierarchy + +```mermaid +graph TD + SA["👑 Super Admin\n(User ID=1)\nFull system access\nManages all admins"] --> A["🔧 Admin\n(IsAdmin=true)\nManages users & groups\nCannot modify other admins"] + A --> D["💻 Developer\n(ShowPortal=true)\nAccess to Portal UI\nCan create Apps"] + D --> CU["💬 Chat User\n(Default)\nAccess to Chat UI only"] +``` */} + +| Role | `IsAdmin` | `ShowPortal` | Capabilities | +|---|---|---|---| +| **Super Admin** | ✅ (ID=1) | ✅ | Full access, manages admins, SSO config, audit logs | +| **Admin** | ✅ | ✅ | Manages users, groups, LLMs, plugins | +| **Developer** | ❌ | ✅ | Portal access, creates apps, uses tools | +| **Chat User** | ❌ | ❌ | Chat interface access only | + +{/* #### Authentication Methods + +```mermaid +sequenceDiagram + participant User + participant AI_Studio + participant DB + + Note over User, DB: Session-based (UI Login) + User->>AI_Studio: POST /auth/login (email + password) + AI_Studio->>DB: Validate credentials + DB-->>AI_Studio: User record + AI_Studio-->>User: Session cookie + + Note over User, DB: API Key (Programmatic Access) + User->>AI_Studio: GET /llm/call/... (Authorization: Bearer ) + AI_Studio->>DB: Validate API key → find user + DB-->>AI_Studio: User + entitlements + AI_Studio-->>User: LLM response +``` + +#### Group-Based Access Control + +```mermaid +graph LR + USER["User"] -->|member of| GROUP["Group\n(e.g. 'Data Science Team')"] + GROUP -->|has access to| LLM_CAT["LLM Catalogue\n(set of LLM configs)"] + GROUP -->|has access to| DATA_CAT["Data Catalogue\n(set of data sources)"] + GROUP -->|has access to| TOOL_CAT["Tool Catalogue\n(set of tools)"] + GROUP -->|has access to| CHAT_EXP["Chat Experiences"] +``` + +**Access Control Flow (API Request):** + +```mermaid +graph LR + subgraph Edge["Edge Gateway"] + EP["Edge Plugin"] -->|"SendToControl()"| QUEUE["Local Queue\n(SQLite)"] + QUEUE -->|batch gRPC| CTRL_SERVER + end + + subgraph Control["AI Studio"] + CTRL_SERVER["gRPC Control Server\nSendPluginControlBatch()"] --> ROUTE["Route by Plugin ID"] + ROUTE --> CP["Control Plane Plugin\nAcceptEdgePayload()"] + end +``` + +> **CE vs Enterprise:** In Community Edition, all users are in a single "Default" group with access to all resources. In Enterprise Edition, you can create unlimited groups with fine-grained catalogue assignments. + +--- */} + +### Analytics & Monitoring + +AI Studio automatically collects and stores analytics data for every LLM interaction that flows through the system. + +#### Data Collection Flow + +```mermaid +sequenceDiagram + participant App + participant Proxy as Embedded Proxy / Edge Gateway + participant Analytics as Analytics Engine + participant DB + + App->>Proxy: LLM Request + Proxy->>LLM: Forward to provider + LLM-->>Proxy: Response (with token counts) + Proxy->>Analytics: Record async (non-blocking) + Analytics->>DB: Batch write LLMChatRecord + Proxy-->>App: Return response +``` + +#### What Gets Recorded + +Every LLM interaction records: + +| Field | Description | +|---|---| +| `timestamp` | When the request occurred | +| `user_id` | Which user made the request | +| `app_id` | Which application was used | +| `llm_id` | Which LLM configuration was targeted | +| `vendor` | LLM provider (openai, anthropic, etc.) | +| `model_name` | Specific model used (e.g. gpt-4-turbo) | +| `prompt_tokens` | Input token count | +| `response_tokens` | Output token count | +| `total_tokens` | Combined token count | +| `cost` | Calculated cost (using model pricing) | +| `latency_ms` | Request duration in milliseconds | +| `interaction_type` | `chat` or `proxy` | +| `cache_write_tokens` | Tokens written to cache (Anthropic) | +| `cache_read_tokens` | Tokens read from cache (Anthropic) | + +{/* #### Dashboard & Reporting + +```mermaid +graph TD + subgraph Dashboard["Admin Dashboard"] + CONV["📊 Conversations\n- Unique users/day\n- Chat interactions/day"] + COST["💰 Cost Analysis\n- Cost by currency over time\n- Cost per vendor & model"] + MODELS["🤖 Model Usage\n- Most used LLM models\n- Token usage trends"] + TOOLS["🔧 Tool Usage\n- Tool call statistics\n- Operations over time"] + BUDGET["📈 Budget Tracking\n- LLM budget usage\n- App budget usage\n- % of limit consumed"] + end +``` + +**Available Analytics API Endpoints:** + +| Endpoint | Description | +|---|---| +| `GET /analytics/chat-records-per-day` | Daily chat volume | +| `GET /analytics/cost-analysis` | Cost over time by currency | +| `GET /analytics/most-used-llm-models` | Top models by usage | +| `GET /analytics/token-usage-per-user` | Token breakdown per user | +| `GET /analytics/token-usage-per-app` | Token breakdown per app | +| `GET /analytics/vendor-usage` | Usage by LLM vendor | +| `GET /analytics/model-usage` | Usage by specific model | +| `GET /analytics/total-cost-per-vendor-and-model` | Cost breakdown | +| `GET /analytics/budget-usage` | Budget consumption status | +| `GET /analytics/app-interactions-over-time` | App activity trends | + +#### Budget Enforcement + +```mermaid +flowchart LR + REQ["Incoming Request"] --> CHECK_BUDGET{"Budget\nConfigured?"} + CHECK_BUDGET -->|No| ALLOW["Allow Request"] + CHECK_BUDGET -->|Yes| CALC["Calculate Current\nMonthly Spend"] + CALC --> COMPARE{"Spend ≥ Budget?"} + COMPARE -->|No| ALLOW + COMPARE -->|Yes| BLOCK["429 Too Many Requests\n(Budget Exceeded)"] +``` + +> **CE vs Enterprise:** Community Edition tracks costs but does **not** enforce budget limits. Budget enforcement (blocking requests when over budget + email alerts at 80%/100%) is an **Enterprise Edition** feature. + +--- */} + +### Plugin System + +The Plugin System is AI Studio's extensibility layer. Plugins run as **isolated processes** communicating over gRPC, providing security and fault tolerance. All plugins use a **Unified Plugin SDK** that works in both AI Studio and Edge Gateway contexts. + +{/* #### Plugin Types + +```mermaid +graph TD + PLUGINS["Plugin System"] --> GATEWAY["🔌 Gateway Plugins\n(run on Edge Gateway / Embedded Proxy)"] + PLUGINS --> STUDIO["🖥️ AI Studio Plugins\n(run on AI Studio control plane)"] + + GATEWAY --> PRE_AUTH["pre_auth\nRun before authentication"] + GATEWAY --> AUTH["auth\nCustom authentication logic"] + GATEWAY --> POST_AUTH["post_auth\nRun after authentication"] + GATEWAY --> ON_RESP["on_response\nTransform LLM responses"] + GATEWAY --> DATA_COLL["data_collection\nCollect analytics/metrics"] + GATEWAY --> CUSTOM_EP["custom_endpoint\nCustom HTTP endpoints"] + + STUDIO --> STUDIO_UI["studio_ui\nExtend Admin UI with custom pages"] + STUDIO --> PORTAL_UI["portal_ui\nExtend Portal/Chat UI"] + STUDIO --> AGENT["agent\nConversational AI agents"] + STUDIO --> OBJ_HOOKS["object_hooks\nCRUD lifecycle hooks\n(before/after create/update/delete)"] + STUDIO --> RES_PROV["resource_provider\nCustom resource types for Apps"] +``` + +#### Plugin Lifecycle + +```mermaid +sequenceDiagram + participant Admin + participant AI_Studio + participant PluginManager + participant PluginProcess + + Admin->>AI_Studio: Register plugin\n(command path or OCI reference) + AI_Studio->>PluginManager: LoadPlugin(id) + PluginManager->>PluginProcess: Start subprocess (go-plugin / gRPC) + PluginProcess-->>PluginManager: GetManifest() → capabilities + PluginManager->>AI_Studio: Update hook_types from manifest + Note over AI_Studio: Plugin is now active + + Note over PluginProcess: On LLM request... + AI_Studio->>PluginProcess: Execute hook (pre_auth / post_auth / etc.) + PluginProcess-->>AI_Studio: PluginResponse (allow/block/modify) +``` */} + +#### Plugin Distribution + +Plugins can be distributed in three ways: + +| Method | Description | Example | +|---|---|---| +| **Local Binary** | Path to executable on disk | `/usr/local/bin/my-plugin` | +| **Remote Binary** | URL to download | `https://example.com/plugin` | +| **OCI Artifact** | Container registry reference | `oci://ghcr.io/org/plugin:v1.0.0` | + +#### Plugin Marketplace + +AI Studio includes a built-in marketplace for discovering and installing community plugins: + +{/* ```mermaid +graph LR + MARKET_INDEX["Marketplace Index\n(GitHub / custom URL)"] -->|hourly sync| LOCAL_CACHE["Local Plugin Cache\n(marketplace_plugins table)"] + LOCAL_CACHE --> BROWSE["Browse & Search\n(by category, publisher, maturity)"] + BROWSE --> INSTALL["One-click Install\n(creates Plugin record + downloads OCI)"] + INSTALL --> LOAD["Auto-load & fetch manifest"] +``` */} + +> **CE vs Enterprise:** Community Edition supports one official Tyk marketplace. Enterprise Edition supports multiple custom marketplace sources with full management UI. + +{/* #### Edge-to-Control Plugin Communication + +In hub-and-spoke deployments, plugins running on Edge Gateways can send data back to plugins running on AI Studio: + +```mermaid +graph LR + subgraph Edge["Edge Gateway"] + EP["Edge Plugin"] -->|"SendToControl()"| QUEUE["Local Queue\n(SQLite)"] + QUEUE -->|batch gRPC| CTRL_SERVER + end + + subgraph Control["AI Studio"] + CTRL_SERVER["gRPC Control Server\nSendPluginControlBatch()"] --> ROUTE["Route by Plugin ID"] + ROUTE --> CP["Control Plane Plugin\nAcceptEdgePayload()"] + end +``` + +This enables use cases like: aggregating analytics from multiple edges, centralising audit data, or streaming events from distributed deployments back to a single dashboard. */} + +## How Configuration Synchronization Works? + +Tyk AI Studio uses a checksum-based system to track configuration synchronization between the control plane and edge gateways. + +### How It Works + +1. **Checksum Generation:** When configuration changes occur on the control plane, a SHA-256 checksum is computed from the serialized configuration snapshot +2. **Heartbeat Reporting:** Edge gateways report their loaded configuration checksum in each heartbeat +3. **Status Comparison:** The control plane compares reported checksums to determine sync status +4. **UI Notifications:** The admin UI displays sync status and notifies administrators when edges are out of sync +5. **On configuration change**, an admin pushes a reload signal. This can target all gateways or a specific namespace. Each gateway then pulls the latest snapshot. +6. **Namespaces** control what gets loaded onto each gateway. LLMs, Apps, Filters, and Plugins can all be namespaced. +7. **If the hub is unreachable**, gateways continue operating from their last-known snapshot stored in a local database (SQLite or PostgreSQL). + +### What Gets Synced to Gateways + +| Synced (part of config snapshot) | NOT synced (Studio-only) | +|---|---| +| LLM Configurations | Tools | +| Apps | Data Sources | +| Filters | Chat configurations | +| Plugins | User management | +| Model Prices | | +| Model Routers (Enterprise) | | + +> **Note:** Apps are included in the sync but are **not** part of the checksum calculation because they change frequently. Credentials are **not** pulled until a gateway actually needs them — this is a pull-on-miss caching strategy that ensures the admin retains ongoing control over access tokens. + +### Sync Status Values + +| Status | Description | UI Indicator | +|--------|-------------|--------------| +| **In Sync** | Edge has the current configuration | Green chip | +| **Pending** | Edge needs a configuration update | Yellow chip | +| **Stale** | Edge has been out of sync for >15 minutes | Orange chip | +| **Unknown** | Edge hasn't reported a checksum yet | Gray chip | + +### Pushing Configuration + +Configuration changes are pushed to edge gateways on-demand (not automatically) to ensure administrators maintain control over when changes are deployed. + +#### Push Configuration Modal + +Click the **Push Configuration** button to open the push modal. You can choose to: + +1. **Push to All Namespaces:** Sends configuration to all connected edge gateways +2. **Push to Specific Namespace:** Sends configuration only to edges in a selected namespace (Enterprise) + +#### Push Process + +When you push configuration: + +1. The control plane generates a new configuration snapshot for the target namespace(s) +2. Edge gateways receive a reload signal via gRPC +3. Each edge fetches the new configuration and applies it +4. Edges report the new checksum in their next heartbeat +5. The sync status updates to reflect the new state + +{/* +### Analytics Flow + +Both the Edge Gateway and AI Studio record analytics for all client interactions. In the Edge Gateway, analytics are batched and sent back to AI Studio every few seconds (configurable). + +> **Important:** Analytics must be **explicitly enabled** in the Edge Gateway configuration for data to appear in Studio dashboards. This is a common stumbling block — see the [Analytics](/ai-management/ai-studio/analytics) docs for configuration details. + +### Distributed Budget Control + +Since Edge Gateways can be horizontally scaled, budget tracking faces a split-brain problem. The solution: + +1. All gateways send analytics batches back to AI Studio, giving Studio a complete view of token spend across the estate. +2. AI Studio sends a periodic **budget pulse** containing the total spend for each access token. +3. Gateways update their local spend counter if Studio's number is higher than what they have locally. + +This provides **eventually-accurate** budget control across a multi-gateway environment. See [Budget Control](/ai-management/ai-studio/budgeting) for details. + */} +## Configuration Reference + +To know more about configuring AI Studio, see the [Configuration Reference](/ai-management/ai-studio/ai-studio-env) for detailed documentation on all environment variables. \ No newline at end of file diff --git a/ai-management/ai-studio/analytics.mdx b/ai-management/ai-studio/analytics.mdx new file mode 100644 index 0000000000..d1c237b5da --- /dev/null +++ b/ai-management/ai-studio/analytics.mdx @@ -0,0 +1,135 @@ +--- +title: "Analytics & Monitoring in Tyk AI Studio" +description: "How to configure analytics in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Analytics, Monitoring" +sidebarTitle: "Analytics & Monitoring" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio incorporates an Analytics System designed to collect, aggregate, and provide insights into the usage, cost, and performance of the platform's core components, particularly LLMs, Tools, and Chat interactions. + +## Purpose + +The Analytics System serves several key purposes: + +* **Cost Tracking:** Monitor spending associated with different LLM providers and models. +* **Usage Monitoring:** Understand how users and applications are interacting with LLMs and Tools. +* **Performance Analysis:** Track metrics like latency and token counts for LLM requests. +* **Auditing & Debugging:** Provide detailed logs of interactions for troubleshooting and security analysis. +* **Reporting:** Offer data points for dashboards and reports for administrators and potentially end-users. + +## Data Collection + +The primary point of data collection is the **[Proxy & API Gateway](/ai-management/ai-studio/proxy)**. As requests flow through the proxy: + +1. **Request Details:** Information about the incoming request is captured (e.g., user ID, application ID, requested LLM/route, timestamp). +2. **LLM Interaction:** Details about the interaction with the backend LLM are recorded (e.g., model used, prompt tokens, completion tokens, latency). +3. **Cost Calculation:** Using data from the [Model Pricing System](/ai-management/ai-studio/ai-studio#model-pricing), the cost of the interaction is calculated based on token counts. +4. **Tool Usage:** If the interaction involved [Tools](/ai-management/ai-studio/tools), relevant details might be logged (e.g., which tool was called, success/failure). +5. **Chat Context:** For interactions originating from the [Chat Interface](/ai-management/ai-studio/chat-interface), metadata about the chat session might be included. + +## Edge Gateway Analytics Configuration + +> **Important:** Analytics must be **explicitly enabled** in the Edge Gateway configuration for data to appear in AI Studio dashboards. This is a common stumbling block when first deploying edge gateways. + +When enabled, the Edge Gateway batches analytics records and sends them back to AI Studio every few seconds (this interval is configurable). Without this configuration, AI Studio will have no visibility into gateway traffic. + +Ensure your Edge Gateway environment includes the appropriate analytics configuration. The default quickstart and packaged deployments enable this, but custom deployments may not. + +The analytics subsystem in the Edge Gateway also supports plugins, offering multiple ways to handle data: +- Each data class (Analytics, Logs, Budgets) can have its own processor +- Processors can either override or work in parallel with the default batch system + +## Architecture + +* **Asynchronous Ingestion:** To minimize impact on request latency, analytics data is typically collected by the Proxy and sent asynchronously to a dedicated analytics database or processing pipeline. +* **Data Storage:** Analytics records are stored in the application's relational database (PostgreSQL or SQLite, configured via environment variables). The same database used for other application data stores analytics records. +* **API Endpoints:** Tyk AI Studio exposes internal API endpoints that allow the Admin UI (and potentially other authorized services) to query the aggregated analytics data. + +## Key Metrics Tracked (Examples) + +* **Per LLM Request:** + * Timestamp + * User ID / API Key ID + * LLM Configuration ID / Route ID + * Model Name + * Prompt Tokens + * Completion Tokens + * Total Tokens + * Calculated Cost + * Latency (ms) + * Success/Error Status +* **Per Tool Call (if applicable):** + * Timestamp + * Tool ID + * Success/Error Status + * Latency (ms) +* **Aggregated Metrics:** + * Total cost per user/application/LLM over time. + * Total requests per user/application/LLM over time. + * Average latency per LLM. + * Most frequently used models/tools. + +## Monitoring & Dashboards (Admin) + +Administrators typically access analytics data via dashboards within the Tyk AI Studio UI. + +* **Overview:** High-level summaries of cost, usage, and requests. +* **Filtering & Grouping:** Ability to filter data by time range, user, application, LLM configuration, etc. +* **Visualizations:** Charts and graphs showing trends in cost, token usage, request volume, and latency. +* **Detailed Logs:** Access to raw or near-raw event logs for specific interactions (useful for debugging). + + Analytics Dashboard + +### Per-LLM Attribution + +From v2.1.0, proxy logs are attributed to the specific LLM configuration (`llm_id`) rather than the vendor string. Two LLM entries that share a vendor, for example a personal Anthropic key and a work Anthropic key, no longer cross-pollinate in the LLM detail page. Logs recorded before the upgrade have `llm_id = 0` and do not appear in per-LLM detail views. + +### Compliance Dashboard + +From v2.1.0, the Compliance dashboard (`/admin/compliance`) surfaces [Compliance Events](/ai-management/ai-studio/compliance-events) emitted by filter scripts alongside the existing blocked-request data: + +* **Summary Cards:** Critical Events and Warning Events counts over the selected window, with trend arrows and configurable escalation thresholds. +* **Policy Violations:** the summary splits **Blocked** (4xx proxy logs) from **Flagged** (warning and critical events that passed through), and the timeline chart merges both streams. +* **Filter Events Tab:** severity totals with a stacked timeline chart, severity and event-type filters, expandable rows showing raw event metadata, and CSV export. +* **App Risk:** per-app risk scores are weighted by event severity, and the drill-down interleaves blocked requests with compliance events. + +## Prometheus and OpenTelemetry Metrics + +From v2.1.0, both Tyk AI Studio and the Edge Gateway expose operational metrics on a Prometheus-compatible `/metrics` endpoint, with OpenTelemetry trace correlation through the analytics pipeline. + +Metrics are enabled by default. To configure them: + +| Component | Environment variable | Default | +|---|---|---| +| AI Studio (control plane) | `METRICS_ENABLED` (set to `false` or `0` to disable) | enabled | +| AI Studio (control plane) | `METRICS_PATH` | `/metrics` | +| Edge Gateway | `ENABLE_METRICS` | `true` | + +The endpoint exposes the following metrics: + +| Metric | Type | Description | +|---|---|---| +| `aistudio_llm_requests_total` | Counter | LLM requests processed | +| `aistudio_llm_tokens_total` | Counter | Tokens consumed | +| `aistudio_llm_cost_total` | Counter | Calculated cost | +| `aistudio_tool_calls_total` | Counter | Tool invocations | +| `aistudio_policy_blocks_total` | Counter | Requests blocked by policy | +| `aistudio_compliance_events_total` | Counter | [Compliance events](/ai-management/ai-studio/compliance-events) recorded, labeled by `filter_scope`, `severity`, and `event_type` | +| `aistudio_llm_request_duration_seconds` | Histogram | LLM request latency | +| `aistudio_tool_execution_duration_seconds` | Histogram | Tool execution latency | +| `aistudio_llm_inflight_requests` | Gauge | Requests currently in flight | + +Point your Prometheus scrape configuration (or an OpenTelemetry Collector with a Prometheus receiver) at the `/metrics` path of each component you want to monitor. + +## Integration with Other Systems + +* **[Budget Control](/ai-management/ai-studio/budgeting):** Analytics data (specifically cost) is likely used by the Budget Control system to track spending against defined limits. +* **[Model Pricing](/ai-management/ai-studio/ai-studio#model-pricing):** The pricing definitions are crucial for calculating the cost metric within the analytics system. + +By providing detailed analytics, Tyk AI Studio enables organizations to effectively manage costs, understand usage patterns, and ensure the optimal performance of their AI interactions. \ No newline at end of file diff --git a/ai-management/ai-studio/architecture.mdx b/ai-management/ai-studio/architecture.mdx new file mode 100644 index 0000000000..08910180d5 --- /dev/null +++ b/ai-management/ai-studio/architecture.mdx @@ -0,0 +1,98 @@ +--- +title: "Tyk AI Studio Architecture" +description: "Overview of Tyk AI Studio's architecture, components, and design principles" +keywords: "AI Studio, AI Management, Configuration, LLMs, Edge Gateway" +sidebarTitle: "Overview" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio uses a **hub-and-spoke** architecture that supports both standalone and distributed enterprise deployments. This design separates the control plane (configuration management) from the data plane (request processing), ensuring scalable, resilient AI gateway deployment. + +## Architecture Diagram + +```mermaid +graph TB + subgraph "Control Plane" + Control[AI Studio
Control Mode] + ControlDB[(PostgreSQL)] + Control --> ControlDB + end + + subgraph "Edge Gateways" + Edge1[Edge Gateway 1] + Edge2[Edge Gateway 2] + EdgeN[Edge Gateway N] + end + + Control -.->|Config Sync| Edge1 + Control -.->|Config Sync| Edge2 + Control -.->|Config Sync| EdgeN + + Users1[Users Region 1] --> Edge1 + Users2[Users Region 2] --> Edge2 + UsersN[Users Region N] --> EdgeN + + Edge1 --> Providers[AI Providers] + Edge2 --> Providers + EdgeN --> Providers + + style Control fill:#4A90E2 + style Edge1 fill:#7ED321 + style Edge2 fill:#7ED321 + style EdgeN fill:#7ED321 +``` + +### Community vs Enterprise Edition + +The architecture remains identical in both Community and Enterprise Editions. The only difference is the feature set enabled at build time. + +## Core Components + +Tyk AI Studio consists of two main components: + +### AI Studio (Control Plane) + +AI Studio is the central management hub that provides: + +- **Configuration Management**: LLMs, applications, policies, users +- **Policy Enforcement**: Rate limits, budgets, access controls +- **Analytics & Monitoring**: Usage tracking, cost analysis, performance metrics +- **User Management**: Authentication, authorization, RBAC +- **gRPC API**: Configuration distribution to edge gateways + +It serves **three audiences** through three distinct sections: +1. **Administration**: Admins configure LLMs, tools, data sources, filters, plugins, users, groups, and budgets. +2. **AI Portal**: A self-service developer portal. LLM users browse available LLMs, MCP Servers, and Data Sources, then request access by creating an App. +3. **Chat**: A managed chat interface for LLM users. + +**AI Studio also runs these services:** + +| Service | Description | +|---------|-------------| +| **Embedded Gateway** | A lightweight AI Gateway for testing LLM proxying. No filters, no middleware, no plugins — just basic proxying to verify an LLM works as expected. Also used by the Chat interface. | +| **API-based Tool Access** | Each Tool defined via OpenAPI spec is also available as a REST API endpoint for developers to call directly. | +| **MCP Tool Access** | An MCP-compliant interface (shim) for tools generated from OpenAPI specs. Provides MCP-API compatibility without a separate MCP proxy. | +| **Datasource API** | A unified REST endpoint for performing vector searches against registered data sources. | + +To know more about the AI Studio component, see the [AI Studio Documentation](/ai-management/ai-studio/ai-studio). + +### Edge Gateway (Data Plane) + +The Edge Gateway operates as an independent, dedicated AI proxy. It processes AI requests, enforces policies, and reports analytics to the control plane. It is optimized for high performance and resilience in production. + +- **Process Requests**: Handle AI API calls locally +- **Cache Configuration**: Store synced config in local SQLite +- **Enforce Policies**: Apply rate limits, budgets, filters +- **Report Analytics**: Send usage data back to control +- **Operate Independently**: Continue working if control plane is unreachable + +The Edge Gateway provides the full middleware pipeline: authentication, filters, plugins, analytics, and budget enforcement. + +**Key difference from the embedded gateway:** The embedded gateway in AI Studio is "gateway-lite" for testing and chat. The Edge Gateway is the production data plane with the full feature set. + +To know more about the Edge Gateway, see the [Edge Gateway Architecture](/ai-management/ai-studio/proxy) documentation. \ No newline at end of file diff --git a/ai-management/ai-studio/budgeting.mdx b/ai-management/ai-studio/budgeting.mdx new file mode 100644 index 0000000000..e2ca87121b --- /dev/null +++ b/ai-management/ai-studio/budgeting.mdx @@ -0,0 +1,82 @@ +--- +title: "Budget Control in Tyk AI Studio" +description: "How to configure budgets in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Budget Control" +sidebarTitle: "Budget Control" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio provides a Budget Control system to help organizations manage and limit spending on Large Language Model (LLM) usage. + +## Purpose + +The primary goals of the Budget Control system are: + +* **Prevent Overspending:** Set hard limits on costs associated with LLM API calls. +* **Cost Allocation:** Track and enforce spending limits at different granularities (e.g., per organization, per specific LLM configuration). +* **Predictability:** Provide better predictability for monthly AI operational costs. + +## Scope & Configuration + +Budgets are typically configured by administrators and applied at specific levels: + +* **Organization Level:** A global budget limit for all LLM usage within the organization. +* **LLM Configuration Level:** A specific budget limit tied to a particular LLM setup (e.g., a dedicated budget for a high-cost `gpt-4` configuration). +* **(Potentially) Application/User Level:** Granular budgets might be assignable to specific applications or teams (depending on implementation specifics). + +**Configuration Parameters:** + +* **Limit Amount:** The maximum monetary value allowed (e.g., $500). +* **Currency:** The currency the budget is defined in (e.g., USD). +* **Time Period:** The reset interval for the budget, typically monthly (e.g., resets on the 1st of each month). +* **Scope:** Which entity the budget applies to (Organization, specific LLM Configuration ID, etc.). + +Administrators configure these budgets via the Tyk AI Studio UI or API. + + Budget Config UI + +## Enforcement + +> **Note:** Budget *enforcement* (blocking requests when limits are exceeded) is an **Enterprise Edition** feature. In Community Edition, budgets are tracked and recorded for reporting purposes, but requests are not blocked when limits are exceeded. + +Budget enforcement primarily occurs at the **[Proxy & API Gateway](/ai-management/ai-studio/proxy)**: + +1. **Request Received:** The Proxy receives a request destined for an LLM. +2. **Cost Estimation:** Before forwarding the request, the Proxy might estimate the potential maximum cost (or rely on post-request cost calculation). +3. **Budget Check:** The Proxy checks the current spending against all applicable budgets (e.g., the specific LLM config budget AND the overall organization budget) for the current time period. +4. **Allow or Deny (Enterprise Edition):** + * If the current spending plus the estimated/actual cost of the request does *not* exceed the limit(s), the request is allowed to proceed. + * If the request *would* cause a budget limit to be exceeded, the request is blocked with HTTP 403, and an error is returned to the caller. + +## Distributed Budget Control (Multi-Gateway) + +When running multiple Edge Gateways in a hub-and-spoke architecture, budget tracking faces a split-brain challenge — each gateway only has local visibility into its own spend. Tyk AI Studio solves this with a **budget pulse** mechanism: + +1. **Analytics batching:** All Edge Gateways send analytics records (including cost data) back to AI Studio in regular batches. This gives AI Studio a **complete view** of token spend across the entire estate. + +2. **Budget pulse:** AI Studio periodically sends a budget pulse to each gateway containing the **total spend** for each access token across all gateways. + +3. **Local update:** Each gateway updates its local spend counter if Studio's reported number is higher than what it has locally. + +This provides **eventually-accurate** budget control. There may be a slight overrun window under very high concurrent load across multiple gateways, but the system converges quickly and prevents sustained overspending. + +> **Note:** Budget *enforcement* (blocking requests at the limit) is an Enterprise Edition feature. In Community Edition, budgets are tracked and visible in dashboards but requests are not blocked. + +## Integration with Other Systems + +* **[Analytics & Monitoring](/ai-management/ai-studio/analytics):** The Analytics system provides the cost data used to track spending against budgets. The current spent amount for a budget period is derived from aggregated analytics data. +* **[Model Pricing](/ai-management/ai-studio/ai-studio#model-pricing):** The pricing definitions are essential for the Analytics system to calculate costs accurately, which in turn feeds the Budget Control system. +* **[Notification System](/ai-management/ai-studio/notifications):** Budgets trigger notifications when spending reaches defined thresholds. The system supports alerts at **50%**, **80%**, **90%**, and **100%** of the budget limit. Administrators receive notifications when these thresholds are crossed. + +## Benefits + +* **Financial Control:** Prevents unexpected high bills from LLM usage. +* **Resource Management:** Ensures fair distribution of AI resources according to allocated budgets. +* **Accountability:** Tracks spending against specific configurations or organizational units. + +Budget Control is a critical feature for organizations looking to adopt AI technologies responsibly and manage their operational costs effectively. diff --git a/ai-management/ai-studio/call-settings.mdx b/ai-management/ai-studio/call-settings.mdx new file mode 100644 index 0000000000..ffb803e35d --- /dev/null +++ b/ai-management/ai-studio/call-settings.mdx @@ -0,0 +1,58 @@ +--- +title: "Manage LLM Call Settings in Tyk AI Studio" +description: "How to configure default call settings for Large Language Models (LLMs) in Tyk AI Studio, including parameters like temperature, max tokens, and more." +keywords: "AI Studio, AI Management, LLMs, Large Language Models, LLM Call Settings" +sidebarTitle: "LLM Call Settings" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Model call settings let you configure how Large Language Models handle prompts. These settings control parameters like response length, temperature (creativity level), and other options that shape the output when a prompt is sent to the LLM. + +```mermaid +graph LR + A[User Prompt] --> B[AI Studio] + B --> C{LLM Call Settings} + C -->|Temperature, Max Tokens, etc.| D[LLM Provider] + D --> E[Generated Response] +``` + +### Use cases + +- **Chats**: These settings control how the LLM responds in conversational interfaces within the Chat Room feature, allowing administrators to fine-tune the user experience. +- **Middleware Function Calls**: The settings guide LLM behavior in automated backend processes where the LLM is used for tasks such as data generation or content analysis. + +## LLM Call Settings Details + +The **LLM Call Settings** section allows administrators to configure default runtime parameters for Large Language Models (LLMs) used in chat interactions and middleware system function calls. These settings provide control over how the LLM processes inputs and generates outputs. + +It is important to note that these settings are not utilized in the AI Gateway proxy (Tyk Edge Gateway). Applications created in the AI portal by end users for accessing LLMs provide their own model configurations (like temperature and max tokens) in the API payload when making requests to the AI Gateway. + +The Call Settings configured by the admin are specifically used for the built-in Chat Interface (accessed via the AI Portal) and for internal middleware system function calls (such as tool calling and RAG). + +## Configuration + +The **Edit/Create LLM Call Settings View** enables administrators to configure or update call-time options for a specific Large Language Model (LLM). Below is an explanation of each field and its purpose: + +- **Model Name**: The name of the language model (e.g., `gpt-5.2`, `claude-opus-4-5-20251101`). +- **Temperature**: Controls randomness: `0` is deterministic, `1` is very random. Range: `0` to `1`. +- **Max Tokens**: The maximum number of tokens to generate in the response. Must be at least `1`. +- **Top P**: Controls diversity via nucleus sampling: `0.5` means half of all likelihood-weighted options are considered. Range: `0` to `1`. +- **Top K**: Controls diversity by limiting to `k` most likely tokens. `0` means no limit. +- **Min Length**: The minimum number of tokens to generate in the response. +- **Max Length**: The maximum number of overall tokens. +- **Repetition Penalty**: Penalizes repetition: `1.0` means no penalty, `>1.0` discourages repetition. Typically between `1.0` and `1.5`. +- **System Prompt**: A long-form text prompt that sets the context or behavior for the language model. + +## How to Create LLM Call Settings + +1. Navigate to the **LLM Call Settings** section in the AI Studio dashboard. +2. Click the green **+ ADD LLM CALL SETTING** button located at the top-right of the view. +3. Fill in the required fields such as **Model Name** and configure the desired parameters like **Temperature** and **Max Tokens**. +4. Click **Create LLM Call Settings** to save the configuration. + + Create LLM Call Settings UI diff --git a/ai-management/ai-studio/catalogs.mdx b/ai-management/ai-studio/catalogs.mdx new file mode 100644 index 0000000000..3e0e03c704 --- /dev/null +++ b/ai-management/ai-studio/catalogs.mdx @@ -0,0 +1,115 @@ +--- +title: "Catalogs in Tyk AI Studio" +description: "How to manage catalogs in AI Studio?" +keywords: "AI Studio, AI Management, Catalogs" +sidebarTitle: "Catalogs" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Catalogs in Tyk AI Studio are collections of AI resources ([LLM providers](/ai-management/ai-studio/llm-management), [Data sources](/ai-management/ai-studio/datasources-rag), and [Tools](/ai-management/ai-studio/tools)) that you can assign to specific [Teams](/ai-management/ai-studio/teams) to easily manage access. They act as the bridge between the raw AI capabilities and the users who need to consume them. + +```mermaid +graph TD + LLM[LLM Provider] -->|Added to| LLMCat[LLM Catalog] + Data[Data Source] -->|Added to| DataCat[Data Catalog] + Tool[Tool] -->|Added to| ToolCat[Tool Catalog] + + LLMCat -->|Assigned to| Team[Team] + DataCat -->|Assigned to| Team + ToolCat -->|Assigned to| Team + + Team -->|Grants access to| User[User] +``` + +### Use cases +- **Resource Grouping**: Group all OpenAI models into an "OpenAI Models" catalog and all Anthropic models into an "Anthropic Models" catalog to manage vendor access easily. +- **Environment Separation**: Create a "Production Data" catalog and a "Staging Data" catalog, ensuring that only the production Team has access to the production data sources. +- **Tool Bundling**: Bundle specific tools (like web search and calculators) into a "Research Tools" catalog for your data science Team. + +### Community vs Enterprise Edition +In the **Community Edition**, catalog management is automated. There are three built-in **"Default" Catalogs** (one for LLMs, one for Data Sources, and one for Tools). Any new resource you create is automatically added to its respective default catalog, and these catalogs are permanently linked to the "Default" Team. + +In the **Enterprise Edition**, you can create custom Catalogs, group specific resources together, and assign them to different Teams to enforce strict resource isolation and Role-Based Access Control (RBAC). The "Default" catalogs still exist and cannot be deleted. + +## What is a Catalog? + +A Catalog is a logical grouping of a specific type of AI resource. There are three types of catalogs in Tyk AI Studio: +1. **LLM Catalogs**: Collections of LLM providers (e.g., OpenAI, Anthropic, local models). +2. **Data Catalogs**: Collections of data sources (e.g., vector databases, knowledge bases). +3. **Tool Catalogs**: Collections of tools (e.g., web scrapers, calculators, custom APIs). + +Catalogs do not grant access on their own. To make the resources within a catalog available to [Users](/ai-management/ai-studio/users), the catalog must be assigned to a [Team](/ai-management/ai-studio/teams). + +## Configuration +When configuring a Catalog, the options vary slightly depending on the type: + +### LLM Catalog +- **Catalog Name**: A descriptive name for the collection. +- **LLMs in this Catalog**: A list where you can add or remove specific LLM providers. + +### Data Catalog +- **Catalog Name**: A descriptive name for the collection. +- **Short Description**: A brief summary of the data sources included. +- **Long Description**: Detailed information about the catalog's contents. +- **Icon**: A visual identifier for the catalog. +- **Data Sources**: A list where you can add or remove specific data sources. +- **Tags**: Labels to help categorize and filter the catalog. + +### Tool Catalog +- **Name**: A descriptive name for the collection. +- **Short Description**: A brief summary of the tools included. +- **Long Description**: Detailed information about the catalog's capabilities. +- **Icon**: A visual identifier for the catalog. +- **Tools**: A list where you can add or remove specific tools. +- **Tags**: Labels to help categorize and filter the catalog. + +## How to Create an LLM Catalog +Catalogs are collections of LLM providers that you can assign to specific user groups to manage access easily. + +To create a new LLM Catalog in Tyk AI Studio: +1. Navigate to the **Catalogs** section in the AI Studio dashboard. +2. Select the **LLM** tab. +3. Click on the **Add Catalog** button. +4. Fill in the **Catalog Name** (Required). +5. Under **LLMs in this Catalog**, click **Add LLM** to select the specific LLM providers you want to include. +6. Click **Save** to create the catalog. +7. Once created, navigate to the [Teams](/ai-management/ai-studio/teams) section to assign this catalog to a Team. + + Add LLM Catalog Form + +## How to Create a Data Source Catalog +Catalogs are collections of data sources that you can assign to specific user groups to manage access easily. + +To create a new Data Catalog in Tyk AI Studio: +1. Navigate to the **Catalogs** section in the AI Studio dashboard. +2. Select the **Data** tab. +3. Click on the **Add Catalog** button. +4. Fill in the **Catalog Name** (Required) and **Short Description** (Required). +5. (Optional) Provide a **Long Description** and an **Icon**. +6. Under **Data Sources**, click **Add Data Source** to select the specific data sources you want to include. +7. Under **Tags**, click **Add Tag** to label and categorize the catalog. +8. Click **Save** to create the catalog. +9. Once created, navigate to the [Teams](/ai-management/ai-studio/teams) section to assign this catalog to a Team. + + Add Data Catalog Form + +## How to Create a Tool Catalog +Catalogs are collections of tools that you can assign to specific user groups to manage access easily. + +To create a new Tool Catalog in Tyk AI Studio: +1. Navigate to the **Catalogs** section in the AI Studio dashboard. +2. Select the **Tools** tab. +3. Click on the **Add Catalog** button. +4. Fill in the **Name** (Required). +5. (Optional) Provide a **Short Description**, **Long Description**, and an **Icon**. +6. Under **Tools**, click **Add Tool** to select the specific tools you want to include. +7. Under **Tags**, click **Add Tag** to label and categorize the catalog. +8. Click **Save** to create the catalog. +9. Once created, navigate to the [Teams](/ai-management/ai-studio/teams) section to assign this catalog to a Team. + + Add Tool Catalog Form \ No newline at end of file diff --git a/ai-management/ai-studio/chat-interface.mdx b/ai-management/ai-studio/chat-interface.mdx new file mode 100644 index 0000000000..ef871d1e30 --- /dev/null +++ b/ai-management/ai-studio/chat-interface.mdx @@ -0,0 +1,68 @@ +--- +title: "Chat Interface" +description: "How AI Studios Chat Interface works?" +keywords: "AI Studio, AI Management, Chat Interface" +sidebarTitle: "Chat Interface" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +## Chat Interface + +Tyk AI Studio's Chat Interface provides a secure and interactive environment for users to engage with Large Language Models (LLMs), leveraging integrated tools and data sources. It serves as the primary front-end for conversational AI interactions within the platform. + +## Purpose + +The main goals of the Chat Interface are: + +* **User-Friendly Interaction:** Offer an intuitive web-based chat experience for users of all technical levels. +* **Unified Access:** Provide a single point of access to various configured LLMs, Tools, and Data Sources. +* **Context Management:** Maintain conversation history and manage context, including system prompts and retrieved data (RAG). +* **Secure & Governed:** Enforce access controls based on teams and apply configured Filters. + +## Key Features + +* **Chat Sessions:** Each conversation happens within a session, preserving history and context. +* **Streaming Responses:** LLM responses are streamed back to the user for a more interactive feel. +* **Tool Integration:** Seamlessly uses configured [Tools](/ai-management/ai-studio/tools) when the LLM determines they are necessary to fulfill a user's request. The available tools depend on the Chat Experience configuration and the user's team permissions. +* **Data Source (RAG) Integration:** Can automatically query configured [Data Sources](/ai-management/ai-studio/datasources-rag) to retrieve relevant information (Retrieval-Augmented Generation) to enhance LLM responses. The available data sources depend on the Chat Experience configuration and the user's team permissions. +* **System Prompts:** Administrators can define specific system prompts for different Chat Experiences to guide the LLM's persona, tone, and behavior. +* **History:** Users can view their past chat sessions. +* **Export to PDF:** From v2.1.0, users can export a chat conversation to PDF using the print button, available in both the standard chat view and agent chats. +* **File Upload (Context):** Users might be able to upload files directly within a chat to provide temporary context for the LLM (depending on configuration). +* **Access Control:** Users only see and can interact with Chat Experiences assigned to their [Teams](/ai-management/ai-studio/user-management). + +## Using the Chat Interface + +Users access the Chat Interface through the Tyk AI Studio web UI. + +1. **Select Chat Experience:** Users choose from a list of available Chat Experiences (pre-configured chat environments) they have access to. +2. **Interact:** Users type their prompts or questions. +3. **Receive Responses:** The LLM processes the request, potentially using tools or data sources behind the scenes, and streams the response back. + +Chat UI + +## Configuration (Admin) + +Administrators configure the available "Chat Experiences" (formerly known as Chat Rooms) via the UI or API. Configuration involves: + +* **Naming:** Giving the Chat Experience a descriptive name. +* **Assigning LLM:** Linking to a specific [LLM Configuration](/ai-management/ai-studio/llm-management). +* **Enabling Tools:** Selecting which [Tool Catalogues](/ai-management/ai-studio/tools) are available. +* **Enabling Data Sources:** Selecting which [Data Source Catalogues](/ai-management/ai-studio/datasources-rag) are available. +* **Setting System Prompt:** Defining the guiding prompt for the LLM. +* **Applying Filters:** Associating specific [Filters](/ai-management/ai-studio/filters) for governance. +* **Assigning Teams:** Determining which [Teams](/ai-management/ai-studio/user-management) can access this Chat Experience. +* **Enabling/Disabling Features:** Toggling features like file uploads or direct tool usage. + +Chat Config + +## API Access + +Beyond the UI, Tyk AI Studio provides APIs (`/api/v1/chat/...`) for programmatic interaction with the chat system, allowing developers to build custom applications or integrations that leverage the configured Chat Experiences. + +This comprehensive system provides a powerful yet controlled way for users to interact with AI capabilities managed by Tyk AI Studio. diff --git a/ai-management/ai-studio/chats.mdx b/ai-management/ai-studio/chats.mdx new file mode 100644 index 0000000000..985d4a6a6a --- /dev/null +++ b/ai-management/ai-studio/chats.mdx @@ -0,0 +1,41 @@ +--- +title: "Manage Chats in Tyk AI Studio" +description: "Chats are customized interfaces that allow users to have one-on-one conversations with specific LLM providers, tools, and data source based on their needs. Access is tailored to the user's group, ensuring relevant and secure interactions." +keywords: "AI Studio, AI Management, Chats, Chat Interface, LLMs" +sidebarTitle: "Chats" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Chats are customized interfaces that allow users to have one-on-one conversations with specific LLM providers, tools, and data based on their needs. Access is tailored to the user's group, ensuring relevant and secure interactions. + +This page covers the administrator's perspective on configuring and managing Chat Rooms. For the user's perspective on interacting with these chats, see the [Chat Interface](/ai-management/ai-studio/chat-interface) documentation. + +## Configuration + +The **Add/Edit Chat View** enables administrators to configure a new or existing chat room. Below is an explanation of each field and its purpose: + +- **Name**: The name of the chat room. +- **Description**: A description for this chat room. +- **LLM Settings**: Select the model call settings. These control parameters like temperature and max tokens. For more details, see [LLM Call Settings](/ai-management/ai-studio/call-settings). +- **LLM**: The specific language model powering the chat (e.g., Anthropic Claude, OpenAI GPT). +- **Groups**: Access control determining which user groups can see and use the chat. +- **Filters**: Governance policies applied to the chat interactions. +- **System Prompt**: The guiding instructions for the LLM's persona and behavior. +- **Enable Tool Support**: Toggles tool usage for the chat room. +- **Default Tools**: Select default tools that will be available in this chat room. These tools will be automatically accessible to the AI when responding to user queries. For more details, see [Tools](/ai-management/ai-studio/tools). +- **RAG Results or Source to Include for Model**: Configures vector database integration for context. +- **Default Data Source**: Setting a default data source will automatically include this vector database in all conversations in this chat room, allowing the model to reference its contents when generating responses. For more details, see [Data Sources (RAG)](/ai-management/ai-studio/datasources-rag). + +## How to Create a Chat Room + +1. Navigate to the **Chats** section in the AI Studio dashboard. +2. Click the green **+ ADD CHAT** button. +3. Fill in the required fields such as **Name** and configure the desired parameters like **LLM** and **System Prompt**. +4. Click **Create Chat** to save the configuration. + + Chat Configuration UI diff --git a/ai-management/ai-studio/compliance-events.mdx b/ai-management/ai-studio/compliance-events.mdx new file mode 100644 index 0000000000..0a365434a2 --- /dev/null +++ b/ai-management/ai-studio/compliance-events.mdx @@ -0,0 +1,279 @@ +--- +title: "Compliance Events in Tyk AI Studio" +description: "How filter scripts emit structured compliance events in Tyk AI Studio, and how to query and monitor them via the API, dashboard, and Prometheus metrics." +keywords: "AI Studio, AI Management, Compliance Events, Governance, Filters, PII Redaction, Audit, Compliance Dashboard" +sidebarTitle: "Compliance Events" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Available from Tyk AI Studio v2.1.0. + +Compliance Events are structured audit records that filter scripts can emit to flag governance-relevant activity, such as PII redactions, content rewrites, policy violations, or silent failures, without affecting whether the request is blocked or allowed. They flow through the same analytics pipeline as proxy logs and chat records, are stored centrally on the control plane, and surface in the Compliance dashboard for review, drill-down, and CSV export. + +This page is the end-to-end reference. For filter script syntax, see [Filters](/ai-management/ai-studio/filters#compliance-event-reporting). + +## Why Compliance Events + +Before v2.1, the only thing the compliance dashboard could count was 4xx proxy logs, that is, requests the gateway blocked. That misses the most common governance activity in practice: a filter quietly redacts an email address, rewrites a passage of text, or flags something suspicious while still allowing the request through. Those interventions are exactly what compliance teams need visibility into, and they previously left no trace beyond a debug log line. + +Compliance events fix that. Any filter script can attach a list of structured events to its output. The platform persists them, propagates them from edge gateways back to the control plane, exposes them via API and dashboard, and counts them in Prometheus, all without changing the filter's block/allow decision. + +## Event Schema + +A compliance event has the following shape (`ComplianceEventOutput` in the script API): + +| Field | Type | Required | Description | +|---|---|---|---| +| `event_type` | string | Yes | Free-form type. Suggested conventions: `pii_redacted`, `content_rewritten`, `policy_violation`, `harmful_content_detected`, `silent_failure`. | +| `severity` | string | No | `info`, `warning`, or `critical`. Defaults to `info` if omitted or invalid. | +| `description` | string | No | Human-readable description of what happened, shown in the dashboard event row. | +| `metadata` | map | No | Arbitrary key-value data. Stored as JSON. Conventions: `matched_pattern` (string of the trigger), `redacted_types` (array of category names). | + +**Validation:** + +- Events without an `event_type` are silently skipped. This avoids polluting the table with empty records when a script branch builds the event partially. +- `severity` values outside `info|warning|critical` are coerced to `info`. +- `event_type` on the API query side is limited to 100 characters; any longer is a 400. +- `severity` on the API query side is validated against the same whitelist; an invalid value is a 400. +- All API queries use GORM parameterized queries, so SQL injection attempts in `event_type` or `severity` are treated as literal string matches and return empty results. + +### Server-Side Enrichment + +Scripts only declare the four fields above. The platform enriches each event at recording time with context from the filter execution site: + +| Enriched field | Source | +|---|---| +| `app_id` | App that owns the request (proxy paths). `0` for chat-session paths, where `user_id` is the principal. | +| `user_id` | Authenticated user (chat-session paths). | +| `llm_id` | LLM in scope for the request. | +| `vendor` | LLM vendor string (for example `openai`, `anthropic`, `bedrock`). | +| `model_name` | Model being called. | +| `filter_name` | Name of the filter that emitted the event. | +| `filter_scope` | Where the script ran (see below). | +| `timestamp` | Wall-clock time at recording. | + +### Filter Scopes + +`filter_scope` identifies which of the six filter execution sites recorded the event: + +| Scope | Site | +|---|---| +| `proxy_request` | LLM proxy request filters (before reaching the LLM). | +| `proxy_response` | LLM proxy response filters (REST and streaming). | +| `chat_request` | Chat-session message filters (before RAG search and LLM). | +| `chat_response` | Chat-session response filters (regular and streaming). | +| `file_reference` | File content filters (before RAG indexing). | +| `tool_response` | Tool response filters (after tool execution). | + +You can filter on `filter_scope` indirectly via the dashboard drill-down, or by joining `compliance_events.filter_name` against your filter catalog. + +## Emitting Events from a Filter Script + +In a Tengo filter script, set `compliance_events` on the output object: + +```tengo +tyk := import("tyk") + +modified_payload := tyk.redact_pattern( + input, + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + "[EMAIL_REDACTED]" +) + +output := { + block: false, + payload: modified_payload, + message: "", + compliance_events: [ + { + event_type: "pii_redacted", + severity: "info", + description: "Email addresses redacted from request", + metadata: { "redacted_types": ["email"] } + } + ] +} +``` + +See [Filters](/ai-management/ai-studio/filters#compliance-event-reporting) for conditional emission, response-filter examples, and metadata conventions. + +### Behavior + +- Recording is **asynchronous**. Events are queued and written by the analytics pipeline, so filter latency is unaffected. +- Compliance events **never** affect the block/allow decision. Setting `block: false` with critical events is legitimate (the filter chose to redact rather than block); setting `block: true` with no events is also legitimate. +- The list can be empty or omitted. Most filters won't emit events on every invocation; typically only when a condition fires. +- Events are recorded **per filter, per invocation**. A single request can produce multiple events from multiple filters. + +## Edge Gateways + +In a hub-spoke deployment, filter scripts run on the [Edge Gateway](/ai-management/ai-studio/proxy), but compliance events live on the control plane. The propagation path is: + +1. Filter script on the edge sets `compliance_events` in its output. +2. The Edge Gateway's analytics handler (`MicrogatewaAnalyticsHandler.RecordComplianceEvents`) queues the events to the pulse plugin's local buffer, which survives a gateway restart. +3. On the next analytics pulse (every 30 seconds by default), events are batched into the gRPC `AnalyticsPulse` message as `ComplianceEventProto` entries. +4. The control server's `SendAnalyticsPulse` handler reconstructs the compliance event from each entry, preserving `LLMID`, `AppID`, `UserID`, severity, type, description, metadata, vendor, and model name, and persists it. + +This means there is no functional difference between an event recorded by the embedded gateway and one recorded by an Edge Gateway: both end up in the same table with the same enrichment. + +If you need to verify that edge events are arriving, check the `aistudio_compliance_events_total` counter on the control-plane `/metrics` endpoint and group by source. + +## Querying Compliance Events + +### Admin API + +``` +GET /api/v1/compliance/events +``` + +Admin-only (requires the v1 route group's auth middleware and an admin role). + +**Query parameters:** + +| Param | Type | Description | +|---|---|---| +| `start_date` | ISO8601 date | Lower bound (inclusive) on `timestamp`. | +| `end_date` | ISO8601 date | Upper bound (inclusive) on `timestamp`. | +| `app_id` | uint | Filter by app. | +| `event_type` | string (max 100 chars) | Exact match on `event_type`. | +| `severity` | `info` / `warning` / `critical` | Whitelist; other values return 400. | +| `limit` | int | Page size (default and max are deployment-dependent). | +| `offset` | int | Page offset. | + +**Example:** + +```bash +curl -H "Authorization: Bearer $ADMIN_TOKEN" \ + "https://studio.example.com/api/v1/compliance/events?start_date=2026-05-01&end_date=2026-05-14&severity=critical&limit=50" +``` + +**Response shape:** + +```json +{ + "events": [ + { + "id": 1234, + "app_id": 42, + "user_id": 0, + "llm_id": 7, + "filter_name": "PII Detector", + "filter_scope": "proxy_request", + "event_type": "pii_redacted", + "severity": "info", + "description": "Email addresses redacted from request", + "metadata": "{\"redacted_types\":[\"email\"]}", + "vendor": "openai", + "model_name": "gpt-4", + "timestamp": "2026-05-14T09:23:17Z" + } + ], + "total": 1 +} +``` + +`metadata` is returned as a JSON-encoded string (the storage shape). Parse it client-side. + +### SQL + +The underlying table is `compliance_events`. Useful indexes: + +- `(app_id, timestamp)`: composite, for per-app time-bounded queries. +- `user_id`, `llm_id`, `filter_name`, `event_type`, `severity`: single-column, for filtering. +- `timestamp`: for retention sweeps. + +Example: + +```sql +SELECT filter_name, event_type, COUNT(*) AS n +FROM compliance_events +WHERE timestamp >= NOW() - INTERVAL '7 days' + AND severity IN ('warning', 'critical') +GROUP BY filter_name, event_type +ORDER BY n DESC; +``` + +## Dashboard Surfaces + +The Compliance dashboard (`/admin/compliance`) was extended in v2.1 to surface compliance events alongside the existing blocked-request data. + +### Summary Cards + +Two new cards next to the blocked-request total: + +- **Critical Events**: count over the selected window, with trend arrow. Escalates the card style above `COMPLIANCE_EVENTS_CRITICAL_ESCALATE_AT` (default 1, since a single critical event already warrants attention). +- **Warning Events**: count over the selected window, with trend arrow. Escalates above `COMPLIANCE_EVENTS_WARNING_ESCALATE_AT` (default 20, since warnings are advisory and need to accumulate before they matter). + +### Policy Violations Tab + +The summary is split into three counters: + +- **Blocked**: 4xx proxy logs (existing). +- **Flagged**: warning and critical compliance events that passed through (new). +- **Affected Apps**: distinct apps appearing in either source. + +The timeline chart merges both streams. The per-app violation list unions events into the aggregation, so apps that only ever flag (and never block) appear in the drill-down. + +### Filter Events Tab + +A dedicated tab for compliance events with: + +- Severity totals (info / warning / critical) with a stacked timeline chart. +- Severity and event-type filters (the event-type dropdown is populated from observed values in the window). +- Pagination over the event list. +- Expandable rows showing the raw `metadata` JSON. +- CSV export wired to `GET /compliance/events`. + +### App Risk Modal + +Per-app risk score now includes event counts, with `warningEventWeight = 1` and `criticalEventWeight = 3`, so a single critical event contributes the same to the score as three warning events. Recent Violations interleaves blocked requests and compliance events sorted by timestamp, with severity-aware row rendering. + +## Metrics + +A Prometheus / OpenTelemetry counter is exposed on the `/metrics` endpoint: + +``` +aistudio_compliance_events_total{filter_scope, severity, event_type} +``` + +Use this to alert on critical-event bursts, or to watch for a sudden drop in expected redaction activity (which can indicate a misconfigured filter). Combine with `aistudio_llm_requests_total` to get a per-request rate. + +See [Analytics & Monitoring](/ai-management/ai-studio/analytics#prometheus-and-opentelemetry-metrics) for the full metric catalog. + +## Suggested Event-Type Conventions + +`event_type` is free-form, but consistency across filters makes dashboards and queries much more useful. Conventions used by the bundled filter scripts: + +| Event type | When to emit | +|---|---| +| `pii_redacted` | Filter replaced PII patterns with placeholders. Set `metadata.redacted_types` to an array of categories (`["email", "ssn", "phone"]`). | +| `content_rewritten` | Filter transformed user or LLM content in a way the user can't see (for example system-prompt augmentation, anonymization). | +| `sensitive_content_detected` | Filter found content matching a sensitive pattern. Pair with `block: true` for hard blocks; pair with `block: false` to record the detection without blocking. | +| `harmful_content_detected` | Response filter detected harmful output. Set `metadata.matched_pattern` to the trigger. | +| `policy_violation` | Filter detected a policy breach that may or may not be blocked. | +| `silent_failure` | Filter encountered an error path it chose to swallow (for example an unreachable classifier service, falling back to allow). Use `severity: warning` so it shows up in the dashboard without paging anyone. | + +For `metadata` keys, prefer: + +- `matched_pattern` (string): the substring or regex match that fired the event. +- `redacted_types` (array of strings): categories of PII or content removed. + +These keys are what the bundled filter scripts and the dashboard expect. + +## Migration Notes + +- **Schema**: v2.1 GORM auto-migration creates the `compliance_events` table on first startup. No manual step required. +- **Filter scripts** written for v2.0 are forward-compatible. The `compliance_events` field is optional and ignored if absent. +- **Bundled filter library**: the ship-with scripts (PII redaction, content blocking, response guardrails) were updated in v2.1 to emit compliance events with the conventions above. Re-import them from the marketplace, or copy from the updated templates in the admin UI, if you want them out of the box. +- **Custom analytics handlers**: the `AnalyticsHandler` interface now takes `context.Context` on its recording methods, including the new `RecordComplianceEvents`. Custom implementations need to update their signatures; `RecordComplianceEvents` can be a no-op if you do not consume compliance events. + +## See Also + +- [Filters](/ai-management/ai-studio/filters#compliance-event-reporting): script-level syntax and examples. +- [Analytics & Monitoring](/ai-management/ai-studio/analytics): the surrounding analytics pipeline and the full metric catalog. +- [Edge Gateway](/ai-management/ai-studio/proxy): hub-spoke architecture and the analytics pulse that carries edge events to control. +- [Tyk AI Studio release notes](/developer-support/release-notes/ai-studio): the `AnalyticsHandler` context change and related v2.1.0 SDK updates. diff --git a/ai-management/ai-studio/configuration.mdx b/ai-management/ai-studio/configuration.mdx new file mode 100644 index 0000000000..cc98cbe034 --- /dev/null +++ b/ai-management/ai-studio/configuration.mdx @@ -0,0 +1,92 @@ +--- +title: "Getting Started with Tyk AI Studio" +description: "In this guide, we will walk through the initial configuration steps after deploying Tyk AI Studio, including logging in, connecting an LLM provider, and starting your first chat." +keywords: "AI Studio, AI Management, Configuration" +sidebarTitle: "Getting Started" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + + +This guide assumes you are logged in as a **Studio Administrator** with full permissions to configure the system. + + +## 1. Login to the AI Studio + +After completing the [installation process](/ai-management/ai-studio/quickstart) and registering your first user: + +1. **Access the UI:** Open your web browser and navigate to your configured `SITE_URL` +2. **Admin Login:** Log in using the administrator account you created during registration: + + Login Screen + + + **Reminder**: If you haven't completed the initial registration yet, go back to your [installation guide](/ai-management/ai-studio/quickstart#first-user-registration) and follow the "First User Registration" section. + + + +## 2. Configure Your First LLM + +One of the most common initial steps is connecting Tyk AI Studio to an LLM provider. + +1. **Navigate to LLM Management:** In the admin UI sidebar, select **LLM Management > LLM Providers**. +2. **Add LLM Configuration:** Click the button to add a new LLM Configuration. +4. **Enter Details:** + * **Configuration Name:** Give it a recognizable name (e.g., `OpenAI-GPT-4o`). + * **Description:** Optionally, add a description for this configuration. + * **Select Vendor:** Choose the LLM provider you want to connect (e.g., OpenAI, Anthropic, Azure OpenAI). + * **Select Default Model:** Specify the exact model identifier(s) provided by the vendor (e.g., `gpt-4o`, `gpt-4-turbo`). + * Add API Key in the **Access Details** section: + + Do *not* paste your API key directly here. Instead, use [Secrets Management](/ai-management/ai-studio/secrets). + + * If you haven't already, go to the **Secrets** section in the admin UI and create a new secret: + * **Variable Name:** `OPENAI_API_KEY` (or similar) + * **Secret Value:** Paste your actual OpenAI API key here. + * Save the secret. + * Return to the LLM Configuration screen. + * In the API Key field, enter the secret reference: `$SECRET/OPENAI_API_KEY` (using the exact Variable Name you created). + * **Other Parameters:** Configure any other provider-specific settings (e.g., Base URL for Azure/custom endpoints, default temperature, etc.). +5. **Save:** Save the LLM configuration. + + LLM Config UI + +After adding and saving an LLM vendor, it is added to the default Catalog, making it available to [users](/ai-management/ai-studio/user-management) who have access to the default Catalog. + +For more details, see the [LLM Management](/ai-management/ai-studio/llm-management) documentation. + +## 3. Create Chat Experience + +1. Navigate to the **Chats > Chats** section in the admin UI. +2. Click to create a new Chat Experience. + - **Name:** Give your chat a descriptive name (e.g., `OpenAPI GPT4o`). + - **LLM Call settings:** Select the [LLM Call settings](/ai-management/ai-studio/call-settings) you want to use for this chat (e.g., `gpt-4o`). If the specific model settings are not available, you can create one in the [LLM model settings](/ai-management/ai-studio/call-settings#how-to-create-llm-call-settings) page. We have added defaults for the popular ones. + - **Select LLM vendor:** Select the LLM configuration you created in the previous step. + - **Select Group**: Assign this chat to a specific Group or select "Default" to make it available to all users. +3. Save the new Chat Experience. + + Chat Config + +## 4. Use the Chat Interface + +1. Navigate to the **Chat** tab at the top of the UI. +2. **Select Chat Experience:** Choose the `OpenAPI GPT4o` experience you just created from the list of available chats. +3. **Interact:** Type a question or prompt in the chat box. +4. **Receive Responses:** The LLM will process your request and stream the response back to you in the chat window. + + Chat UI + +You have now successfully configured an LLM and used it to answer a question through the Tyk AI Studio Chat Interface. + +## Next Steps + +With the initial configuration complete, you can now: + +* Explore [User Management](/ai-management/ai-studio/user-management) to create users and groups. +* Set up [Tools](/ai-management/ai-studio/tools) for external API integration. +* Configure [Data Sources](/ai-management/ai-studio/datasources-rag) for RAG. +* Define [Filters](/ai-management/ai-studio/filters) for custom request/response logic. diff --git a/ai-management/ai-studio/core-concepts.mdx b/ai-management/ai-studio/core-concepts.mdx new file mode 100644 index 0000000000..1d1368b714 --- /dev/null +++ b/ai-management/ai-studio/core-concepts.mdx @@ -0,0 +1,94 @@ +--- +title: "What is AI Studio?" +description: "Introduction to Tyk AI Studio, comprehensive AI management platform" +keywords: "AI Studio, AI Management, Introduction" +sidebarTitle: "Core Concepts" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio is a comprehensive platform that enables organizations to manage, govern, and deploy AI applications with enterprise-grade security, control, and observability. Before diving into installation and configuration, let's understand what AI Studio offers and its core concepts. + +## Key Components & Philosophy + +Tyk AI Studio is designed as a secure, observable, and extensible gateway for interacting with Large Language Models (LLMs) and other AI services. Key architectural pillars include: + +* **[AI Gateway](/ai-management/ai-studio/proxy):** The central gateway managing all interactions between your applications and various LLM providers. It enforces policies, logs activity, and handles vendor abstraction. The gateway exists in two forms: + * **Embedded Gateway** (in AI Studio): A lightweight \"gateway-lite\" for testing LLM configurations, powering the Chat interface, and proxying tool/datasource requests. No filters, no middleware, no plugins. + * **[Edge Gateway](/ai-management/ai-studio/proxy)** (standalone binary): The full-featured data plane with the complete middleware pipeline — authentication, filters, plugins, analytics, budget enforcement, tool calling (REST + MCP), and datasource querying. Deployed at edge locations in a hub-and-spoke architecture. +* **[Model Router](/ai-management/ai-studio/model-router) (Enterprise):** Intelligent request routing across multiple LLM vendors based on model name patterns, with support for load balancing, failover, and model name translation. +* **[Chat](/ai-management/ai-studio/chat-interface):** Provides a secure and interactive environment for users to engage with LLMs, leveraging integrated tools and data sources. +* **[User Management & RBAC](/ai-management/ai-studio/user-management):** Securely manages users, teams, and permissions. Access to resources like LLMs, Tools, and Data Sources is controlled via team memberships. +* **[AI Portal](/ai-management/ai-studio/ai-portal):** Empowers developers with a curated catalog of AI tools and services for faster innovation. +* **Policy Enforcement ([Filters](/ai-management/ai-studio/filters)):** Intercept and modify LLM requests/responses using custom scripts to enforce specific rules or data transformations. +* **Configuration over Code:** Many aspects like LLM parameters, Filters, and [Budgets](/ai-management/ai-studio/budgeting) are configured through the UI/API rather than requiring code changes. +* **Security First:** Features like [Secrets Management](/ai-management/ai-studio/secrets), [SSO integration](/ai-management/ai-studio/sso), and fine-grained access control are integral to the platform. +* **Observability:** Includes systems for [Analytics & Monitoring](/ai-management/ai-studio/analytics) and [Notifications](/ai-management/ai-studio/notifications) to track usage, costs, and system events. + +## Core Entities + +Understanding these entities is crucial: + +### User +Represents an individual interacting with Tyk AI Studio, managed within the [User Management system](/ai-management/ai-studio/user-management#core-concepts). + +### Team +Teams are the central access control mechanism for organizing users and managing their access to LLMs, data sources, and tools via Catalogs. See also [Teams](/ai-management/ai-studio/teams). + +### App +An App serves as the primary interface or bridge between an end-user and a Large Language Model (LLM). It acts as a managed API endpoint that wraps around an LLM provider (like OpenAI, Anthropic, or Mistral) to provide governance, security, and tracking. It encapsulates the LLMs, tools, and data sources needed for specific AI use cases and provides RESTful access via credentials. + +### API Key +Credentials generated by Users to allow applications or scripts programmatic access to Tyk AI Studio APIs (like the Proxy), inheriting the User's permissions. See also [User Management](/ai-management/ai-studio/user-management#core-concepts). + +### LLM Configuration +Represents a specific LLM provider and model setup (e.g., OpenAI GPT-4, Anthropic Claude 3), including parameters and potentially associated [pricing](/ai-management/ai-studio/llm-management) and [budgets](/ai-management/ai-studio/budgeting). + +### Tool +Definitions of external APIs (via OpenAPI spec) that can be invoked by LLMs during chat sessions to perform actions or retrieve external data. See also [Tools](/ai-management/ai-studio/tools). + +### Data Source +Connections to vector databases or other data repositories used for Retrieval-Augmented Generation (RAG) within chat sessions. See also [Data Sources](/ai-management/ai-studio/datasources-rag). + +### Catalogue +Collections that group related [Tools](/ai-management/ai-studio/tools) or [Data Sources](/ai-management/ai-studio/datasources-rag) for easier management and assignment to Teams for access control. + +### Secret +Securely stored credentials (API keys, tokens) referenced indirectly (e.g., `$SECRET/MY_KEY`) in configurations like LLMs, Tools, or Data Sources. See also [Secrets Management](/ai-management/ai-studio/secrets). + +### Filter +Custom logic (using Tengo scripts) associated with specific execution points (e.g., pre/post LLM request) to intercept and modify requests/responses. See also [Filters](/ai-management/ai-studio/filters). + +### Chat / Chat Room +Customized interfaces configured by admins that combine specific LLMs, tools, data sources, and system prompts for user interactions. See also [Chats](/ai-management/ai-studio/chats). + +### LLM Call Settings +Default runtime parameters (e.g., temperature, max tokens, top P) for LLMs used in chat interactions and middleware function calls. See also [LLM Call Settings](/ai-management/ai-studio/call-settings). + +### Model Price +Defines the cost per million tokens (input, output, cache) for using different language models, essential for analytics and budget enforcement. See also [Model Prices](/ai-management/ai-studio/model-prices). + +### Plugin +The extensibility system using a Unified Plugin SDK to add custom capabilities like Edge Gateway middleware, UI extensions, Agent plugins, and Object Hooks. See also [Plugins](/ai-management/ai-studio/plugins/overview). + +### Budget +Defines hard limits on costs (amount, currency, period) applied at the organization or LLM configuration level. See also [Budget Control](/ai-management/ai-studio/budgeting). + + +For a detailed view of how these components fit together, including the hub-and-spoke architecture and proxy modes, see the [Architecture Overview](/ai-management/ai-studio/architecture). + +## Getting Started + +Now that you understand the core concepts, you're ready to begin your AI Studio journey: + +1. **[Choose your installation method](/ai-management/ai-studio/quickstart)**: Docker/Packages (recommended) or Kubernetes +2. **[Complete first-time setup](/ai-management/ai-studio/configuration)**: Register your admin user and configure your first LLM +3. **Explore the platform**: Start with the chat interface and gradually explore advanced features + + + **Ready to start?** Head to the [Installation Guide](/ai-management/ai-studio/quickstart) to get AI Studio up and running in minutes. + diff --git a/ai-management/ai-studio/datasources-rag.mdx b/ai-management/ai-studio/datasources-rag.mdx new file mode 100644 index 0000000000..ae1e8d0f2a --- /dev/null +++ b/ai-management/ai-studio/datasources-rag.mdx @@ -0,0 +1,286 @@ +--- +title: "Manage Data Sources in Tyk AI Studio" +description: "Learn how to configure and manage Data Sources in Tyk AI Studio to enable Retrieval-Augmented Generation (RAG), connecting LLMs to external knowledge bases and vector stores." +keywords: "AI Studio, AI Management, Datasources, RAG" +sidebarTitle: "Data Sources & RAG" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio's Data Source system connects the platform to external knowledge bases, primarily vector stores, enabling **Retrieval-Augmented Generation (RAG)**. This allows Large Language Models (LLMs) to access and utilize specific information from your documents, grounding their responses in factual data. + +## Purpose + +The primary goal is to enhance LLM interactions by: + +* **Providing Context:** Injecting relevant information retrieved from configured data sources directly into the LLM prompt. +* **Improving Accuracy:** Reducing hallucinations and grounding LLM responses in specific, verifiable data. +* **Accessing Private Knowledge:** Allowing LLMs to leverage internal documentation, knowledge bases, or other proprietary information. + +## Core Concepts + +* **Data Source:** A configuration in Tyk AI Studio that defines a connection to a specific knowledge base (typically a vector store) and the associated embedding service used to populate it. +* **Vector Store Abstraction:** Tyk AI Studio provides a unified interface to interact with various vector database types. Supported stores: **Pinecone**, **PGVector**, **Chroma**, **Redis**, **Qdrant**, and **Weaviate**. Administrators configure the connection details for their chosen store. +* **Embedding Service:** Text needs to be converted into numerical vector embeddings before being stored and searched. Administrators configure the embedding service (e.g., OpenAI `text-embedding-ada-002`, a local Sentence Transformer model via an API endpoint) and its credentials (using [Secrets Management](/ai-management/ai-studio/secrets)). +* **File Processing:** Administrators upload documents (e.g., PDF, TXT, DOCX) to a Data Source configuration. Tyk AI Studio automatically: + * Chunks the documents into smaller, manageable pieces. + * Uses the configured Embedding Service to convert each chunk into a vector embedding. + * Stores the text chunk and its corresponding embedding in the configured Vector Store. +* **RAG (Retrieval-Augmented Generation):** The core process where: + 1. A user's query in the [Chat Interface](/ai-management/ai-studio/chat-interface) is embedded using the same embedding service. + 2. This query embedding is used to search the relevant vector store(s) for the most similar text chunks (based on vector similarity). + 3. The retrieved text chunks are added as context to the prompt sent to the LLM. + 4. The LLM uses this context to generate a more informed and relevant response. +* **Data Source Catalogues:** Similar to Tools, Data Sources are grouped into Catalogues for easier management and assignment to teams. +* **Privacy Levels:** Each Data Source has a privacy level. It can only be used in RAG if its level is less than or equal to the privacy level of the [LLM Configuration](/ai-management/ai-studio/llm-management) being used, ensuring data governance. + + Privacy levels define how data is protected by controlling LLM access based on its sensitivity: + - **Public (0)** – Safe to share (e.g., blogs, press releases). + - **Internal (25)** – Company-only info (e.g., reports, policies). + - **Confidential (50)** – Sensitive business data (e.g., financials, strategies). + - **Restricted/PII (100)** – Personal data (e.g., names, emails, customer info). + + *Note: Privacy levels are stored as integer scores in the system. The values shown in parentheses are the typical score mappings.* + +## Availability + +Data Sources are available on both **AI Studio** (embedded gateway) and **Edge Gateway**. Datasource configurations including vector store connection strings, API keys, and embedder credentials are synced to edge gateways via the hub-spoke configuration system with encryption in transit. Datasources support namespace filtering for enterprise multi-tenant deployments. All four proxy endpoints (search, vector search, metadata query, and embedding generation) are functional on edge gateways. + +## How RAG Works in the Chat Interface + +When RAG is enabled for a Chat Experience: + +1. User sends a prompt. +2. Tyk AI Studio embeds the user's prompt using the configured embedding service for the relevant Data Source(s). +3. Tyk AI Studio searches the configured Vector Store(s) using the prompt embedding to find relevant text chunks. +4. The retrieved chunks are formatted and added to the context window of the LLM prompt. +5. The combined prompt (original query + retrieved context) is sent to the LLM. +6. The LLM generates a response based on both the query and the provided context. +7. The response is streamed back to the user. + +{/* ## Advanced Data Source Management (Plugin Service API) + +Plugins can perform advanced data source operations through the Service API for managing vector store data programmatically: + +### Delete Documents by Metadata + +Remove specific chunks from vector stores using metadata filters: + +```go +// Delete all chunks for a specific file +metadata := map[string]string{"file_path": "src/main.go"} +count, err := ai_studio_sdk.DeleteDocumentsByMetadata(ctx, datasourceID, metadata, "AND", false) +``` + +**Features:** +- AND/OR filter modes for multiple conditions +- Dry-run mode to preview deletions without actually removing data +- Works with Chroma, PGVector, Pinecone, Weaviate + +**Use cases:** +- Clean up orphaned chunks when files are deleted +- Remove outdated documentation chunks +- Selective data cleanup based on metadata tags + +### Query by Metadata Only + +Find documents using metadata without vector similarity search: + +```go +// Find all chunks from a specific source +metadata := map[string]string{"source": "github", "repo": "myrepo"} +results, totalCount, err := ai_studio_sdk.QueryByMetadataOnly(ctx, datasourceID, metadata, "AND", 10, 0) +``` + +**Features:** +- Pagination with limit and offset parameters +- AND/OR filter modes for complex queries +- Returns total count for pagination UI + +**Use cases:** +- List all chunks from a specific source +- Find documents by file path or document ID +- Audit what data is stored in the vector store + +### Namespace Management + +List and manage vector store namespaces/collections: + +```go +// List all namespaces with document counts +namespaces, err := ai_studio_sdk.ListNamespaces(ctx, datasourceID) +for _, ns := range namespaces { + fmt.Printf("Namespace: %s, Documents: %d\n", ns.Name, ns.DocumentCount) +} + +// Delete entire namespace (requires confirmation for safety) +err := ai_studio_sdk.DeleteNamespace(ctx, datasourceID, "old-namespace", true) +``` + +**Features:** +- List all namespaces/collections in a vector store +- Get document counts per namespace +- Bulk deletion with safety confirmation requirement + +**Use cases:** +- Manage multi-tenant vector stores +- Clean up entire repositories when projects are deleted +- Monitor storage usage across namespaces + +**Vector Store Support:** +- ✅ Full support: Chroma, PGVector, Pinecone, Weaviate +- ⚠️ Limited: Redis (basic operations only) +- ⚠️ Partial: Qdrant (namespace management only) */} + +## Creating & Managing Data Sources (Admin) + +Administrators configure Data Sources via the UI or API: + +1. **Define Data Source:** Provide a name, description, and privacy level. +2. **Configure Vector Store:** + * Select the database type (e.g., `pinecone`). + * Provide connection details (e.g., endpoint/connection string, namespace/index name). + * Reference a [Secret](/ai-management/ai-studio/secrets) containing the API key/credentials. +3. **Configure Embedding Service:** + * Select the vendor/type (e.g., `openai`, `local`). + * Specify the model name (if applicable). + * Provide the service URL (if applicable, for local models). + * Reference a [Secret](/ai-management/ai-studio/secrets) containing the API key (if applicable). +4. **Upload Files:** Upload documents to be chunked, embedded, and indexed into the vector store. + + Datasource Config + +## Organizing & Assigning Data Sources (Admin) + +* **Create Catalogues:** Group related Data Sources into Catalogues (e.g., "Product Docs", "Support KB"). +* **Assign to Teams:** Assign Data Source Catalogues to specific [teams](/ai-management/ai-studio/user-management). + + Catalogue Config + +## Using Data Sources (User) + +Data Sources are primarily used implicitly via RAG within the [Chat Interface](/ai-management/ai-studio/chat-interface). + +A Data Source will be used for RAG if: + +1. The specific Chat Experience configuration includes the relevant Data Source Catalogue. +2. The user belongs to a Team that has been assigned that Data Source Catalogue. +3. The Data Source's privacy level is compatible with the LLM being used. + +## Programmatic Access via API + +Tyk AI Studio provides a direct API endpoint for querying configured Data Sources programmatically: + +### Datasource API Endpoint + +* **Endpoint:** `/datasource/{dsSlug}` (where `{dsSlug}` is the datasource identifier) +* **Method:** POST +* **Authentication:** Bearer token required in the Authorization header + +### Request Format + +```json +{ + "query": "your semantic search query here", + "n": 5 // optional, number of results to return (default: 3) +} +``` + +### Response Format + +```json +{ + "documents": [ + { + "content": "text content of the document chunk", + "metadata": { + "source": "filename.pdf", + "page": 42 + } + }, + // additional results... + ] +} +``` + +### Example Usage + +#### cURL + +```bash +curl -X POST "https://your-tyk-instance/datasource/product-docs" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"query": "How do I configure authentication?", "n": 3}' +``` + +#### Python + +```python +import requests + +url = "https://your-tyk-instance/datasource/product-docs" +headers = { + "Authorization": "Bearer YOUR_TOKEN", + "Content-Type": "application/json" +} +payload = { + "query": "How do I configure authentication?", + "n": 3 +} + +response = requests.post(url, json=payload, headers=headers) +results = response.json() + +for doc in results["documents"]: + print(f"Content: {doc['content']}") + print(f"Source: {doc['metadata']['source']}") + print("---") +``` + +#### JavaScript + +```javascript +async function queryDatasource() { + const response = await fetch('https://your-tyk-instance/datasource/product-docs', { + method: 'POST', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + query: 'How do I configure authentication?', + n: 3 + }) + }); + + const data = await response.json(); + + data.documents.forEach(doc => { + console.log(`Content: ${doc.content}`); + console.log(`Source: ${doc.metadata.source}`); + console.log('---'); + }); +} +``` + +### Common Issues and Troubleshooting + +1. **Trailing Slash Error:** The endpoint does not accept a trailing slash. Use `/datasource/{dsSlug}` and not `/datasource/{dsSlug}/`. + +2. **Authentication Errors:** Ensure your Bearer token is valid and has not expired. The token must have permissions to access the specified datasource. + +3. **404 Not Found:** Verify that the datasource slug is correct and that the datasource exists and is properly configured. + +4. **403 Forbidden:** Check that your user account has been granted access to the datasource catalogue containing this datasource. + +5. **Empty Results:** If you receive an empty documents array, try: + - Reformulating your query to better match the content + - Increasing the value of `n` to get more results + - Verifying that the datasource has been properly populated with documents + +This API endpoint allows developers to build custom applications that leverage the semantic search capabilities of configured vector stores without needing to implement the full RAG pipeline. diff --git a/ai-management/ai-studio/deployment-k8s.mdx b/ai-management/ai-studio/deployment-k8s.mdx new file mode 100644 index 0000000000..d0e2157674 --- /dev/null +++ b/ai-management/ai-studio/deployment-k8s.mdx @@ -0,0 +1,512 @@ +--- +title: "Install Tyk AI Studio on Kubernetes" +description: "Installation guide for the Tyk AI Studio on Kubernetes" +keywords: "AI Studio, AI Management, Installation, Kubernetes, Helm" +sidebarTitle: "Kubernetes" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + + +This guide focuses on the Enterprise Edition of Tyk AI Studio. For the Community Edition, please refer to the [Tyk AI Studio GitHub repository](https://github.com/TykTechnologies/ai-studio/blob/main/docs/site/docs/deployment-helm-k8s.md). The Community Edition uses different Docker images (`tykio/tyk-ai-studio` and `tykio/tyk-microgateway`) and does not require a license key. + + +This guide explains how to deploy Tyk AI Studio (control plane), an Edge Gateway (data plane), and PostgreSQL on Kubernetes using Helm. AI Studio manages configuration centrally and the Edge Gateway processes AI requests, receiving configuration via gRPC. + +## Prerequisites + +- Kubernetes 1.16+ +- Helm 3.0+ +- `kubectl` configured with access to your cluster +- A Tyk AI License key (contact support@tyk.io or your account manager to obtain) +- For production with TLS: cert-manager installed in your cluster + + +Running on Podman, containerd, or another container runtime? See [Container Runtimes](/deployment-and-operations/container-runtimes). + + +## Generate Secrets + +Before installing, generate three secret keys to secure communication and encrypt data: + +```bash +# Secret key for encryption (used for secrets management and SSO) +openssl rand -hex 16 +# Example output: a35b3f7b0fb4dd3a048ba4fc6e9fe0a8 + +# Encryption key for Edge Gateway communication (must be exactly 32 hex chars) +openssl rand -hex 16 +# Example output: 822d3d1e0e2d849263e45fc7bb842364 + +# gRPC auth token (for hub-spoke communication) +openssl rand -hex 16 +# Example output: 9f2c4a6b8d0e1f3a5c7d9e1b3a5c7d9e +``` + +Save these values — you will substitute them into the values file below. + +--- + +## Option 1: Testing / Quickstart + +For local development or test clusters. Uses NodePort services, internal PostgreSQL, and no ingress. + +### 1. Add the Helm Chart + +```bash +cd /path/to/tyk-ai-studio/helm +helm dependency update . +``` + +### 2. Create `values-testing.yaml` + +Replace the placeholder secrets with your generated values. The `grpcAuthToken` / `edgeAuthToken` and `microgatewayEncryptionKey` / `encryptionKey` pairs **must match**. + +```yaml Expandable +midsommar: + image: + repository: tykio/tyk-ai-studio-ent + tag: v2.0.0 + service: + type: NodePort + ports: + - name: http + port: 8080 + targetPort: 8080 + nodePort: 32580 + - name: gateway + port: 9090 + targetPort: 9090 + nodePort: 32590 + - name: grpc + port: 50051 + targetPort: 50051 + +config: + allowRegistrations: "true" + siteUrl: "http://localhost:32580" # Update post-install if not localhost + fromEmail: "noreply@localhost" + devMode: "true" # Required for login over plain HTTP + databaseType: "postgres" + tykAiSecretKey: "CHANGE-ME-first-secret" + tykAiLicense: "your-license-key" + ociCacheDir: "./data/cache/plugins" + ociRequireSignature: "false" + gatewayMode: "control" + grpcPort: "50051" + grpcHost: "0.0.0.0" + grpcTlsInsecure: "true" + grpcAuthToken: "CHANGE-ME-third-secret" + microgatewayEncryptionKey: "CHANGE-ME-second-secret" + # proxyUrl auto-resolves to the Edge Gateway k8s service — no need to set it + +database: + internal: true + user: "tyk" + password: "your-db-password" + name: "tyk_ai_studio" + +postgres: + persistence: + enabled: true + size: 1Gi + +microgateway: + enabled: true + image: + repository: tykio/tyk-microgateway-ent + tag: v2.0.0 + service: + type: NodePort + port: 8080 + nodePort: 32591 + config: + edgeId: "edge-1" + edgeNamespace: "default" + secrets: + edgeAuthToken: "CHANGE-ME-third-secret" # Must match config.grpcAuthToken + encryptionKey: "CHANGE-ME-second-secret" # Must match config.microgatewayEncryptionKey + tykAiLicense: "your-license-key" +``` + +### 3. Install + +```bash +helm install midsommar . -f values-testing.yaml +``` + +### 4. Set External Gateway URL + +The Edge Gateway's internal service URL is used for routing by default, but the portal needs to display the correct external URL for tools and datasources. After install, patch the config with your cluster's node IP: + +```bash +# Get the node IP and set the gateway URL +NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') +GATEWAY_URL="http://${NODE_IP}:32591" + +# Patch the configmap with correct URLs and restart AI Studio +STUDIO_URL="http://${NODE_IP}:32580" +kubectl patch configmap midsommar-config -p \ + "{\"data\":{\"SITE_URL\":\"${STUDIO_URL}\",\"TOOL_DISPLAY_URL\":\"${GATEWAY_URL}\",\"DATASOURCE_DISPLAY_URL\":\"${GATEWAY_URL}\"}}" +kubectl rollout restart deployment midsommar +``` + + +**Tip:** If you know your cluster's external IP or hostname upfront, you can skip this step by setting `config.toolDisplayUrl` and `config.datasourceDisplayUrl` in your values file instead. + + +### 5. Verify + +```bash +# Check all pods are running +kubectl get pods + +# Check AI Studio health (via NodePort) +curl -s http://${NODE_IP}:32580/health + +# Check Edge Gateway health (via NodePort) +curl -s http://${NODE_IP}:32591/health +``` + +### Access Points + +| Port | URL | Purpose | +|------|-----|---------| +| 32580 | `http://:32580` | AI Studio UI + REST API | +| 32590 | `http://:32590` | Embedded AI Gateway | +| 32591 | `http://:32591` | Edge Gateway | + +--- + +## Option 2: Production with TLS + +For production deployments with Ingress, TLS via cert-manager, and an external database. + +### 1. Create `values-production.yaml` + +Replace all placeholder values with your actual configuration. + +```yaml Expandable +midsommar: + image: + repository: tykio/tyk-ai-studio-ent + tag: v2.0.0 + ingress: + enabled: true + certificateEnabled: true + className: nginx + certManager: + issuer: letsencrypt-prod + hosts: + - host: studio.yourdomain.com + paths: + - path: / + pathType: Prefix + port: 8080 + tls: + - secretName: studio-tls-secret + hosts: + - studio.yourdomain.com + service: + ports: + - name: http + port: 8080 + targetPort: 8080 + - name: gateway + port: 9090 + targetPort: 9090 + - name: grpc + port: 50051 + targetPort: 50051 + +config: + allowRegistrations: "true" + siteUrl: "https://studio.yourdomain.com" + fromEmail: "noreply@yourdomain.com" + devMode: "false" + databaseType: "postgres" + tykAiSecretKey: "CHANGE-ME-first-secret" + tykAiLicense: "your-license-key" + ociCacheDir: "./data/cache/plugins" + ociRequireSignature: "false" + gatewayMode: "control" + grpcPort: "50051" + grpcHost: "0.0.0.0" + grpcTlsInsecure: "true" # Set to "false" with TLS certs + grpcAuthToken: "CHANGE-ME-third-secret" + microgatewayEncryptionKey: "CHANGE-ME-second-secret" + # proxyUrl, toolDisplayUrl, datasourceDisplayUrl auto-resolve from Edge Gateway ingress config + +database: + internal: false + url: "postgres://user:password@your-db-host:5432/tyk_ai_studio?sslmode=require" + +microgateway: + enabled: true + image: + repository: tykio/tyk-microgateway-ent + tag: v2.0.0 + ingress: + enabled: true + certificateEnabled: true + className: nginx + certManager: + issuer: letsencrypt-prod + hosts: + - host: gateway.yourdomain.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: gateway-tls-secret + hosts: + - gateway.yourdomain.com + config: + edgeId: "edge-1" + edgeNamespace: "default" + allowInsecure: "false" + tlsEnabled: "false" # gRPC client TLS to AI Studio + secrets: + edgeAuthToken: "CHANGE-ME-third-secret" + encryptionKey: "CHANGE-ME-second-secret" + tykAiLicense: "your-license-key" +``` + +### 2. Install + +```bash +helm dependency update . +helm install midsommar . -f values-production.yaml +``` + +### 3. Verify + +```bash +kubectl get pods +kubectl get ingress +curl -s https://studio.yourdomain.com/health +curl -s https://gateway.yourdomain.com/health +``` + +--- + +## After Deployment + +### First User Registration + +After deployment, you need to create your first admin user: + +1. **Access the application**: Navigate to your configured `SITE_URL` (e.g., `https://studio.yourdomain.com`) +2. **Register with admin email**: Use the EXACT email address you set in the `ADMIN_EMAIL` environment variable in your configuration. +3. **Complete registration**: The first user who registers with the admin email will automatically become the administrator. + + +**Important**: The first user registration must use the same email address specified in the `ADMIN_EMAIL` environment variable. This user will have full administrative privileges. + + +### Add Your API Keys + +AI Studio pre-populates OpenAI and Anthropic LLM configurations on first startup with placeholder secrets (`OPENAI_KEY` and `ANTHROPIC_KEY`). To start using them: + +1. Open AI Studio at the `siteUrl` you configured and log in with your admin account +2. Navigate to **Governance → Secrets** in the sidebar +3. Click on **`OPENAI_KEY`** and edit it to add your OpenAI API key +4. Click on **`ANTHROPIC_KEY`** and edit it to add your Anthropic API key + +### Push Configuration to the Edge Gateway + +1. Navigate to **AI Portal → Edge Gateways** in the sidebar +2. Verify your edge gateway (`edge-1`) shows as **Connected** +3. Click **Push Configuration** to sync the latest settings to the Edge Gateway + +Once the sync status shows **Synced**, the Edge Gateway is ready to proxy LLM requests. + +For further setup (additional LLMs, users, applications), see the **[Initial Configuration](/ai-management/ai-studio/configuration)** guide. + +--- + +## Shared Secrets Reference + +These values **must match** between AI Studio and Edge Gateway configuration: + +| AI Studio Config | Edge Gateway Config | Purpose | +|---|---|---| +| `config.grpcAuthToken` | `microgateway.secrets.edgeAuthToken` | Authenticates the gRPC connection | +| `config.microgatewayEncryptionKey` | `microgateway.secrets.encryptionKey` | Encrypts synced configuration data | +| `config.tykAiLicense` | `microgateway.secrets.tykAiLicense` | Enterprise license | + +## Port Reference + +| Port | Component | Purpose | +|------|-----------|---------| +| 8080 | AI Studio | Admin UI + REST API | +| 9090 | AI Studio | Embedded AI Gateway | +| 50051 | AI Studio | gRPC control server (internal) | +| 8080 | Edge Gateway | Edge AI Gateway | +| 5432 | PostgreSQL | Database | + +--- + +## Advanced Configuration + +### Message Queue (NATS) + +For distributed deployments with message persistence, add NATS configuration to your values file: + +```yaml +config: + queueType: "nats" + natsUrl: "nats://nats-server:4222" + natsStorageType: "file" + natsRetentionPolicy: "interest" + natsMaxAge: "4h" + natsTlsEnabled: true + natsCredentialsFile: "/etc/nats/user.creds" +``` + +### Optional Components + +#### Reranker Service + +Improves RAG result relevance: + +```yaml +reranker: + enabled: true + image: + repository: tykio/reranker_cpu + tag: latest + resources: + requests: + cpu: 500m + memory: 1Gi +``` + +#### Transformer Server + +Handles embedding generation: + +```yaml +transformer-server: + enabled: true + image: + repository: tykio/transformer_server_cpu + tag: latest + resources: + requests: + cpu: 500m + memory: 1Gi +``` + +### Scaling Edge Gateways + +To deploy multiple edge gateways for different regions, override `edgeId` and `edgeNamespace` per instance. You can either deploy separate Helm releases or create additional Kubernetes Deployments with unique values: + +```yaml +microgateway: + config: + edgeId: "edge-eu-west-1" + edgeNamespace: "eu-west" +``` + +Each edge instance registers independently with AI Studio and receives only the configuration assigned to its namespace. + +### Database Options + +**Internal PostgreSQL** (testing/small deployments): + +```yaml +database: + internal: true + user: "tyk" + password: "secure-password" + name: "tyk_ai_studio" + +postgres: + persistence: + enabled: true + size: 10Gi + storageClass: "standard" +``` + +**External Database** (production): + +```yaml +database: + internal: false + url: "postgres://user:password@your-db-host:5432/tyk_ai_studio?sslmode=require" +``` + +--- + +## Maintenance + +### Upgrading + +```bash +helm upgrade midsommar . -f your-values.yaml +``` + +### Uninstalling + +```bash +helm uninstall midsommar +``` + +### Viewing Logs + +```bash +# AI Studio logs +kubectl logs -l app.kubernetes.io/name=midsommar + +# Edge Gateway logs +kubectl logs -l app=microgateway + +# Database logs (internal postgres) +kubectl logs -l app=postgres +``` + +## Troubleshooting + + + + + +```bash +kubectl get pods +kubectl get ingress +kubectl describe pod +```` + + + + + +* **Database connection failures**: Check credentials and network access +* **Ingress not working**: Verify DNS records and TLS configuration +* **Login fails on HTTP**: Set `devMode: "true"` — session cookies require this when not using HTTPS +* **Marketplace page is empty**: Set `ociCacheDir: "./data/cache/plugins"` in your config values — the marketplace service will not start without it +* **Plugin signature verification**: Docker images use distroless bases without cosign. Set `ociRequireSignature: "false"` to disable signature verification + + + + + +* Verify the Edge Gateway pod logs: + +```bash +kubectl logs -l app=microgateway +``` + +* Check that `CONTROL_ENDPOINT` resolves to the AI Studio service (default: `midsommar:50051`) +* Verify `edgeAuthToken` matches `grpcAuthToken` exactly +* Verify `encryptionKey` matches `microgatewayEncryptionKey` exactly +* Check that `GATEWAY_MODE=control` is set in AI Studio config + + + + \ No newline at end of file diff --git a/ai-management/ai-studio/edge-gateway-env.mdx b/ai-management/ai-studio/edge-gateway-env.mdx new file mode 100644 index 0000000000..d5a1c442df --- /dev/null +++ b/ai-management/ai-studio/edge-gateway-env.mdx @@ -0,0 +1,42 @@ +--- +title: "Tyk Edge Gateway Environment Variables" +description: "Environment variables and configuration options for Tyk Edge Gateway." +order: 2 +sidebarTitle: "Edge Gateway" +--- + +import EdgeGatewayConfig from '/snippets/edge-gateway-config.mdx'; +import EnvTypeMapping from '/snippets/env-type-mapping.mdx'; + +This page details the environment variables that can be used to configure the Tyk Edge Gateway. + +## Configuration + +Tyk Edge Gateway (Edge Gateway) is configured primarily using environment variables. + +### Configuration Precedence + +The application loads configuration in the following order of precedence (highest to lowest): + +1. **Shell Environment Variables**: Variables set in the OS/Shell (e.g., `export PORT=9090`) always override everything else. +2. **`.env` File**: Variables loaded from the file specified by the `-env` flag. + * *Note:* The application checks if a variable is already set in the environment before loading it from the file, ensuring shell variables are preserved. + +### Command Line Flags + +You can specify a `.env` file using the `-env` flag when starting the binary: + +```bash +./microgateway -env /path/to/prod.env +``` + +### Supported Formats + +* **Main Configuration**: Only the `.env` format (key=value pairs) is supported via the `-env` flag. +* **Plugin Configuration**: Supports JSON and YAML files for plugin definitions, specified via the `PLUGINS_CONFIG_PATH` environment variable. + + + +## Variables + + \ No newline at end of file diff --git a/ai-management/ai-studio/filters.mdx b/ai-management/ai-studio/filters.mdx new file mode 100644 index 0000000000..8cf3f62405 --- /dev/null +++ b/ai-management/ai-studio/filters.mdx @@ -0,0 +1,1063 @@ +--- +title: "Filters and Middleware" +description: "How to use Filters and Middleware in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Filters, Middleware" +sidebarTitle: "Filters & Policies" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +The **Filters List View** allows administrators to manage filters and middleware applied to prompts or data sent to Large Language Models (LLMs) via the AI Gateway or Chat Rooms. Filters and middleware ensure data governance, compliance, and security by processing or controlling the flow of information. Below is an enhanced description with the distinction between **Filters** and **Middleware**: + +--- + +#### **Filters: Unified Blocking and Modification** + +Filters in Midsommar provide comprehensive request/response processing with both **blocking** and **modification** capabilities: + +1. **Blocking Filters**: + - **Purpose**: Governance controls that deny requests based on content analysis. + - **Behavior**: + - Analyze message content, metadata, and context. + - Block requests that violate policies or contain restricted content. + - Example: Block prompts containing PII, sensitive keywords, or unauthorized patterns. + +2. **Modification Filters**: + - **Purpose**: Transform message content before it reaches the LLM or after tool responses. + - **Behavior**: + - Redact sensitive information (emails, phone numbers, SSNs). + - Enhance system prompts with safety instructions. + - Normalize or transform content across vendors. + - Example: Automatically redact PII while allowing the request to proceed. + +3. **Combined Approach**: + - Filters can both inspect AND modify in a single script. + - Example: Redact emails from user messages, but block if SSN is detected. + +**Key Capabilities**: +- ✅ **Request Filters**: Modify/block requests before reaching LLM + - LLM Proxy Requests (before reaching LLM) + - Chat Session Messages (before RAG search and LLM) + - File Content (before RAG indexing) + - Tool Responses (after tool execution) +- ✅ **Response Filters**: Block LLM responses based on content + - LLM Proxy Responses (REST and streaming) + - Chat Session Responses (regular and streaming) + +--- + +#### **Table Overview** + +1. **Name**: + - The name of the filter or middleware (e.g., `Anonymize PII (LLM)`, `Fixed PII Filter`). + +2. **Description**: + - A brief summary of the filter or middleware's functionality (e.g., "Uses Regex to remove obvious PII"). + +3. **Actions**: + - A menu (three-dot icon) that allows administrators to: + - Edit the filter or middleware. + - Delete the filter or middleware. + +--- + +#### **Features** + +1. **Add Filter Button**: + - A green button labeled **+ ADD FILTER**, located in the top-right corner. Clicking this button opens a form to create a new filter or middleware. + +2. **Pagination Dropdown**: + - Located at the bottom-left corner, this control allows administrators to adjust the number of entries displayed per page. + +--- + +#### **Examples of Filters and Middleware** + +- **Filters**: + - **PII Detector**: A regex-based filter that blocks prompts containing sensitive PII. + - **JIRA Field Analysis**: Ensures no PII is included in data retrieved from JIRA fields before passing to the LLM. + +- **Middleware**: + - **Anonymize PII (LLM)**: Uses an LLM to anonymize sensitive data before sending it downstream. + - **NER Service Filter**: A Named Entity Recognition (NER) microservice that modifies outputs to remove identified entities. + +--- + +#### **Use Cases** + +1. **Prompt Validation with Filters**: + - Ensures that only compliant and secure prompts are sent to LLMs. + - Example: Blocking a prompt with sensitive data that should not be processed by an unapproved vendor. + +2. **Data Preprocessing with Middleware**: + - Prepares data from tools or external sources for safe interaction with LLMs by modifying or anonymizing content. + - Example: Removing sensitive ticket details from a JIRA query before sending to an LLM. + +3. **Organizational Security**: + - Both filters and middleware ensure sensitive information is protected and handled in line with organizational governance policies. + +4. **Enhanced Tool Interactions**: + - Middleware supports tools by transforming their outputs, enabling richer and safer LLM interactions. + +--- + +#### **Key Benefits** + +1. **Improved Data Governance**: + - Filters and middleware work together to enforce strict controls over data flow, protecting sensitive information. + +2. **Flexibility**: + - Middleware allows for data transformation, enhancing interoperability between tools and LLMs. + - Filters ensure compliance without altering user-provided prompts. + +3. **Compliance and Security**: + - Prevent unauthorized or sensitive data from reaching unapproved vendors, ensuring regulatory compliance. + +This detailed structure for **Filters and Middleware** provides organizations with robust governance tools to secure and optimize data workflows in the Tyk AI Studio. + +### Filter Edit View (and example Filter) + +The **Filter Edit View** enables administrators to create or modify filters using the **Tengo scripting language**. Filters serve as governance tools that analyze input data (e.g., prompts or files) and decide whether the content is permitted to pass to the upstream LLM. In this example, the filter uses regular expressions (regex) to detect Personally Identifiable Information (PII) and blocks the prompt if any matches are found. + +--- + +#### **Form Sections and Fields** + +1. **Name** *(Required)*: + - Specifies the name of the filter (e.g., `PII Detector`). + +2. **Description** *(Optional)*: + - A brief summary of the filter's purpose and functionality (e.g., "Simple Regex-based PII detector to prevent the wrong data being sent to LLMs"). + +3. **Script** *(Required)*: + - A **Tengo script** that defines the logic of the filter. The script evaluates input data and determines whether the filter approves or blocks it. + - The example script detects PII using a collection of regex patterns and blocks the data if a match is found. + +--- + +#### **New Unified Script API** + +Modern filters use a unified API that provides rich context and supports both blocking and modification: + +**Input Object:** +```javascript +input := { + raw_input: "...", // Full JSON request payload + messages: [ // Normalized message array with roles + {role: "system", content: "You are helpful"}, + {role: "user", content: "Hello"} + ], + vendor_name: "openai", // LLM vendor (openai, anthropic, google_ai, etc.) + model_name: "gpt-4", // Model being called + is_chat: false, // Context: chat session (true) or proxy (false) + context: { // Additional metadata + llm_id: 123, + app_id: 456, + user_id: 789 + } +} +``` + +**Output Object:** +```javascript +output := { + block: false, // Set true to block the request + payload: "", // Modified JSON payload (or empty for no change) + messages: [], // Alternative: modified message array + message: "" // Optional reason/log message +} +``` + +--- + +#### **Example Script 1: Blocking Filter (PII Detection)** + +This script blocks requests containing PII patterns: + +```tengo +text := import("text") + +// Check all user messages for email addresses +should_block := false +block_reason := "" + +for msg in input.messages { + if msg.role == "user" { + if text.contains(msg.content, "@") { + should_block = true + block_reason = "Email addresses not allowed" + break + } + } +} + +output := { + block: should_block, + payload: input.raw_input, + message: block_reason +} +``` + +--- + +#### **Example Script 2: Modification Filter (Email Redaction)** + +This script redacts emails while allowing the request to proceed: + +```tengo +tyk := import("tyk") + +// Use helper to redact email addresses across all messages +modified_payload := tyk.redact_pattern( + input, + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + "[EMAIL_REDACTED]" +) + +output := { + block: false, + payload: modified_payload, + message: "Emails redacted" +} +``` + +--- + +#### **Example Script 3: Advanced Modification (Messages Array)** + +This script shows complex message modification using the messages array approach: + +```tengo +text := import("text") + +// Modify messages based on role +modified := [] + +for msg in input.messages { + new_msg := { + role: msg.role, + content: msg.content + } + + // Add safety prefix to system prompts + if msg.role == "system" { + new_msg.content = "[SAFETY MODE] " + msg.content + } + + // Redact emails from user messages + if msg.role == "user" { + new_msg.content = text.replace(msg.content, "@", "[AT]", -1) + } + + modified = append(modified, new_msg) +} + +output := { + block: false, + messages: modified, // System handles vendor-specific JSON reconstruction + message: "Content modified" +} +``` + +--- + +#### **Example Script 4: Combined Blocking + Modification** + +This script redacts emails but blocks if SSN is detected: + +```tengo +text := import("text") +tyk := import("tyk") + +// First check for SSN (hard block) +ssn_pattern := "\\d{3}-\\d{2}-\\d{4}" +has_ssn := false + +for msg in input.messages { + if text.re_match(ssn_pattern, msg.content) { + has_ssn = true + break + } +} + +if has_ssn { + output := { + block: true, + payload: "", + message: "Blocked: SSN detected" + } +} else { + // No SSN - redact emails and continue + modified_payload := tyk.redact_pattern( + input, + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + "[EMAIL]" + ) + + output := { + block: false, + payload: modified_payload, + message: "Emails redacted" + } +} +``` + +--- + +#### **Available Helper Functions** + +The `midsommar` module provides helper functions for common message modification tasks: + +1. **`redact_pattern(input, pattern, replacement)`**: + - Redacts a regex pattern from all messages (system, user, assistant) + - Parameters: + - `input` - The input object provided to your script + - `pattern` - Regular expression pattern (string) + - `replacement` - Replacement string + - Returns: Modified payload as string + - Example: `tyk.redact_pattern(input, "\\d{3}-\\d{2}-\\d{4}", "[SSN]")` + +**Vendor-Agnostic**: Helper functions automatically handle differences between OpenAI, Anthropic, Google AI, and other vendor formats. + +--- + +#### **Message Modification Approaches** + +**Approach 1: Helper Functions** (Simple, recommended for pattern-based redaction) +```tengo +tyk := import("tyk") +modified := tyk.redact_pattern(input, "@\\S+", "[EMAIL]") +output := {block: false, payload: modified, message: ""} +``` + +**Approach 2: Messages Array** (Flexible, recommended for complex logic) +```tengo +modified := [] +for msg in input.messages { + new_msg := {role: msg.role, content: msg.content} + if msg.role == "user" { + // Apply custom modification logic + new_msg.content = transform(msg.content) + } + modified = append(modified, new_msg) +} +output := {block: false, messages: modified, message: ""} +``` + +--- + +#### **Accessing Message Context** + +Scripts can access rich contextual information: + +```tengo +// Access vendor and model information +if input.vendor_name == "anthropic" { + // Anthropic-specific logic +} + +// Count messages by role +user_count := 0 +for msg in input.messages { + if msg.role == "user" { + user_count = user_count + 1 + } +} + +// Check if this is a chat session or proxy request +if input.is_chat { + // Chat-specific logic +} + +// Access metadata +app_id := input.context.app_id +user_id := input.context.user_id +``` + +--- + +#### **Action Buttons** +1. **Update Filter / Create Filter**: + - Saves the filter configuration, making it active for future data processing. + +2. **Back to Filters**: + - Returns to the Filters List View without saving changes. + +--- + +#### **Purpose and Benefits** + +1. **Data Governance**: + - Enforces strict control over what data can be sent to LLMs, ensuring compliance with privacy regulations. + +2. **Flexibility**: + - Filters can be tailored to specific organizational needs using custom scripts. + +3. **Security**: + - Prevents sensitive or unauthorized data from leaking to unapproved vendors or external systems. + +This **Filter Edit View** provides a robust and customizable interface for creating scripts to enforce data governance and security in the Tyk AI Studio. + +### Example Middleware for Tools + +Middleware filters in the Tyk AI Studio modify data coming from tools before passing it to the LLM. These filters are applied to sanitize, anonymize, or enhance the data to ensure it complies with organizational standards and privacy regulations. Below is an example of a middleware filter that sanitizes Personally Identifiable Information (PII), specifically email addresses, from the tool's output. + +--- + +#### **Middleware Script: Email Redaction Example** + +```tengo +// Import the 'text' module for regular expression operations +text := import("text") + +// Define regular expression patterns for various PII +email_pattern := `[\w\.-]+@[\w\.-]+\.\w+` + +// Define the function to sanitize PII in the input string +filter := func(input) { + // Replace email addresses + input = text.re_replace(email_pattern, input, "[REDACTED EMAIL]") + + return input +} + +// Process the input payload +result := filter(payload) +``` + +--- + +#### **Explanation of the Script** + +1. **Module Import**: + - The `text` module is imported to enable regular expression operations (`text.re_replace`). + +2. **Regex Pattern**: + - A regex pattern is defined to detect email addresses: + - Example pattern: `[\w\.-]+@[\w\.-]+\.\w+` + - This pattern matches standard email formats. + +3. **Filter Function**: + - The `filter` function accepts an input string (e.g., tool output) and: + - Uses `text.re_replace` to identify email addresses. + - Replaces detected email addresses with `[REDACTED EMAIL]`. + +4. **Return Processed Output**: + - The sanitized output is returned, ensuring that sensitive information like email addresses is redacted before reaching the LLM. + +--- + +#### **Use Case for Middleware** + +**Tool Example**: +Imagine a tool, such as `Support Ticket Viewer`, which retrieves user tickets from a system. These tickets often contain email addresses. Middleware ensures that no sensitive email information is included in the output sent to the LLM. + +- **Input Payload Example**: + ```text + User email: john.doe@example.com has reported an issue with their account. + ``` + +- **Sanitized Output**: + ```text + User email: [REDACTED EMAIL] has reported an issue with their account. + ``` + +--- + +#### **Benefits of Middleware** + +1. **Data Privacy**: + - Protects sensitive user information by ensuring it is sanitized before being sent to external systems. + +2. **Compliance**: + - Ensures organizational adherence to privacy laws like GDPR or HIPAA. + +3. **Enhanced Security**: + - Prevents accidental sharing of PII with external vendors or LLMs. + +--- + +## Available Tengo Modules + +Filters have access to powerful standard library modules: + +### **text** - String Operations +```tengo +text := import("text") + +// Common functions: +text.contains(str, substr) // Check if substring exists +text.replace(str, old, new, n) // Replace occurrences +text.to_upper(str) // Convert to uppercase +text.to_lower(str) // Convert to lowercase +text.split(str, sep) // Split string +text.trim_space(str) // Remove whitespace +text.re_match(pattern, str) // Regex match +text.re_replace(pattern, str, repl) // Regex replace +``` + +### **json** - JSON Operations +```tengo +json := import("json") + +parsed := json.decode(json_string) // Parse JSON +encoded := json.encode(object) // Encode to JSON +``` + +### **fmt** - Formatting and Printing +```tengo +fmt := import("fmt") + +fmt.println("Debug:", variable) // Print for debugging +formatted := fmt.sprintf("Value: %v", val) // Format strings +``` + +### **tyk** - Extended Capabilities (Enterprise) +```tengo +tyk := import("tyk") + +// Redact regex patterns from all messages (vendor-agnostic) +modified := tyk.redact_pattern(input, pattern, replacement) +// Returns: Modified payload as string + +// Make HTTP requests from within filters +result := tyk.makeHTTPRequest(method, url, headers, body) +// Returns: {status: 200, response: "..."} + +// Call other LLMs for analysis/enrichment +response := tyk.llm(llm_id, settings_object, prompt) +// Parameters: +// - llm_id: ID of the managed LLM to use +// - settings_object: Map with model settings (model_name, temperature, max_tokens, etc.) +// - prompt: The prompt text to send +// Returns: LLM response as string +``` + +**Example - Use LLM for PII Detection:** +```tengo +tyk := import("tyk") + +// Get user message +user_msg := "" +for msg in input.messages { + if msg.role == "user" { + user_msg = msg.content + break + } +} + +// Use another LLM to detect PII +// Define settings for the LLM call +settings := { + model_name: "gpt-3.5-turbo", + temperature: 0.0, + max_tokens: 10, + system_prompt: "You are a PII detection assistant. Answer only 'yes' or 'no'." +} + +pii_check_prompt := "Does this text contain PII?: " + user_msg +pii_result := tyk.llm(1, settings, pii_check_prompt) + +output := { + block: pii_result == "yes", + payload: input.raw_input, + message: pii_result == "yes" ? "PII detected by LLM" : "" +} +``` + +**Example - Call External Service:** +```tengo +tyk := import("tyk") +json := import("json") + +// Get user message +user_msg := "" +for msg in input.messages { + if msg.role == "user" { + user_msg = msg.content + } +} + +// Call external PII detection API +headers := { + "Content-Type": "application/json", + "Authorization": "Bearer YOUR_TOKEN" +} +body := json.encode({text: user_msg}) + +result := tyk.makeHTTPRequest("POST", "https://pii-api.example.com/detect", headers, body) + +// Parse response +response := json.decode(result.response) +has_pii := response.has_pii + +output := { + block: has_pii, + payload: input.raw_input, + message: has_pii ? "External PII service detected sensitive data" : "" +} +``` + +--- + +## Compliance Event Reporting + +Available from Tyk AI Studio v2.1.0 (Enterprise edition). + +Filter scripts can emit structured [Compliance Events](/ai-management/ai-studio/compliance-events) to record governance-relevant activity, such as PII redactions, content rewrites, or guardrail triggers, without affecting the block/allow decision. Events flow through the analytics pipeline into the Compliance dashboard, where they appear in the Filter Events tab with severity filters, drill-down, and CSV export. + +To emit events, set the optional `compliance_events` field on the script output: + +```tengo +tyk := import("tyk") + +modified_payload := tyk.redact_pattern( + input, + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + "[EMAIL_REDACTED]" +) + +output := { + block: false, + payload: modified_payload, + message: "", + compliance_events: [ + { + event_type: "pii_redacted", + severity: "info", + description: "Email addresses redacted from request", + metadata: { "redacted_types": ["email"] } + } + ] +} +``` + +Each event accepts: + +| Field | Required | Description | +|---|---|---| +| `event_type` | Yes | Free-form type, for example `pii_redacted`, `content_rewritten`, `policy_violation`, `silent_failure`. Events without an `event_type` are skipped. | +| `severity` | No | `info`, `warning`, or `critical`. Defaults to `info`. | +| `description` | No | Human-readable description shown in the dashboard. | +| `metadata` | No | Arbitrary key-value data. Use `matched_pattern` (string) and `redacted_types` (array) for consistency with the bundled scripts. | + +**Conditional Emission.** Most filters should only emit events when a condition actually fires. Build the list dynamically: + +```tengo +events := [] +if redaction_count > 0 { + events = append(events, { + event_type: "pii_redacted", + severity: "info", + description: fmt.sprintf("%d PII values redacted", redaction_count), + metadata: { "redacted_types": redacted_types } + }) +} + +output := { + block: false, + payload: modified_payload, + message: "", + compliance_events: events +} +``` + +Key behaviors: + +- Recording is asynchronous, so filter latency is unaffected. +- Events never change the block/allow decision. Redact-and-allow with a `critical` event is a legitimate pattern. +- Events work in every filter scope: proxy request/response, chat request/response, file reference, and tool response filters. +- On Edge Gateways, events are batched and forwarded to the control plane on the analytics pulse. + +The platform enriches each event with the app, user, LLM, vendor, model, filter name, and filter scope at recording time. For the event schema, querying via API, dashboard surfaces, and suggested event-type conventions, see [Compliance Events](/ai-management/ai-studio/compliance-events). + +--- + +## Tool Response Filters + +Filters can also be applied to tool responses (e.g., API calls, database queries). Tool responses are **plain strings**, not JSON-structured messages, so they require simpler handling. + +### **Example 1: Block Tool Responses Containing Errors** + +```tengo +text := import("text") + +// Access tool response from messages array +tool_output := "" +if len(input.messages) > 0 { + tool_output = input.messages[0].content +} + +// Block if response indicates an error +should_block := text.contains(tool_output, "error") || text.contains(tool_output, "failed") + +output := { + block: should_block, + payload: input.raw_input, + message: should_block ? "Tool returned error response" : "" +} +``` + +### **Example 2: Redact PII from Tool Responses** + +```tengo +text := import("text") + +// Tool responses are plain strings - use direct string manipulation +modified := input.raw_input + +// Get tool content +if len(input.messages) > 0 { + content := input.messages[0].content + + // Redact email addresses + modified = text.re_replace("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", content, "[EMAIL]") + + // Redact phone numbers + modified = text.re_replace("\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}", modified, "[PHONE]") +} + +output := { + block: false, + payload: modified, + message: "PII redacted from tool response" +} +``` + +### **Example 3: Filter Based on Tool Name** + +```tengo +// Access tool metadata from context +tool_name := "" +if input.context && input.context.tool_name { + tool_name = input.context.tool_name +} + +// Only allow specific tools to return data +allowed_tools := ["weather_api", "stock_prices"] +is_allowed := false + +for allowed in allowed_tools { + if tool_name == allowed { + is_allowed = true + break + } +} + +output := { + block: !is_allowed, + payload: input.raw_input, + message: is_allowed ? "" : "Tool '" + tool_name + "' is not allowed" +} +``` + +**Note**: The `tyk.redact_pattern()` helper is designed for LLM message structures (JSON format) and will not work with tool responses. For tool responses, use direct string manipulation with the `text` module as shown above. + +--- + +--- + +## Filter Execution Order in Chat Sessions + +When a user sends a message in a chat session, filters are executed at multiple points in the pipeline: + +### **1. User Message Filters** (Before RAG) +**When**: After preprocessing, before RAG vector search +**Purpose**: Redact PII from user messages before they're used for vector similarity search +**Context**: `input.messages[0].role == "user"` + +```tengo +text := import("text") + +// Redact before RAG search +modified := text.replace(input.messages[0].content, "@", "[EMAIL]", -1) + +output := { + block: false, + payload: modified, + message: "" +} +``` + +**Impact**: The filtered/modified message is used for: +- ✅ RAG vector similarity search +- ✅ Subsequent LLM processing +- ✅ Chat history storage + +### **2. File Content Filters** (Before RAG) +**When**: When files are attached to messages +**Purpose**: Filter sensitive content from uploaded files before indexing +**Context**: `input.context.file_ref` contains the file reference + +### **3. Tool Response Filters** (After Tool Execution) +**When**: After a tool returns data, before sending to LLM +**Purpose**: Filter sensitive data from external API responses +**Context**: `input.messages[0].role == "tool"`, `input.context.tool_name` available + +--- + +## Best Practices + +1. **Always define `output`** - Scripts must set the output variable +2. **Use `tyk.redact_pattern` for LLM messages** - Handles vendor differences automatically +3. **Use `text` module for tool responses** - Direct string manipulation +4. **Use messages array for complex LLM modifications** - Gives you full control +5. **Provide clear block messages** - Help users understand policy violations +6. **Test across vendors** - OpenAI, Anthropic, and Google AI have different formats +7. **Check message roles** - Different logic for system, user, assistant, and tool messages +8. **Handle edge cases** - Empty arrays, missing fields, etc. +9. **Consider RAG impact** - Filters run before RAG, so redactions affect vector search + +--- + +## Migration from Legacy API + +**Old API** (still supported for backward compatibility): +```tengo +filter := func(payload) { + // Returns true/false for blocking only + return true +} +result := filter(payload) +``` + +**New Unified API** (recommended): +```tengo +// Rich input with messages, vendor info, context +output := { + block: false, // Blocking capability + payload: "", // Modification capability + messages: [], // Alternative modification approach + message: "" // Optional message +} +``` + +--- + +## Response Filters + +**Response Filters** enable administrators to block LLM responses based on content analysis, providing governance controls on what LLMs can say to end users. + +### Key Characteristics + +1. **Block-Only**: Response filters can only block responses, not modify them +2. **Works on LLM Responses**: Applied to LLM responses only (not tool responses) +3. **Streaming Support**: Execute per-chunk during streaming with access to accumulated buffer +4. **Script-Controlled**: Filter scripts decide when to evaluate based on buffer length + +### Configuration + +Enable response filtering by checking **"Is this a Response Filter?"** when creating or editing a filter in the admin UI. + +### Script API for Response Filters + +Response filters use the same `ScriptInput`/`ScriptOutput` structure as request filters, with additional response-specific fields: + +**Input Object (Non-Streaming):** +```tengo +input := { + raw_input: "The LLM's complete response text", + is_response: true, + is_chunk: false, + vendor_name: "openai", + model_name: "gpt-4", + is_chat: true, // or false for proxy + context: { + llm_id: 123, + session_id: "abc-123", // Only in chat context + user_id: 789, + chat_id: 456, + status_code: 200 + } +} +``` + +**Input Object (Streaming):** +```tengo +input := { + raw_input: "current chunk text", + is_response: true, + is_chunk: true, + chunk_index: 5, + current_buffer: "accumulated response text so far", + vendor_name: "openai", + model_name: "gpt-4", + is_chat: false, + context: { + llm_id: 123, + app_id: 456 + } +} +``` + +**Output Object:** +```tengo +output := { + block: false, // Set true to block/interrupt response + message: "" // Block reason (shown to user) +} +``` + +**Note**: The `payload` and `messages` fields in output are **ignored** for response filters. + +### Example 1: Block Refund Promises (Works for Streaming and Non-Streaming) + +```tengo +text := import("text") + +// Get response text - use buffer for streaming +response_text := input.is_chunk ? input.current_buffer : input.raw_input + +// Default output +output := { + block: false, + message: "" +} + +// For streaming: only evaluate once we have enough context +if !input.is_chunk || len(response_text) >= 100 { + // Check for forbidden patterns + forbidden := ["will refund", "issue a refund", "provide a refund", "get a refund"] + should_block := false + + for pattern in forbidden { + if text.contains(text.to_lower(response_text), pattern) { + should_block = true + break + } + } + + output = { + block: should_block, + message: should_block ? "Response blocked: Cannot promise refunds to customers" : "" + } +} +``` + +### Example 2: Block Harmful Content (Streaming with Buffer Check) + +```tengo +text := import("text") + +// Get response text (streaming-aware) +response_text := input.is_chunk ? input.current_buffer : input.raw_input + +// Default output +output := { + block: false, + message: "" +} + +// For streaming: script controls when to evaluate based on buffer length +// Wait until we have enough context before checking +if !input.is_chunk || len(response_text) >= 150 { + // Check for harmful instruction patterns + harmful := [ + "instructions for making", + "how to build a weapon", + "steps to create explosives" + ] + + is_harmful := false + for pattern in harmful { + if text.contains(text.to_lower(response_text), pattern) { + is_harmful = true + break + } + } + + output = { + block: is_harmful, + message: is_harmful ? "Response blocked: Potentially harmful content detected" : "" + } +} +``` + +### Example 3: Combined with LLM Analysis + +Use another LLM to analyze the response for policy violations: + +```tengo +tyk := import("tyk") +text := import("text") + +// Get response text +response_text := input.is_chunk ? input.current_buffer : input.raw_input + +// Default output +output := { + block: false, + message: "" +} + +// For streaming: only evaluate once buffer is large enough +if !input.is_chunk || len(response_text) >= 200 { + // Use fast LLM to check for policy violations + settings := { + model_name: "gpt-3.5-turbo", + temperature: 0.0, + max_tokens: 5, + system_prompt: "You are a content policy checker. Answer only 'yes' or 'no'." + } + + check_prompt := "Does this response promise a refund or commit to a specific action?: " + response_text + result := tyk.llm(1, settings, check_prompt) + + should_block := text.contains(text.to_lower(result), "yes") + + output = { + block: should_block, + message: should_block ? "Response blocked: LLM policy violation detected" : "" + } +} +``` + +### Response Filter Execution + +**Proxy (REST)**: +- Executes after response hooks (if any) +- Full response available in `input.raw_input` +- If blocked: Error returned to client instead of response + +**Proxy (Streaming)**: +- Executes on every chunk +- Access to both `raw_input` (current chunk) and `current_buffer` (accumulated text) +- If blocked: Streaming stops, error sent to client + +**Chat (Non-Streaming)**: +- Executes before adding to chat history +- If blocked: Error published to queue instead of response + +**Chat (Streaming)**: +- Executes on every chunk before publishing to queue +- If blocked: Error published, streaming callback returns error to stop further chunks + +### Best Practices for Response Filters + +1. **Buffer Management** (Streaming): Script controls evaluation timing based on `len(current_buffer)` +2. **Performance**: Keep filter logic lightweight (executes per-chunk in streaming) +3. **Clear Messages**: Provide helpful block messages for users +4. **Fail Open**: Filters fail open on script errors (response allowed through) +5. **LLM-Only**: Response filters only work on LLM responses, not tool responses +6. **Block-Only**: Cannot modify responses, only block them + +### When to Use Response Filters vs Request Filters + +**Use Request Filters When:** +- Preventing sensitive data from reaching the LLM +- Redacting PII before LLM processing +- Enforcing input policies + +**Use Response Filters When:** +- Preventing LLMs from making commitments (refunds, promises) +- Blocking harmful or inappropriate LLM outputs +- Enforcing corporate communication policies +- Detecting policy violations in LLM responses + +--- + +This unified filter system demonstrates how flexible and powerful Midsommar's scripting capabilities are, enabling administrators to enforce strict data governance policies while supporting advanced LLM and tool integration workflows with both blocking and modification capabilities. diff --git a/ai-management/ai-studio/installation/linux.mdx b/ai-management/ai-studio/installation/linux.mdx new file mode 100644 index 0000000000..03aeacef2f --- /dev/null +++ b/ai-management/ai-studio/installation/linux.mdx @@ -0,0 +1,531 @@ +--- +title: "Install Tyk AI Studio on Linux" +description: "Installation guide for the Tyk AI Studio on Linux" +keywords: "AI Studio, AI Management, Installation, Linux, DEB, RPM" +sidebarTitle: "Linux" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + + +This guide focuses on the Enterprise Edition of Tyk AI Studio. For the Community Edition, please refer to the [Tyk AI Studio GitHub repository](https://github.com/TykTechnologies/ai-studio/blob/main/docs/site/docs/deployment-packages.md). + +The Community Edition uses different packages (`tyk-ai-studio` and `tyk-microgateway`) and does not require a license key. + + +This guide covers installing Tyk AI Studio and the Edge Gateway on Linux servers using DEB or RPM packages. This is suitable for bare-metal servers, virtual machines, and cloud instances. In this architecture, AI Studio acts as the **control plane** (hub) and the Edge Gateway acts as the **data plane** (spoke), receiving configuration via gRPC. + +## Prerequisites + +- **OS**: Ubuntu/Debian (DEB) or RHEL 7-9/CentOS 7-9/Amazon Linux 2/2023 (RPM) +- **Architecture**: amd64 (x86_64), arm64 (aarch64), or s390x + + + RPM packages are published for EL 7, 8, 9 and Amazon Linux 2/2023. If you are running a different RPM-based distribution (e.g. CentOS Stream 10, Fedora), you can edit the repo file to use the closest supported version: + ```bash + # Example: use el/9 packages on CentOS Stream 10 + sudo sed -i 's/el\/10/el\/9/g' /etc/yum.repos.d/tyk_*.repo + sudo yum clean all + ``` + + +- **PostgreSQL 14+** (for AI Studio production use; SQLite is the default for development) +- **systemd** (for service management) +- **cosign** (for plugin signature verification in the Marketplace, [install instructions](https://docs.sigstore.dev/cosign/system_config/installation/)) +- Root or sudo access +- A Tyk AI License key (contact support@tyk.io or your account manager to obtain) + +## Generate Secrets + +Before configuring, generate the required secret keys: + +```bash +# Secret key for encryption (used for secrets management and SSO) +openssl rand -hex 16 + +# Encryption key for edge gateway communication (must be exactly 32 hex chars) +openssl rand -hex 16 + +# gRPC auth token (for hub-spoke communication) +openssl rand -hex 16 +``` + +Save these values, you will need them for both the AI Studio and Edge Gateway configuration. + +## Part 1: Install AI Studio + +### Add Package Repository + +**Debian / Ubuntu (DEB):** + +```bash +curl -s https://packagecloud.io/install/repositories/tyk/tyk-ee-unstable/script.deb.sh | sudo bash +``` + +**RHEL / CentOS / Amazon Linux (RPM):** + +```bash +curl -s https://packagecloud.io/install/repositories/tyk/tyk-ee-unstable/script.rpm.sh | sudo bash +``` + +### Install the Package + +**DEB:** + +```bash +sudo apt-get install tyk-ai-studio-ee +``` + +**RPM:** + +```bash +sudo yum install tyk-ai-studio-ee +``` + +The package installs: + +| Path | Description | +|------|-------------| +| `/opt/tyk-ai-studio/tyk-ai-studio` | Application binary | +| `/opt/tyk-ai-studio/tyk-ai-studio.conf.example` | Example configuration | +| `/etc/default/tyk-ai-studio` | Environment configuration (systemd) | +| `/lib/systemd/system/tyk-ai-studio.service` | Systemd service unit | + +The installer automatically creates a `tyk` user and group to run the service. + +### Configure AI Studio + +Edit the environment configuration file: + +```bash +# Debian/Ubuntu +sudo vi /etc/default/tyk-ai-studio + +# RHEL/CentOS (symlinked automatically by the installer) +sudo vi /etc/sysconfig/tyk-ai-studio +``` + +At minimum, set these values: + +```env Expandable +# Security — REQUIRED: replace with your generated secrets +TYK_AI_SECRET_KEY=your-generated-secret-key +MICROGATEWAY_ENCRYPTION_KEY=your-generated-encryption-key + +# Site URL — set to your server's hostname/IP +SITE_URL=http://your-server:8080 + +# Admin email +ADMIN_EMAIL=admin@example.com + +# Database — SQLite is default, use PostgreSQL for production +DATABASE_TYPE=postgres +DATABASE_URL=postgresql://user:password@localhost:5432/tyk_ai_studio?sslmode=require + +# Enterprise Edition — REQUIRED for EE installs +TYK_AI_LICENSE=your-license-key + +# Plugin Marketplace — set this to enable the marketplace. +# Without it, the Marketplace page will be empty. +AI_STUDIO_OCI_CACHE_DIR=/opt/tyk-ai-studio/cache/plugins + +# If NOT using HTTPS, enable dev mode to allow login over plain HTTP +# Without this, session cookies will be rejected by the browser +DEVMODE=true + +# Hub-Spoke: Control Plane Mode +GATEWAY_MODE=control +GRPC_PORT=50051 +GRPC_HOST=0.0.0.0 +GRPC_TLS_INSECURE=true +GRPC_AUTH_TOKEN=your-generated-grpc-token + +# Proxy URL — point to the Edge Gateway so AI requests are routed through it +PROXY_URL=http://your-edge-gateway-host:9091 + +# Portal display URLs — set these to your Edge Gateway's address so that +# the portal shows the correct endpoint URLs for tools and datasources +TOOL_DISPLAY_URL=http://your-edge-gateway-host:9091 +DATASOURCE_DISPLAY_URL=http://your-edge-gateway-host:9091 +``` + + +**Note:** `DEVMODE=true` disables secure cookie flags so that login works over HTTP. For production deployments with HTTPS, remove this setting. + + +### Start AI Studio + +```bash +sudo systemctl enable --now tyk-ai-studio +``` + +### Verify + +```bash +sudo systemctl status tyk-ai-studio +curl -s http://localhost:8080/health +``` + +View logs: + +```bash +sudo journalctl -u tyk-ai-studio -f +``` + +## Part 2: Install Edge Gateway + +The Edge Gateway is the data plane component for hub-spoke deployments. It connects to AI Studio via gRPC to receive configuration and processes AI requests locally. + +Skip this section if you're using AI Studio in standalone mode with its embedded gateway. + +### Add Package Repository + +If you haven't already added the Enterprise repository in Part 1, add it now: + +**Debian / Ubuntu (DEB):** + +```bash +curl -s https://packagecloud.io/install/repositories/tyk/tyk-ee-unstable/script.deb.sh | sudo bash +``` + +**RHEL / CentOS / Amazon Linux (RPM):** + +```bash +curl -s https://packagecloud.io/install/repositories/tyk/tyk-ee-unstable/script.rpm.sh | sudo bash +``` + +### Install the Package + +**DEB:** + +```bash +sudo apt-get install tyk-microgateway-ee +``` + +**RPM:** + +```bash +sudo yum install tyk-microgateway-ee +``` + +The package installs: + +| Path | Description | +|------|-------------| +| `/opt/tyk-microgateway/tyk-microgateway` | Server binary | +| `/opt/tyk-microgateway/mgw` | CLI tool | +| `/opt/tyk-microgateway/data/` | Data directory (SQLite database) | +| `/opt/tyk-microgateway/examples/analytics-pulse-config.yaml` | Analytics pulse example config | +| `/etc/default/tyk-microgateway` | Environment configuration (systemd) | +| `/lib/systemd/system/tyk-microgateway.service` | Systemd service unit | + +### Configure Edge Gateway + + +**Important:** If you are installing the Edge Gateway on the **same machine** as AI Studio, you **must** change the `PORT` to something other than `8080` (e.g. `9091`) to avoid a port conflict. AI Studio already listens on port 8080. + + +Edit the environment configuration file: + +```bash +# Debian/Ubuntu +sudo vi /etc/default/tyk-microgateway + +# RHEL/CentOS (symlinked automatically by the installer) +sudo vi /etc/sysconfig/tyk-microgateway +``` + +At minimum, set these values: + +```env Expandable +# Server port — change if running on the same host as AI Studio +PORT=9091 + +# Hub-Spoke: Edge Mode +GATEWAY_MODE=edge +CONTROL_ENDPOINT=your-studio-host:50051 +EDGE_ID=edge-1 +EDGE_NAMESPACE=default + +# Security — MUST MATCH AI Studio values +EDGE_AUTH_TOKEN=must-match-studio-GRPC_AUTH_TOKEN +ENCRYPTION_KEY=must-match-studio-MICROGATEWAY_ENCRYPTION_KEY + +# TLS — disable for initial setup, enable for production +EDGE_ALLOW_INSECURE=true +EDGE_TLS_ENABLED=false + +# Enterprise Edition — REQUIRED for EE installs +TYK_AI_LICENSE=your-license-key +``` + +### Configure Analytics Pulse + +To send analytics data from the Edge Gateway back to the AI Studio control plane, configure the analytics pulse plugin. + +Copy the example config: + +```bash +sudo cp /opt/tyk-microgateway/examples/analytics-pulse-config.yaml /opt/tyk-microgateway/analytics-pulse-config.yaml +sudo chown tyk:tyk /opt/tyk-microgateway/analytics-pulse-config.yaml +``` + +The default configuration is: + +```yaml Expandable +version: "1.0" + +data_collection_plugins: + - name: "analytics_pulse" + enabled: true + hook_types: ["analytics", "budget", "proxy_log"] + replace_database: false + priority: 100 + config: + interval_seconds: 10 + max_batch_size: 1000 + max_buffer_size: 10000 + compression_enabled: true + include_proxy_summaries: true + include_request_response_data: true + edge_retention_hours: 24 + excluded_vendors: ["mock", "test"] + timeout_seconds: 30 + max_retries: 3 + retry_interval_secs: 5 +``` + +Then enable it in `/etc/default/tyk-microgateway`: + +```env +PLUGINS_CONFIG_PATH=/opt/tyk-microgateway/analytics-pulse-config.yaml +``` + +### Start Edge Gateway + +```bash +sudo systemctl enable --now tyk-microgateway +``` + +### Verify + +```bash +sudo systemctl status tyk-microgateway +curl -s http://localhost:9091/health +``` + +View logs: + +```bash +sudo journalctl -u tyk-microgateway -f +``` + +Check the AI Studio logs for a successful edge gateway connection: + +```bash +sudo journalctl -u tyk-ai-studio | grep -i "edge\|grpc" +``` +## Database Setup + +### PostgreSQL for AI Studio + +AI Studio defaults to SQLite, which is fine for development. For production, use PostgreSQL: + +```bash +# Install PostgreSQL (Ubuntu/Debian) +sudo apt-get install postgresql + +# Create database and user +sudo -u postgres psql -c "CREATE USER tyk WITH PASSWORD 'your-db-password';" +sudo -u postgres psql -c "CREATE DATABASE tyk_ai_studio OWNER tyk;" +``` + +Then set in `/etc/default/tyk-ai-studio`: + +```env +DATABASE_TYPE=postgres +DATABASE_URL=postgresql://tyk:your-db-password@localhost:5432/tyk_ai_studio?sslmode=require +``` + +### SQLite for Edge Gateway + +The Edge Gateway uses SQLite by default, stored at `/opt/tyk-microgateway/data/microgateway.db`. No additional setup is required. + + +## First User Registration + +After starting the service, you need to create your first admin user: + +1. **Access the application**: Open your browser and navigate to `http://your-server:8080` +2. **Register with admin email**: Use the EXACT email address you set in the `ADMIN_EMAIL` environment variable +3. **Complete registration**: The first user who registers with the admin email will automatically become the administrator + + +**Important**: The first user registration must use the same email address specified in the `ADMIN_EMAIL` environment variable. This user will have full administrative privileges. + + +## Shared Secrets Reference + +When running AI Studio with an Edge Gateway, these values **must match**: + +| AI Studio Variable | Edge Gateway Variable | Purpose | +|---|---|---| +| `GRPC_AUTH_TOKEN` | `EDGE_AUTH_TOKEN` | Authenticates the gRPC connection | +| `MICROGATEWAY_ENCRYPTION_KEY` | `ENCRYPTION_KEY` | Encrypts synced configuration data | +| `TYK_AI_LICENSE` | `TYK_AI_LICENSE` | Enterprise license | + +## Firewall Configuration + +Open the following ports based on your deployment: + +| Port | Component | Required | +|------|-----------|----------| +| 8080 | AI Studio (API + UI) | Always | +| 9090 | AI Studio (embedded gateway) | Standalone mode | +| 50051 | AI Studio (gRPC control server) | Hub-spoke mode | +| 9091 | Edge Gateway (proxy API) | Hub-spoke mode (on edge gateway host) | + +Example using `ufw`: + +```bash +# AI Studio host +sudo ufw allow 8080/tcp +sudo ufw allow 9090/tcp +sudo ufw allow 50051/tcp # Only if using hub-spoke mode + +# Edge Gateway host (if separate machine) +sudo ufw allow 9091/tcp +``` + +Example using `firewalld`: + +```bash +# AI Studio host +sudo firewall-cmd --permanent --add-port=8080/tcp +sudo firewall-cmd --permanent --add-port=9090/tcp +sudo firewall-cmd --permanent --add-port=50051/tcp +sudo firewall-cmd --reload +``` + +## TLS Configuration (Production) + +For production deployments, enable TLS on the gRPC connection between AI Studio and the Edge Gateway. + +**AI Studio** (`/etc/default/tyk-ai-studio`): + +```env +GRPC_TLS_INSECURE=false +GRPC_TLS_CERT_PATH=/etc/tyk-ai-studio/tls/server-cert.pem +GRPC_TLS_KEY_PATH=/etc/tyk-ai-studio/tls/server-key.pem +``` + +**Edge Gateway** (`/etc/default/tyk-microgateway`): + +```env +EDGE_TLS_ENABLED=true +EDGE_ALLOW_INSECURE=false +# If using a private CA: +# EDGE_TLS_CA_PATH=/etc/tyk-microgateway/tls/ca-cert.pem +``` + +## Upgrading + +**DEB:** + +```bash +sudo apt-get update + +sudo apt-get upgrade tyk-ai-studio-ee +sudo apt-get upgrade tyk-microgateway-ee # if installed +``` + +**RPM:** + +```bash +sudo yum update tyk-ai-studio-ee +sudo yum update tyk-microgateway-ee # if installed +``` + + +**Note:** Package upgrades will **not** overwrite your configuration in `/etc/default/`. The services are automatically restarted after upgrade. + + +## Troubleshooting + + + + + +```bash +sudo journalctl -u tyk-ai-studio --no-pager -n 50 +```` + +**Common causes:** + +* Missing or invalid `TYK_AI_SECRET_KEY` +* Database connection failure (check `DATABASE_URL`) +* Port already in use + + + + + +The services run as the `tyk` user. Ensure data directories are owned correctly: + +```bash +sudo chown -R tyk:tyk /opt/tyk-ai-studio/ +sudo chown -R tyk:tyk /opt/tyk-microgateway/ +``` + + + + + +If SELinux is enforcing and blocking the service: + +```bash +sudo setsebool -P httpd_can_network_connect 1 + +# Or check audit log for specific denials: +sudo ausearch -m avc -ts recent +``` + + + + + +The Plugin Marketplace requires `AI_STUDIO_OCI_CACHE_DIR` to be set. Without it, the marketplace service does not start and no plugins will appear. + +Add this to `/etc/default/tyk-ai-studio`: + +```env +AI_STUDIO_OCI_CACHE_DIR=/opt/tyk-ai-studio/cache/plugins +``` + +Restart the service after making this change: + +```bash +sudo systemctl restart tyk-ai-studio +``` + + + + + +* Verify `CONTROL_ENDPOINT` points to the correct AI Studio host and gRPC port +* Verify `EDGE_AUTH_TOKEN` matches `GRPC_AUTH_TOKEN` exactly +* Verify `ENCRYPTION_KEY` matches `MICROGATEWAY_ENCRYPTION_KEY` exactly +* Check firewall rules allow traffic on the gRPC port (default `50051`) +* Check AI Studio logs: + +```bash +sudo journalctl -u tyk-ai-studio | grep grpc +``` + + + + diff --git a/ai-management/ai-studio/installation/nats.mdx b/ai-management/ai-studio/installation/nats.mdx new file mode 100644 index 0000000000..80d6aebe4b --- /dev/null +++ b/ai-management/ai-studio/installation/nats.mdx @@ -0,0 +1,370 @@ +--- +title: "NATS JetStream Configuration for Tyk AI Studio" +description: "How to configure NATS JetStream as the message queue backend for Tyk AI Studio?" +keywords: "AI Studio, AI Management, Installation, Setup" +sidebarTitle: "NATS JetStream" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +This guide covers configuring Tyk AI Studio to use NATS JetStream as the message queue backend for distributed deployments and high availability scenarios. + +## Overview + +Tyk AI Studio supports two queue implementations: +- **In-Memory Queue** (default): For single-instance deployments +- **NATS JetStream Queue**: For distributed, persistent message handling + +NATS JetStream provides: +- **Persistent Message Storage**: Messages survive server restarts +- **Distributed Architecture**: Scale across multiple instances +- **Automatic Reconnection**: Handle network disruptions gracefully +- **Message Deduplication**: Ensure exactly-once delivery +- **Authentication & Security**: Multiple authentication methods including JWT and TLS + +## Configuration Options + +Tyk AI Studio can be configured to use NATS JetStream by setting the appropriate environment variables. + +### Basic Configuration + +#### Enable NATS Queue + +Set the queue type to NATS in your environment configuration: + +```bash +QUEUE_TYPE=nats +NATS_URL=nats://your-nats-server:4222 +``` + +#### NATS Server Configuration + +```bash +# NATS Connection +NATS_URL=nats://localhost:4222 # NATS server URL + +# JetStream Settings +NATS_STORAGE_TYPE=file # Use persistent storage (file|memory) +NATS_RETENTION_POLICY=interest # Delete messages when consumed (interest|limits|workqueue) +NATS_MAX_AGE=2h # Maximum message age +NATS_MAX_BYTES=104857600 # Max stream size (100MB) +NATS_DURABLE_CONSUMER=true # Use durable consumers for restart recovery + +# Performance Tuning +NATS_ACK_WAIT=30s # Acknowledgment timeout +NATS_MAX_DELIVER=3 # Maximum delivery attempts +NATS_FETCH_TIMEOUT=5s # Message fetch timeout +NATS_RETRY_INTERVAL=1s # Retry interval for failed operations +NATS_MAX_RETRIES=3 # Maximum retry attempts +``` + + +### Advanced Configuration + +#### Custom Stream Configuration + +For advanced use cases, configure streams directly in NATS: + +```bash +# Create custom stream for chat messages +nats stream add CHAT_CUSTOM \ + --subjects "chat.sessions.*.chat_response" \ + --storage file \ + --retention interest \ + --max-age 4h \ + --max-bytes 500MB +``` + +#### High Availability Setup + +For production HA deployments: + +```bash +# Connect to NATS cluster +NATS_URL=nats://nats-1:4222,nats-2:4222,nats-3:4222 + +# Enable clustering support +NATS_DURABLE_CONSUMER=true +NATS_MAX_DELIVER=5 +``` + +This configuration ensures message delivery even if individual NATS servers fail. + +## Authentication Methods + +### 1. No Authentication (Development Only) + +```bash +NATS_URL=nats://localhost:4222 +``` + +**Use Case**: Local development, testing environments +**Security**: None - suitable only for development + +### 2. Username/Password Authentication + +```bash +NATS_URL=nats://localhost:4222 +NATS_USERNAME=chat_service +NATS_PASSWORD=secure_password_123 +``` + +**Use Case**: Simple deployments with basic security +**Security**: Basic authentication with shared credentials + +### 3. Token-Based Authentication + +```bash +NATS_URL=nats://localhost:4222 +NATS_TOKEN=your-secret-token-here +``` + +**Use Case**: Simple token-based access control +**Security**: Shared secret token authentication + +### 4. JWT/User Credentials Authentication (Recommended) + +```bash +NATS_URL=nats://localhost:4222 +NATS_CREDENTIALS_FILE=/etc/nats/user.creds +``` + +**Use Case**: Production deployments requiring fine-grained access control +**Security**: JWT-based authentication with user credentials +**Benefits**: +- Decentralized authentication +- Subject-level permissions +- Automatic token renewal +- Audit trails + +#### Creating User Credentials + +1. Generate user credentials with NATS CLI: +```bash +# Create account +nsc add account TykAIStudio + +# Create user for chat service +nsc add user chat_service --account TykAIStudio + +# Generate credentials file +nsc generate creds --account TykAIStudio --name chat_service > /etc/nats/user.creds +``` + +2. Configure permissions in account settings: +```bash +nsc edit user chat_service --allow-pub "chat.>" --allow-sub "chat.>" +``` + +### 5. NKey Authentication + +```bash +NATS_URL=nats://localhost:4222 +NATS_NKEY_FILE=/etc/nats/user.nkey +``` + +**Use Case**: Cryptographic authentication without JWT overhead +**Security**: Ed25519 key-based authentication + +#### Creating NKey + +```bash +# Generate NKey +nsc add user chat_service_nkey --account TykAIStudio --allow-pub "chat.>" --allow-sub "chat.>" +nk -gen user > /etc/nats/user.nkey +``` + +### 6. TLS Configuration + +#### Basic TLS (Server Authentication) + +```bash +NATS_URL=nats://your-secure-nats:4222 +NATS_TLS_ENABLED=true +``` + +#### Mutual TLS (Client + Server Authentication) + +```bash +NATS_URL=nats://your-secure-nats:4222 +NATS_TLS_ENABLED=true +NATS_TLS_CERT_FILE=/etc/ssl/certs/client-cert.pem +NATS_TLS_KEY_FILE=/etc/ssl/private/client-key.pem +NATS_TLS_CA_FILE=/etc/ssl/certs/ca-cert.pem +``` + +#### TLS Development Mode (Skip Verification) + +```bash +NATS_URL=nats://localhost:4222 +NATS_TLS_ENABLED=true +NATS_TLS_SKIP_VERIFY=true # Only for development! +``` + +### 7. Combined Authentication (Production Recommended) + +```bash +# TLS + JWT Authentication +NATS_URL=nats://secure-nats.production:4222 +NATS_TLS_ENABLED=true +NATS_CREDENTIALS_FILE=/etc/nats/production-user.creds +NATS_TLS_CA_FILE=/etc/ssl/certs/nats-ca.pem + +# Additional security settings +NATS_TLS_SKIP_VERIFY=false +NATS_MAX_DELIVER=3 +NATS_ACK_WAIT=30s +``` + +## Docker/Kubernetes Configuration + +### Docker Compose + +```yaml +version: '3.8' +services: + nats: + image: nats:latest + command: + - "--jetstream" + - "--store_dir=/data" + ports: + - "4222:4222" + volumes: + - nats_data:/data + + midsommar: + image: tyk/midsommar:latest + environment: + QUEUE_TYPE: "nats" + NATS_URL: "nats://nats:4222" + NATS_STORAGE_TYPE: "file" + NATS_RETENTION_POLICY: "interest" + depends_on: + - nats + +volumes: + nats_data: +``` + +### Kubernetes ConfigMap + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nats-config +data: + QUEUE_TYPE: "nats" + NATS_URL: "nats://nats-cluster:4222" + NATS_STORAGE_TYPE: "file" + NATS_RETENTION_POLICY: "interest" + NATS_MAX_AGE: "2h" + NATS_DURABLE_CONSUMER: "true" +``` + +### Kubernetes Secrets (for Authentication) + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: nats-auth +type: Opaque +data: + credentials: + username: + password: +``` + +## Migration from In-Memory + +To migrate from in-memory to NATS queue: + +1. **Deploy NATS Server**: Set up NATS with JetStream enabled +2. **Configure Authentication**: Set up appropriate auth method +3. **Update Configuration**: Change `QUEUE_TYPE=nats` +4. **Restart Application**: Deploy updated configuration +5. **Verify Operation**: Check logs for successful NATS connection + +The migration is seamless as the queue interface abstracts the implementation details. + +## Troubleshooting + + + + + +Monitor NATS connection status in application logs: + +```bash +kubectl logs -f deployment/midsommar | grep NATS +``` + +Expected log messages: + +``` +INFO NATS authentication configured with credentials file file=/etc/nats/user.creds +INFO NATS reconnected session_id=abc123 url=nats://nats:4222 +``` + + + + + +1. **Connection Failures** + +```bash +ERROR failed to connect to NATS: nats: no servers available for connection +``` + +**Solution**: Check NATS server is running and URL is correct + +2. **Authentication Failures** + +```bash +ERROR failed to connect to NATS: nats: Authorization Violation +``` + +**Solution**: Verify credentials file exists and has correct permissions + +3. **TLS Certificate Issues** + +```bash +ERROR failed to configure NATS authentication: failed to load TLS client certificate +``` + +**Solution**: Check certificate files exist and have correct permissions + +4. **Permission Errors** + +```bash +ERROR failed to create stream: insufficient permissions +``` + +**Solution**: Ensure user has pub/sub permissions for `chat.>` subjects + + + + + +For high-throughput deployments: + +```bash +# Increase buffer sizes +QUEUE_BUFFER_SIZE=1000 + +# Optimize NATS settings +NATS_MAX_BYTES=1048576000 # 1GB max stream size +NATS_MAX_AGE=24h # Longer retention +NATS_FETCH_TIMEOUT=10s # Longer fetch timeout +NATS_ACK_WAIT=60s # Longer ack timeout +``` + + + + \ No newline at end of file diff --git a/ai-management/ai-studio/installation/overview.mdx b/ai-management/ai-studio/installation/overview.mdx new file mode 100644 index 0000000000..a78b6113f0 --- /dev/null +++ b/ai-management/ai-studio/installation/overview.mdx @@ -0,0 +1,141 @@ +--- +title: "Installation Options for Tyk AI Studio" +description: "Explore the different installation options for Tyk AI Studio, including Docker, Kubernetes, and Linux." +keywords: "AI Studio, AI Management, Installation, Setup" +sidebarTitle: "Overview" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +## Deployment Models + +Tyk AI Studio supports two deployment modes: + +### Standalone + +Standalone mode operates as a single AI Studio instance with embedded gateway functionality. It is suitable for development, testing, and small teams. + +```mermaid +graph LR + Users[Users] --> Studio[AI Studio
Standalone] + Studio --> DB[(SQLite)] + Studio --> Providers[AI Providers] + + style Studio fill:#4A90E2 +``` + +**Characteristics**: +- Single instance deployment +- Built-in gateway functionality +- SQLite database (or PostgreSQL) +- No external dependencies + +### Hub and Spoke (Control + Edge) + +Control Plane with Edge Gateways uses AI Studio as the central control plane managing edge gateways for distributed request processing. This approach uses lightweight Edge Gateways instances that connect to the control plane. + +It is suitable for production, enterprise, and multi-region deployments. + +```mermaid +graph TB + subgraph "Control Plane" + Control[AI Studio
Control Mode] + ControlDB[(PostgreSQL)] + Control --> ControlDB + end + + subgraph "Edge Gateways" + Edge1[Edge Gateway 1] + Edge2[Edge Gateway 2] + EdgeN[Edge Gateway N] + end + + Control -.->|Config Sync| Edge1 + Control -.->|Config Sync| Edge2 + Control -.->|Config Sync| EdgeN + + Users1[Users Region 1] --> Edge1 + Users2[Users Region 2] --> Edge2 + UsersN[Users Region N] --> EdgeN + + Edge1 --> Providers[AI Providers] + Edge2 --> Providers + EdgeN --> Providers + + style Control fill:#4A90E2 + style Edge1 fill:#7ED321 + style Edge2 fill:#7ED321 + style EdgeN fill:#7ED321 +``` + +**Characteristics**: +- Centralized configuration management +- Distributed request processing +- Regional edge deployments +- High availability and fault tolerance +- Namespace-based multi-tenancy + +## Choosing Your Deployment + +| Scenario | Recommended Mode | Why | +|----------|-----------------|-----| +| Local development | Standalone | Simple, fast setup | +| Single office/location | Standalone | No distribution needed | +| Multiple regions | Hub-and-Spoke | Low latency for users | +| High availability | Hub-and-Spoke | Fault tolerance | +| Multi-tenant SaaS | Hub-and-Spoke | Namespace isolation | +| Compliance (data locality) | Hub-and-Spoke | Regional data processing | + +## Requirements + +**Tyk AI Studio** and **Edge Gateway** requires a persistent datastore for its operations. By default, SQLite is used, while PostgreSQL is recommended for production deployments. + + +### Required Components + +| Component | AI Studio (Hub) | Edge Gateway (Edge) | +|-----------|----------------|---------------------| +| **SQLite** | ✅ **Supported** (default) | ✅ **Supported** (default) | +| **PostgreSQL** | ✅ **Supported** | ✅ **Supported** | + + +### Optional Components + +**Tyk AI Studio** uses a message queue for chat interface. By default, an in-memory queue is used, which is suitable for development and single-instance deployments. + +For production and multi-instance deployments, you can configure AI Studio to use [NATS JetStream](/ai-management/ai-studio/installation/nats) as the message queue backend. + +## Recommended Installation: Docker + +For development, testing, and proof of concept purposes, we recommend using our Docker installation, which allows you to quickly spin up AI Studio on your local machine. + + + + + +Install with Docker + + + + +## Alternative Installation Methods + + + + + +Install on Kubernetes + + + + +Install on Linux + + + diff --git a/ai-management/ai-studio/llm-management.mdx b/ai-management/ai-studio/llm-management.mdx new file mode 100644 index 0000000000..2f8c3b4751 --- /dev/null +++ b/ai-management/ai-studio/llm-management.mdx @@ -0,0 +1,73 @@ +--- +title: "LLM Management" +description: "How to manage Large Language Models (LLMs) in Tyk AI Studio, including configuration, pricing, and budgeting." +keywords: "AI Studio, AI Management, LLM Management" +sidebarTitle: "LLMs" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio provides a centralized system for managing Large Language Model (LLM) providers, models, associated costs, and usage budgets. This allows administrators to control which models are available, how they are used, and track associated expenses. + +### Use cases + +- **Centralized AI Access Control:** Manage all your organization's LLM connections (OpenAI, Anthropic, etc.) in one place, controlling which teams and applications can access specific models. +- **Cost Management and Budgeting:** Set monthly spending limits globally per LLM or per application to prevent unexpected AI costs and track usage against budgets. +- **Data Privacy Enforcement:** Assign privacy levels to LLMs to ensure sensitive data is only processed by approved, secure models, preventing data leaks to public LLMs. + +## LLM Provider + +The LLM Management system acts as the core registry for all AI models accessible through Tyk AI Studio. It bridges the gap between external AI providers and internal consumers (Applications and Chat). + +Key components of an LLM entity include: +- **Basic Info:** Name, descriptions, vendor, and active status. +- **Connection:** API credentials, endpoints, and the default model to use. +- **Access & Security:** Privacy scores, allowed model regex patterns, and namespace restrictions. +- **Budgeting:** Monthly spending limits and budget cycle start dates. +- **Extensibility:** Support for attaching plugins that execute in the AI Gateway during the request lifecycle. + +The LLM entity is closely related to [Model Prices](/ai-management/ai-studio/model-prices) (which define the cost of using the LLM) and [Model Routers](/ai-management/ai-studio/model-router) (which can route requests across multiple LLMs). + +## Configuration + +Administrators can configure connections to different LLM providers through the UI or API. The configuration includes: + +- **Vendor Selection:** Choose from supported vendors such as `openai`, `anthropic`, `bedrock` (AWS Bedrock, available from v2.1.0), `vertex` (Google Vertex AI), `google_ai`, `huggingface`, `ollama`. +- **Authentication:** Securely provide API keys or credentials. +- **Model Restrictions:** Use regex patterns (e.g., `gpt-4.*`) to whitelist specific models from a vendor. +- **Privacy Levels:** Define how data is protected by controlling LLM access based on its sensitivity (0 lowest - 100 highest). +- **Budget Control:** Set a `MonthlyBudget` to limit total spending across all applications using this LLM configuration. +- **Body Logging Control:** From v2.1.0, enable **Disable Request/Response Body Logging** (`dont_log_bodies`) to clear request and response bodies before they are stored in proxy logs, chat logs, and analytics events. Use this for privacy or compliance-sensitive providers. It applies on both the embedded gateway and Edge Gateways, and defaults to off. +- **Plugins:** Attach plugins that execute in the AI Gateway when a request flows through the REST endpoint. + +## How to Create a LLM Provider + +You can create and manage LLM providers through the Tyk AI Studio Admin UI. + +1. **Navigate:** Go to the LLM providers section in the Admin UI. This view lists all configured LLMs, showing their Name, Short Description, Vendor, Privacy Level, and whether they are Proxied. +2. **Add New LLM:** Click "Add LLM provider". +3. **Fill in the LLM Details:** + - **Name:** A user-friendly name for this configuration (Required). + - **Short/Long Description:** Provide descriptions for the LLM. + - **Vendor:** Select the LLM vendor (e.g., OpenAI, Anthropic). + - **Default Model:** Specify the default model to use for this LLM (e.g., `gpt-4`, `claude-2`). + - **Monthly Budget:** Set a monthly budget limit (leave empty for no limit) and a Budget Start Date. + - **Privacy levels:** Set a privacy level (0 lowest - 100 highest). LLMs with lower privacy levels can't access higher-level data sources and tools. + - **Allowed Models:** Add regex patterns to whitelist specific models (e.g., `gpt-4.*` for all GPT-4 models). + - In the **Access Details** section, + - **API Endpoint:** Provide the endpoint URL (required if enabling an LLM for the AI Gateway, even for providers with default URLs). + - **API Key:** Securely provide the necessary authentication credentials. + - For **AWS Bedrock** (from v2.1.0), the form instead shows labelled credential fields: **AWS Access Key ID**, **AWS Secret Access Key**, and **AWS Session Token** (optional). These are stored in the LLM's `metadata` map rather than `APIKey`. Use `$SECRET/NAME` references in these fields to keep credentials encrypted at rest via the [Secrets](/ai-management/ai-studio/secrets) manager. + - In the **Portal Display Information** section, + - **Logo URL:** Configure the logo used in the Portal UI for end-users. + - **Enabled in proxy:** Needs to be set to active to use this LLM in proxy. For example, it is required to create an app that uses this LLM. + - **Disable Request/Response Body Logging:** From v2.1.0, when enabled, request and response bodies are not stored in proxy logs, chat logs, or analytics events for this LLM. + - **Plugins:** Select plugins to attach to this LLM. They execute in the order selected during the request lifecycle. + - **Available Namespaces:** Select which edge namespaces this configuration should be available to (leave empty for global availability). +4. **Save:** Save the configuration to make the LLM provider available. + + LLM Provider Config \ No newline at end of file diff --git a/ai-management/ai-studio/manage-edge-gateway.mdx b/ai-management/ai-studio/manage-edge-gateway.mdx new file mode 100644 index 0000000000..11c4e01bbe --- /dev/null +++ b/ai-management/ai-studio/manage-edge-gateway.mdx @@ -0,0 +1,93 @@ +--- +title: "Manage Edge Gateway for Tyk AI Studio" +description: "Understand how to manage and monitor Edge Gateways in Tyk AI Studio, including configuration synchronization and admin actions." +keywords: "AI Studio, AI Management, Edge Gateway, Configuration Sync" +sidebarTitle: "Manage Edge Gateway" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +## What is Edge Gateway Management? + +In a hub-and-spoke architecture, Tyk AI Studio acts as the central control plane (Hub), while distributed gateway proxy instances act as Edge Gateways (Spokes). Edge Gateway management provides administrators with the tools to monitor the health, connection status, and configuration synchronization of these distributed instances from a single interface. + +This centralized management ensures that all edge instances are running the correct policies, routing rules, and configurations, while allowing data processing to remain local to the edge. + +For more details on the data plane architecture and request processing, see the [Edge Gateway (Data Plane) Component](/ai-management/ai-studio/proxy) documentation. + +## How it works + +Tyk AI Studio uses a robust, checksum-based synchronization system to manage configurations across all connected Edge Gateways. + +```mermaid +sequenceDiagram + participant Admin as "Admin (UI)" + participant CP as "Control Plane (Hub)" + participant Edge as "Edge Gateway (Spoke)" + + Admin->>CP: Modifies Configuration (e.g., LLM, Filter) + CP->>CP: Generates new SHA-256 Checksum + Edge->>CP: Sends Heartbeat (with old checksum) + CP-->>Edge: Acknowledges (Status: Out of Sync) + Admin->>CP: Clicks "Push Configuration" + CP->>Edge: Sends gRPC Reload Signal + Edge->>CP: Fetches New Configuration Snapshot + Edge->>Edge: Updates Local SQLite & Reloads Cache + Edge->>CP: Sends Heartbeat (with new checksum) + CP-->>Admin: Updates UI Status to "In Sync" +``` + +1. **Checksum Generation:** Whenever a configuration change occurs on the control plane (e.g., updating an LLM configuration, modifying a filter, or changing a tool), a SHA-256 checksum is computed from the serialized configuration snapshot. +2. **Heartbeat Reporting:** Edge gateways periodically send heartbeats to the control plane via gRPC. Each heartbeat includes the checksum of the configuration currently loaded on that edge. +3. **Status Comparison:** The control plane compares the reported checksum against the expected checksum for that edge's namespace to determine its synchronization status. +4. **Pull-on-miss for Credentials:** To balance performance and security, access tokens are not pushed in the initial snapshot. Instead, edges use a pull-on-miss strategy: they request validation for unknown tokens on-demand and cache them locally. This allows admins to revoke access instantly without waiting for a full configuration push. + +## Edge Gateway Properties + +When viewing the Edge Gateways list (**Admin > Edge Gateways**), administrators can monitor several key properties for each instance: + +| Property | Description | +|--------|-------------| +| **Edge ID** | A unique identifier for the edge gateway instance. | +| **Namespace** | The isolated environment the edge belongs to (Enterprise feature). | +| **Connection** | The current network status based on heartbeats (`Connected`, `Disconnected`, or `Stale`). | +| **Config Sync** | Indicates if the edge has the latest configuration (`In Sync`, `Pending`, `Stale`, or `Unknown`). | +| **Version** | The software version and build hash of the running Edge Gateway. | +| **Last Heartbeat** | The time elapsed since the control plane last received a heartbeat from this edge. | + +Clicking on an individual Edge Gateway reveals additional details, such as the exact loaded and expected configuration checksums, session IDs, and custom metadata reported by the edge (e.g., region or environment). + +## Admin Actions + +Administrators have full control over the lifecycle and configuration of Edge Gateways through the AI Studio UI. + +### Pushing Configuration + +Configuration changes are not applied automatically. Administrators must explicitly push configurations to ensure they maintain control over deployment rollouts. + +When you click the **Push Configuration** button: +1. You can select the target scope: **All Namespaces** or a **Specific Namespace**. +2. The control plane generates a new configuration snapshot. +3. A reload signal is sent to the targeted edge gateways via gRPC. +4. The edges fetch the new configuration, update their local SQLite databases, and reload their in-memory caches. +5. The sync status updates automatically as edges report their new checksums in subsequent heartbeats. + +### Removing Edge Gateways + +If an edge gateway is decommissioned or needs to be reset, administrators can remove it from the control plane: +1. Open the three-dot menu (⋮) on the edge row or navigate to its detail view. +2. Select **Remove Entry**. +3. Confirm the removal. + +> **Note:** This action only removes the entry from the control plane database. If the edge gateway process is still running, it will automatically re-register on its next connection attempt. + +## Troubleshooting Synchronization + +If an Edge Gateway shows a `Pending` or `Stale` sync status, or if a checksum mismatch persists: +- **Check Connectivity:** Ensure the edge gateway is running and firewall rules allow gRPC traffic (default port 50051) to the control plane. +- **Wait for Heartbeat:** After a push, it may take a few seconds for the heartbeat cycle to complete and the UI to update. +- **Review Logs:** Check the edge gateway logs for configuration load errors or permission issues preventing it from fetching the latest snapshot. diff --git a/ai-management/ai-studio/model-prices.mdx b/ai-management/ai-studio/model-prices.mdx new file mode 100644 index 0000000000..92a565a763 --- /dev/null +++ b/ai-management/ai-studio/model-prices.mdx @@ -0,0 +1,76 @@ +--- +title: "Model Prices Management in Tyk AI Studio" +description: "How to manage Large Language Model (LLM) pricing in Tyk AI Studio, including setting costs for input/output tokens and cache usage." +keywords: "AI Studio, AI Management, Model Price" +sidebarTitle: "Model Prices" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Model Prices define the cost per million tokens for using different language models. This helps track usage costs, allowing you to manage and optimize expenses when interacting with different models. + +```mermaid +graph LR + A[LLM Request] --> B[Edge Gateway] + B --> C[LLM Vendor API] + C -->|Response + Token Usage| B + B --> D{Model Price Lookup} + D -->|Find Price by Model & Vendor| E[(Database: model_prices)] + E -->|Return Price Info| D + D --> F[Calculate Total Cost] + F -->|Save Cost & Tokens| G[(Database: llm_chat_records)] + G --> H[Analytics & Budgeting] +``` + + +### Use cases + +- **AI Gateway Tracking:** Ensures that token-based costs for API calls through the AI Gateway are accurately logged and monitored. +- **Chat Room Cost Analysis:** Tracks and evaluates expenses associated with user interactions in the Chat Room feature. +- **Budget Enforcement:** Provides the underlying cost data needed to enforce monthly budgets set on LLMs and Applications. + +## Model Prices + +The Model Prices system allows administrators to define the cost structure for specific Large Language Models (LLMs). The pricing information configured here is used by the Analytics system for cost tracking and billing purposes. + +Key components of a Model Price entity include: +- **Model Identification:** The exact name of the model (e.g., `claude-3.5-sonnet-20240620`) and the vendor providing it. +- **Token Costs:** The price charged per million input tokens and output tokens. +- **Cache Costs:** Optional pricing for tokens written to or read from the LLM's prompt cache. +- **Currency:** The currency in which the model's pricing is defined (e.g., USD). + +The Model Price entity is closely related to [LLM Management](/ai-management/ai-studio/llm-management), as the pricing defined here applies to the models configured in the LLM settings. + +## Configuration + +Administrators can configure pricing for specific models through the UI or API. The configuration includes: + +- **Model Name:** Must match the exact name used in client API calls or the LLM settings in the portal for correct mapping. +- **Vendor:** Selectable from pre-configured vendors in the portal. +- **Cost per Million Input Tokens:** The price charged per million input tokens sent to the LLM. +- **Cost per Million Output Tokens:** The price charged per million output tokens generated by the LLM. +- **Cost per Million Cache Write Tokens:** The price charged per million tokens written to the prompt cache (defaults to input token pricing if not set). +- **Cost per Million Cache Read Tokens:** The price charged per million cached tokens read (typically much lower than input costs). +- **Currency:** The currency in which the pricing is defined. + +## How to Create the Entity + +You can create and manage Model Prices through the Tyk AI Studio Admin UI. + +1. **Navigate:** Go to the Model Prices section in the Admin UI. This view lists all configured model prices, showing the Model Name, Vendor, Cost per Input Token, Cost per Output Token, Cost per Cache Write Token, Cost per Cache Read Token, and Currency. +2. **Add New Model Price:** Click the "+ ADD MODEL PRICE" button. +3. **Fill in the Model Price Details:** + - **Model Name:** The exact name of the model this price configuration applies to (Required). + - **Vendor:** Select the name of the LLM provider (e.g., Anthropic, OpenAI). + - **Cost per Million Input Tokens:** The price charged per million input tokens sent to the LLM (Required). + - **Cost per Million Output Tokens:** The price charged per million output tokens generated by the LLM (Required). + - **Cost per Million Cache Write Tokens:** The price charged per million tokens written to the prompt cache (Optional). + - **Cost per Million Cache Read Tokens:** The price charged per million cached tokens read (Optional). + - **Currency:** The currency in which the pricing is defined (e.g., USD) (Required). +4. **Save:** Click "Update Model Price" or "Create Model Price" to save the configuration. + + Model Price Config \ No newline at end of file diff --git a/ai-management/ai-studio/model-router.mdx b/ai-management/ai-studio/model-router.mdx new file mode 100644 index 0000000000..aced14522e --- /dev/null +++ b/ai-management/ai-studio/model-router.mdx @@ -0,0 +1,154 @@ +--- +title: "Tyk AI Studio Model Router" +description: "Overview of Tyk AI Studio's model routing capabilities" +keywords: "AI Studio, AI Management, Model Routing" +sidebarTitle: "Model Router" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +The **Model Router** is an Enterprise feature that provides intelligent request routing across multiple LLM vendors based on model name patterns. It enables organizations to create unified API endpoints that automatically route requests to the appropriate backend LLM based on configurable rules. + +### Use cases + +- **Multi-Vendor Failover:** Create a pool with multiple vendors for the same model pattern. If one vendor is unavailable or disabled, requests automatically go to other active vendors. +- **Cost Optimization:** Route requests to different vendors based on cost. Use weights to prefer cheaper providers while maintaining access to premium ones. +- **Model Abstraction:** Allow clients to request generic model names (e.g., `large-model`, `fast-model`) and map them to specific vendor models using model mappings. +- **A/B Testing:** Use weighted routing to gradually shift traffic between model versions or vendors. + +## Model Router + +The Model Router acts as a unified endpoint that routes to multiple LLM vendors. It is exposed at `/router/{slug}/v1/chat/completions` and provides an OpenAI-compatible interface. + +Key components of a Model Router entity include: + +### Router + +A **Router** is the top-level entity that defines a routing endpoint. Each router: + +- Has a unique **slug** that becomes part of the API endpoint URL +- Can be **active** or **inactive** +- Contains one or more **pools** for routing logic +- Supports **namespaces** for multi-tenant deployments + +Routers are exposed at `/router/{slug}/v1/chat/completions` and provide an OpenAI-compatible interface. + +### Pool + +A **Pool** groups vendors that handle specific model patterns. Each pool: + +- Has a **model pattern** using glob syntax (e.g., `claude-*`, `gpt-4*`, `*`) +- Defines a **selection algorithm** for choosing among vendors +- Has a **priority** value (higher priority pools are checked first) +- Contains one or more **vendors** + +When a request arrives, pools are checked in priority order. The first pool whose pattern matches the requested model name handles the request. + +### Vendor + +A **Vendor** represents an LLM configuration within a pool. Each vendor: + +- References an existing [LLM configuration](/ai-management/ai-studio/llm-management) +- Has a **weight** for weighted load balancing +- Can be **active** or **inactive** +- Contains optional **model mappings** for name translation + +### Model Mapping + +**Model Mappings** allow translating the requested model name to a different name when sending to a specific vendor. This is configured at the vendor level, enabling different translations for each backend. + +For example, if you want to route `gpt-4` requests to both OpenAI and Anthropic: +- OpenAI vendor: No mapping needed (uses `gpt-4` as-is) +- Anthropic vendor: Map `gpt-4` → `claude-3-opus-20240229` + +The Model Router entity is closely related to [LLM Management](/ai-management/ai-studio/llm-management), as the vendors within a pool reference the configured LLMs. + +### Selection Algorithms + +#### Round Robin + +The `round_robin` algorithm distributes requests evenly across active vendors in a pool. Each request goes to the next vendor in sequence. + +#### Weighted + +The `weighted` algorithm distributes requests based on vendor weights. A vendor with weight 3 receives three times as many requests as a vendor with weight 1. + +## Configuration + +Administrators can configure Model Routers through the UI or API. The configuration includes: + +- **Basic Information:** Name, Slug (used in the URL), Description, Active status, and Available Namespaces. +- **Model Pools:** Define the routing logic based on model patterns. + - **Model Pattern:** Glob pattern to match (e.g., `claude-*`). + - **Selection Algorithm:** Choose `round_robin` or `weighted`. + - **Priority:** Higher values are checked first. +- **Vendors:** Add vendors to a pool, referencing configured LLMs. + - **Weight:** Relative weight for load balancing. + - **Model Mappings:** Translate the requested model name to a target model name. + +## How to Create a Model Router + +You can create and manage Model Routers through the Tyk AI Studio Admin UI. + +1. **Navigate:** Go to the Model Routers section in the Admin UI. +2. **Add New Router:** Click "Create Model Router". +3. **Fill in the Basic Information:** + - **Name:** Human-readable name for the router (Required). + - **Slug:** URL-safe identifier used in the endpoint path `/router/{slug}/v1/chat/completions` (Required). + - **Description:** Optional description for the router. + - **Active:** Enable or disable the router. + - **Available Namespaces:** Select which edge namespaces this configuration should be available to (leave empty for global availability). +4. **Add Model Pools:** + - Click "Add Pool" to define routing logic. + - **Name:** Pool identifier. + - **Model Pattern:** Glob pattern to match (e.g., `claude-*`). + - **Selection Algorithm:** Choose `round_robin` or `weighted`. + - **Priority:** Set the priority (higher values are checked first). +5. **Add Vendors to Pools:** + - For each pool, click "Add Vendor". + - **LLM:** Select from your configured LLMs. + - **Weight:** Set the relative weight for load balancing. + - **Active:** Enable or disable this vendor. +6. **Add Model Mappings (Optional):** + - **Source Model:** The model name in the incoming request. + - **Target Model:** The model name to send to this vendor. +7. **Save:** Click "Create" to save the router. + +{/* *TODO: Add product screenshots of the Model Router list view and the Create Model Router form.* */} + +## Using the Model Router + +Once a router is active, you can send OpenAI-compatible requests to its endpoint: + +```bash +curl -X POST "https://your-host/router/{slug}/v1/chat/completions" \ + -H "Authorization: Bearer YOUR_APP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "claude-3-sonnet-20240229", + "messages": [ + {"role": "user", "content": "Hello!"} + ] + }' +``` + +The router will: +1. Match the model name against pool patterns (in priority order) +2. Select a vendor using the pool's selection algorithm +3. Apply any model mappings for the selected vendor +4. Forward the request to the vendor's LLM endpoint +5. Return the response to the client + +## Current Limitations + +- **AI Portal integration:** Model Routers are not yet integrated with the AI Portal. Users cannot browse or subscribe to routers through the portal interface. Routers are currently managed exclusively through the Admin UI and API. + +## Enterprise Feature + +Model Router is an Enterprise Edition feature. Attempting to use Model Router endpoints without an Enterprise license will return a `402 Payment Required` error. + +To enable Model Router functionality, ensure your deployment has a valid Enterprise license configured. \ No newline at end of file diff --git a/ai-management/ai-studio/notifications.mdx b/ai-management/ai-studio/notifications.mdx new file mode 100644 index 0000000000..585fdb94ae --- /dev/null +++ b/ai-management/ai-studio/notifications.mdx @@ -0,0 +1,101 @@ +--- +title: "Notification in Tyk AI Studio" +description: "How to configure notifications in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Notifications" +sidebarTitle: "Notifications" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio includes a centralized Notification System responsible for generating and delivering alerts and messages to users and administrators based on specific system events. + +## Purpose + +The Notification System aims to: + +* **Inform Stakeholders:** Keep users and administrators aware of important events or required actions. +* **Enable Proactive Management:** Alert administrators to potential issues or thresholds being reached (e.g., budget limits). +* **Improve User Experience:** Provide timely feedback on asynchronous processes or user-related events. + +## Key Features + +* **Event-Driven:** Notifications are triggered by specific occurrences within the Tyk AI Studio platform. +* **Configurable Channels:** Supports multiple delivery methods, primarily: + * **Email:** Sending notifications to registered user email addresses. + * **In-App Notifications:** Displaying messages directly within the Tyk AI Studio UI. +* **User Preferences:** Allows users (and potentially administrators) to configure which notifications they wish to receive and via which channels (where applicable). +* **Centralized Logic:** Provides a single system for managing notification templates and delivery rules. + +## Common Notification Triggers + +Examples of events that might trigger notifications include: + +* **[Budget Control](/ai-management/ai-studio/budgeting):** + * Approaching spending limit threshold (e.g., 80% of budget). + * Reaching or exceeding spending limit. +* **[User Management](/ai-management/ai-studio/user-management):** + * New user registration/invitation. + * Password reset request. + * Changes in user roles or team memberships. +* **System Health & Errors:** + * Significant system errors or failures. + * Service degradation alerts. +* **Security Events:** + * Suspicious login activity (if monitored). + * Changes to critical security settings. + +## Configuration + +### SMTP Configuration (Email Notifications) + +To enable email notifications, configure the following environment variables: + +| Variable | Description | Required | +|----------|-------------|----------| +| `SMTP_SERVER` | SMTP server hostname (e.g., `smtp.gmail.com`) | Yes | +| `SMTP_PORT` | SMTP server port (e.g., `587` for TLS, `465` for SSL) | Yes | +| `SMTP_USER` | SMTP authentication username | Yes | +| `SMTP_PASS` | SMTP authentication password | Yes | +| `FROM_EMAIL` | Sender email address for outgoing notifications | Yes | + +**Example `.env` configuration:** + +```bash +SMTP_SERVER=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your-email@gmail.com +SMTP_PASS=your-app-password +FROM_EMAIL=noreply@your-domain.com +``` + +> **Note:** If SMTP is not configured, email notifications will be skipped silently, but in-app notifications will still work. + +### System-Level Settings (Admin) + +Administrators configure core notification settings: +* SMTP server details (via environment variables above) +* Default notification templates +* Enabling/disabling specific system-wide notification types + +### User-Level Settings + +Users can manage their notification preferences in their profile settings: +* Opt-in/opt-out of specific notification categories +* Choose preferred delivery channels (e.g., receive budget alerts via email) + + Notification Prefs UI + +## Integration + +The Notification System integrates with various other Tyk AI Studio components that generate relevant events, including: + +* Budget Control System +* User Management System +* Analytics System (potentially for performance alerts) +* Proxy/Gateway (for error or security event alerts) + +This system ensures timely communication, helping users and administrators stay informed about the status and activity within the Tyk AI Studio platform. diff --git a/ai-management/ai-studio/overview.mdx b/ai-management/ai-studio/overview.mdx new file mode 100644 index 0000000000..a26ea798d4 --- /dev/null +++ b/ai-management/ai-studio/overview.mdx @@ -0,0 +1,153 @@ +--- +title: "Tyk AI Studio" +description: "AI Management for Platform Teams with Tyk AI Studio, a comprehensive platform for managing and deploying AI LLMs and chats" +keywords: "AI Management, Platform Teams, Tyk AI Studio" +sidebarTitle: "Overview" +--- + +import AIStudioCards from '/snippets/AIStudioCards.mdx'; +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + +Tyk AI Studio is a comprehensive platform that enables platform teams to manage and deploy AI applications with enterprise-grade governance, security, and control. + +## Key Features + + + +Tyk AI Studio enables platform teams to: +- Centralise access credentials to AI vendors, including commercial and in-house offerings +- Log and measure AI usage across the organization +- Build and release Chatbots for internal collaboration +- Ingest data into a knowledge corpus with siloed access settings +- Create AI functions as a service for common automations +- Script complex multi-modal assistants that intelligently select which AI vendor, model, and data set to use +- Implement role-based access control to democratise LLM access while maintaining security + + +## Licensing and Editions + +Tyk AI Studio is available in two editions: **Community Edition** (open source) and **Enterprise Edition**. + +### Community Edition +The Community Edition is free and open source. + +**How to get it:** +No license is required. You can get started immediately using Docker Compose: +```bash +git clone https://github.com/TykTechnologies/ai-studio.git +cd ai-studio/quickstart +docker compose -f ./ce/compose.yaml up -d +``` + +### Enterprise Edition +The Enterprise Edition includes advanced features designed for production deployments. It is ideal for organizations requiring cost control, governance, compliance, and enterprise SSO integration. + +**How to get it:** +A valid license key is required. + +Contact support@tyk.io or your account manager to request an AI Studio Enterprise license. Once you have a license, refer our [Installation Guide](/ai-management/ai-studio/installation/overview) for instructions on how to install and configure the Enterprise Edition. + +### Feature Comparison + +The following table compares the features available in the Community and Enterprise editions of Tyk AI Studio: + +| Feature | Community Edition | Enterprise Edition | +|---------|------------------|-------------------| +| **LLM Proxy Gateway** | ✅ | ✅ | +| **Chat Interface** | ✅ | ✅ | +| **Tool Integration** | ✅ | ✅ | +| **User Management & RBAC** | ✅ | ✅ | +| **Hub-and-Spoke Deployment** | ✅ | ✅ | +| **Cost Tracking & Analytics** | ✅ | ✅ | +| **Plugin System** ([Learn more](/ai-management/ai-studio/plugins/overview)) | ✅ | ✅ | +| **Budget Management** | ❌ | ✅ | +| **Budget Enforcement** | ❌ | ✅ | +| **Budget Alerts** | ❌ | ✅ | +| **Advanced SSO (SAML, OIDC)** | ❌ | ✅ | +| **Advanced RBAC** | ❌ | ✅ | +| **Audit Logging** | ❌ | ✅ | +| **Priority Support** | ❌ | ✅ | + +## Enterprise AI Challenges + +Organizations implementing AI face several key challenges: + +* **Shadow AI**: Unauthorized tools running without governance or oversight +* **Data privacy and compliance**: Meeting regulatory requirements while enabling innovation +* **Security and access control**: Implementing proper authentication and authorization +* **Cost management**: Controlling expenses from unmonitored AI usage + +## Integrate AI with Confidence + +Tyk AI Studio helps organizations harness AI's potential while ensuring proper governance, security, compliance, and control. Purpose-built for enterprises, this AI gateway and management solution enables seamless governance that overcomes the risks and challenges of AI adoption. + + + +## Solution Components + +Tyk AI Studio provides a comprehensive suite of capabilities to manage, govern, and interact with AI across your organization: + +### Centralized AI management + +Unify and control AI usage across your organization: +- Govern AI with role-based access control, rate limiting and audit logging +- Monitor usage, costs, budgets, and performance in real time +- Manage how LLMs are accessed and used, with an AI gateway as a single point of control +- Ensure compliance with global privacy regulations through customizable data flow management + +### AI Gateway + +Seamlessly connect to AI tools and models: +- Proxy to large language models (LLMs) and integrate custom data models and tools +- Use the [AI Gateway](/ai-management/ai-studio/proxy) to enable secure, scalable access to AI services across teams +- Track usage statistics, cost breakdowns, and tool utilization to optimize resources + +### AI Portal + +Empower developers with a curated AI service catalog: +- Simplify access to AI tools and services through a unified portal +- Enable seamless integration with internal systems and external workflows +- Accelerate innovation by providing developers with the tools they need to build faster + +### AI Chat + +Bring AI-powered collaboration to every user: +- Deliver intuitive chat interfaces for direct interaction with AI tools and data sources +- Enable teams to access AI-driven insights through a unified, secure chat experience +- Foster collaboration and innovation across your organization +## Benefits + +Tyk AI Studio empowers organizations to adopt AI securely and efficiently, delivering: + +- **Centralized governance and control:** Consistency at the core of your business for enhanced security, compliance, troubleshooting and auditing +- **Strengthened security:** Peace of mind from strict access controls, secure interactions and region-specific compliance +- **Simplified workflows:** Reduced complexity and enhanced efficiency supporting developers and less technical users to work with multiple LLMs and tools +- **Trusted data privacy:** Rigorous compliance with data protection standards, reducing risk of reputational, operational and financial damage +- **Seamless integration:** Enhanced workflows in customer support, development, and marketing with trusted AI tools +- **Cost optimization:** Control over expenses and accountability, enabling smarter budgets + +## Use Cases + +Proxying LLM traffic through the AI Gateway delivers control, visibility, and scalability across various scenarios: + +- **Interact with your APIs:** Connect your API management to enable API interaction for wider teams +- **Banking and financial services:** Ensure only anonymized customer data is sent to LLMs, tracking usage by department to manage costs +- **Software development:** Leverage AI for code suggestions and issue tracking in Jira +- **Data governance:** Audit and secure AI interactions to meet regulatory standards +- **Healthcare:** Route LLM traffic through an AI gateway to comply with HIPAA, protecting patient data while enabling AI-driven insights +- **E-commerce:** Integrate LLMs with product catalogs, allowing employees to query inventory or sales data through a chat interface + +## MCP servers in AI Studio + +AI Studio provides comprehensive [MCP (Model Context Protocol) capabilities](/ai-management/mcps/overview#mcp-for-enterprise-use) including: + +- **Remote MCP catalogues and server support** – Expose internal APIs and tools to AI assistants securely without requiring local installations +- **Secure local MCP server deployment** – Deploy MCP servers within controlled environments, integrated with Tyk AI Gateway for monitoring and governance +- **Ready-to-use MCP integrations** – Including API to MCP conversion, Dashboard API access, and searchable documentation access + +For more details about Model Context Protocol (MCP) integration, please visit the [Tyk MCPs overview](/ai-management/mcps/overview) page. + +
+ + + diff --git a/ai-management/ai-studio/plugins/best-practices.mdx b/ai-management/ai-studio/plugins/best-practices.mdx new file mode 100644 index 0000000000..029ca164f4 --- /dev/null +++ b/ai-management/ai-studio/plugins/best-practices.mdx @@ -0,0 +1,881 @@ +--- +title: "Tyk AI Studio Plugin Best Practices" +description: "Production-ready patterns, performance optimization, and security guidelines for Tyk AI Studio plugins using the Unified Plugin SDK." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Best Practices" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph TD + A[Best Practices] --> B[Security] + A --> C[Performance] + A --> D[Production Readiness] +``` + +Production-ready patterns, performance optimization, and security guidelines for Tyk AI Studio plugins using the **Unified Plugin SDK**. + +## Architecture + +### Single Responsibility + +Each plugin should have a clear, focused purpose: + +```go +// Good: Focused on one concern +type RateLimiterPlugin struct { + plugin_sdk.BasePlugin +} + +// Avoid: Doing too many unrelated things +type EverythingPlugin struct { + plugin_sdk.BasePlugin + // rate limiting + auth + logging + analytics + ... +} +``` + +**When to combine capabilities**: +- ✅ UI + PostAuth for rate limiting dashboard +- ✅ PostAuth + Response for request/response correlation +- ✅ Object Hooks + UI for approval workflows +- ❌ Unrelated features that could be separate plugins + +### BasePlugin Usage + +Always use `BasePlugin` for consistent lifecycle management: + +```go Expandable +type MyPlugin struct { + plugin_sdk.BasePlugin + config *Config + client *http.Client +} + +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-plugin", + "1.0.0", + "Clear description of what it does", + ), + } +} +``` + +### Configuration Management + +Parse configuration in `Initialize()`, validate early: + +```go Expandable +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Extract broker ID for Service API + if brokerIDStr, ok := config["_service_broker_id"]; ok { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + } + + // Validate required configuration + apiKey, ok := config["api_key"] + if !ok || apiKey == "" { + return fmt.Errorf("api_key is required") + } + + // Parse optional configuration with defaults + timeout := 30 + if timeoutStr, ok := config["timeout"]; ok { + if t, err := strconv.Atoi(timeoutStr); err == nil { + timeout = t + } + } + + // Initialize resources + p.config = &Config{ + APIKey: apiKey, + Timeout: time.Duration(timeout) * time.Second, + } + + p.client = &http.Client{ + Timeout: p.config.Timeout, + } + + ctx.Services.Logger().Info("Plugin initialized", + "timeout", timeout, + ) + + return nil +} +``` + +## Error Handling + +### Fail Fast, Fail Clearly + +Return errors early with descriptive messages: + +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Validate input + if req.Method != "POST" { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Only POST requests are allowed", + }, nil + } + + // Check external dependency + status, err := p.checkExternalService(ctx) + if err != nil { + ctx.Services.Logger().Error("External service check failed", + "error", err, + "app_id", ctx.AppID, + ) + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Service temporarily unavailable", + }, nil + } + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### Log Errors with Context + +Always include relevant context in error logs: + +```go +ctx.Services.Logger().Error("Failed to validate request", + "error", err, + "app_id", ctx.AppID, + "user_id", ctx.UserID, + "path", req.Path, + "method", req.Method, +) +``` + +### Graceful Degradation + +Don't fail hard if non-critical operations fail: + +```go +// Bad: Plugin fails if KV write fails +err := ctx.Services.KV().Write(ctx, "cache", data) +if err != nil { + return nil, err // Blocks entire request! +} + +// Good: Log and continue +err := ctx.Services.KV().Write(ctx, "cache", data) +if err != nil { + ctx.Services.Logger().Warn("Failed to cache data", "error", err) + // Continue processing +} +``` + +## Performance + +### Minimize Blocking Operations + +Avoid blocking in request path: + +```go Expandable +// Bad: Synchronous external call in request path +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // This blocks the request! + result, err := p.callSlowExternalAPI(req.Body) + return &pb.PluginResponse{Modified: false}, nil +} + +// Good: Async processing for non-critical operations +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Fire and forget for analytics + go func() { + p.trackRequest(req) + }() + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### Use Connection Pooling + +Reuse HTTP clients and database connections. The SDK provides a `DefaultHTTPClient()` with sensible defaults (30s timeout, connection pooling, TLS handshake timeout): + +```go +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin("my-plugin", "1.0.0", "desc"), + // Use the SDK default client with connection pooling + httpClient: plugin_sdk.DefaultHTTPClient(), + } +} +``` + +You can also configure your own client if you need different settings: + +```go +httpClient: &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + }, +} +``` + +**Important**: Always use `http.NewRequestWithContext(ctx, ...)` to respect context cancellation and timeouts. Never use the default `http.Client{}` with zero timeout in production plugins. + +### Cache Frequently Accessed Data + +Use KV storage or in-memory caching: + +```go Expandable +type MyPlugin struct { + plugin_sdk.BasePlugin + cache sync.Map // In-memory cache + cacheTime time.Duration +} + +func (p *MyPlugin) getConfig(ctx plugin_sdk.Context, appID uint32) (*AppConfig, error) { + // Check memory cache first + key := fmt.Sprintf("config:%d", appID) + if val, ok := p.cache.Load(key); ok { + cached := val.(*cachedConfig) + if time.Since(cached.timestamp) < p.cacheTime { + return cached.config, nil + } + } + + // Check KV storage + data, err := ctx.Services.KV().Read(ctx, key) + if err == nil { + var config AppConfig + json.Unmarshal(data, &config) + + // Update memory cache + p.cache.Store(key, &cachedConfig{ + config: &config, + timestamp: time.Now(), + }) + + return &config, nil + } + + // Fetch from Service API (slowest) + app, err := ctx.Services.Studio().GetApp(ctx, appID) + // ... cache and return +} +``` + +### Batch Operations + +Batch external calls when possible: + +```go +// Bad: N+1 queries +for _, appID := range appIDs { + app, _ := ctx.Services.Studio().GetApp(ctx, appID) + // process app +} + +// Good: Batch fetch +apps, _ := ctx.Services.Studio().ListApps(ctx, 1, 100) +``` + +## Security + +### Input Validation + +Always validate and sanitize inputs: + +```go Expandable +func (p *MyPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + // Validate object JSON + var llm LLM + if err := json.Unmarshal([]byte(req.ObjectJson), &llm); err != nil { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "Invalid LLM configuration", + }, nil + } + + // Validate required fields + if llm.Name == "" { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "LLM name is required", + }, nil + } + + // Validate URL format + if llm.APIEndpoint != "" { + u, err := url.Parse(llm.APIEndpoint) + if err != nil || (u.Scheme != "https" && u.Scheme != "http") { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "Invalid API endpoint URL", + }, nil + } + } + + return &pb.ObjectHookResponse{AllowOperation: true}, nil +} +``` + +### Least Privilege Permissions + +Only request permissions you actually need: + +```json Expandable +// Bad: Requesting everything +{ + "permissions": { + "services": [ + "llms.read", "llms.write", "llms.proxy", + "tools.read", "tools.write", "tools.execute", + "apps.read", "apps.write", + "analytics.read", "kv.readwrite" + ] + } +} + +// Good: Only what's needed +{ + "permissions": { + "services": [ + "llms.read", // Read LLM configs + "kv.readwrite" // Store plugin state + ] + } +} +``` + +### Secrets Management + +Never hardcode secrets, use configuration: + +```go +// Bad: Hardcoded secrets +apiKey := "sk-1234567890abcdef" + +// Good: From configuration +apiKey, ok := config["api_key"] +if !ok { + return fmt.Errorf("api_key configuration required") +} + +// Better: From environment or secrets manager +apiKey := os.Getenv("PLUGIN_API_KEY") +``` + +### Sanitize Logs + +Don't log sensitive data: + +```go +// Bad: Logging sensitive data +ctx.Services.Logger().Info("Request received", + "headers", req.Headers, // May contain auth tokens! + "body", req.Body, // May contain PII! +) + +// Good: Log only safe metadata +ctx.Services.Logger().Info("Request received", + "app_id", ctx.AppID, + "method", req.Method, + "path", req.Path, + "content_length", len(req.Body), +) +``` + +## Observability + +### Structured Logging + +Use key-value pairs for searchable logs: + +```go Expandable +// Good structured logging +ctx.Services.Logger().Info("Request processed", + "app_id", ctx.AppID, + "user_id", ctx.UserID, + "duration_ms", time.Since(startTime).Milliseconds(), + "status", "success", +) + +ctx.Services.Logger().Error("External API call failed", + "error", err, + "endpoint", endpoint, + "status_code", statusCode, + "retry_count", retryCount, +) +``` + +### Request Tracing + +Include request IDs for correlation: + +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + requestID := generateRequestID() + + // Add to request for downstream + if req.Headers == nil { + req.Headers = make(map[string]string) + } + req.Headers["X-Request-ID"] = requestID + + ctx.Services.Logger().Info("Processing request", + "request_id", requestID, + "app_id", ctx.AppID, + ) + + // Use request ID in all logs for this request + defer func() { + ctx.Services.Logger().Info("Request completed", + "request_id", requestID, + "duration_ms", time.Since(startTime).Milliseconds(), + ) + }() + + return &pb.PluginResponse{ + Modified: true, + Request: req, + }, nil +} +``` + +### Metrics Collection + +Track plugin performance: + +```go Expandable +type MyPlugin struct { + plugin_sdk.BasePlugin + stats struct { + sync.RWMutex + requestCount int64 + errorCount int64 + totalDuration time.Duration + } +} + +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + startTime := time.Now() + defer func() { + duration := time.Since(startTime) + + p.stats.Lock() + p.stats.requestCount++ + p.stats.totalDuration += duration + p.stats.Unlock() + + if duration > 100*time.Millisecond { + ctx.Services.Logger().Warn("Slow plugin execution", + "duration_ms", duration.Milliseconds(), + ) + } + }() + + // ... plugin logic +} + +// Expose metrics via RPC (for UI plugins) +func (p *MyPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + if method == "get_metrics" { + p.stats.RLock() + defer p.stats.RUnlock() + + return json.Marshal(map[string]interface{}{ + "request_count": p.stats.requestCount, + "error_count": p.stats.errorCount, + "avg_duration_ms": p.stats.totalDuration.Milliseconds() / p.stats.requestCount, + }) + } + return nil, fmt.Errorf("unknown method") +} +``` + +## Testing + +### Unit Tests + +Test plugin logic in isolation: + +```go Expandable +func TestValidation(t *testing.T) { + plugin := NewValidatorPlugin() + + tests := []struct { + name string + llm LLM + shouldAllow bool + reason string + }{ + { + name: "valid HTTPS endpoint", + llm: LLM{APIEndpoint: "https://api.example.com"}, + shouldAllow: true, + }, + { + name: "HTTP endpoint blocked", + llm: LLM{APIEndpoint: "http://api.example.com"}, + shouldAllow: false, + reason: "API endpoint must use HTTPS", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + llmJSON, _ := json.Marshal(tt.llm) + req := &pb.ObjectHookRequest{ + ObjectType: "llm", + ObjectJson: string(llmJSON), + } + + resp, err := plugin.HandleObjectHook(mockContext(), req) + assert.NoError(t, err) + assert.Equal(t, tt.shouldAllow, resp.AllowOperation) + if !tt.shouldAllow { + assert.Contains(t, resp.RejectionReason, tt.reason) + } + }) + } +} +``` + +### Integration Tests + +Test with Service API: + +```go Expandable +func TestServiceAPIIntegration(t *testing.T) { + // Requires test environment + if os.Getenv("INTEGRATION_TEST") == "" { + t.Skip("Skipping integration test") + } + + plugin := NewMyPlugin() + ctx := testContext() + + // Test KV operations + err := ctx.Services.KV().Write(ctx, "test-key", []byte("value")) + assert.NoError(t, err) + + data, err := ctx.Services.KV().Read(ctx, "test-key") + assert.NoError(t, err) + assert.Equal(t, "value", string(data)) +} +``` + +## Object Hooks Best Practices + +### Hook Priority + +Use priority to control execution order: + +- **0-10**: Critical validation (security, compliance) +- **11-50**: Business logic validation +- **51-100**: Enrichment and metadata + +```go +func (p *SecurityPlugin) GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) { + return []*pb.ObjectHookRegistration{ + { + ObjectType: "llm", + HookTypes: []string{"before_create", "before_update"}, + Priority: 5, // Run early for security checks + }, + }, nil +} +``` + +### Before vs After Hooks + +**Use `before_*` hooks for**: +- Validation that can block operations +- Required field checks +- Security policy enforcement +- Approval workflows + +**Use `after_*` hooks for**: +- Notifications +- Audit logging +- External system sync +- Non-blocking enrichment + +### Metadata Storage + +Use PluginMetadata for tracking: + +```go +return &pb.ObjectHookResponse{ + AllowOperation: true, + PluginMetadata: map[string]string{ + "validated_by": "security-plugin", + "validated_at": time.Now().Format(time.RFC3339), + "security_scan": "passed", + "risk_score": "low", + }, +}, nil +``` + +## Multi-Capability Patterns + +### Shared State + +Share data structures between capabilities: + +```go Expandable +type RateLimiterPlugin struct { + plugin_sdk.BasePlugin + limits sync.Map // app_id -> limit config +} + +// PostAuth uses limits +func (p *RateLimiterPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + limit, _ := p.limits.Load(ctx.AppID) + // Check limit +} + +// UI reads limits +func (p *RateLimiterPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + if method == "get_limits" { + var limits []LimitInfo + p.limits.Range(func(key, value interface{}) bool { + limits = append(limits, LimitInfo{ + AppID: key.(uint32), + Limit: value.(int), + }) + return true + }) + return json.Marshal(limits) + } +} +``` + +### State Persistence + +Use KV storage for durable state: + +```go Expandable +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Load state from KV on startup + data, err := ctx.Services.KV().Read(ctx, "plugin_state") + if err == nil { + json.Unmarshal(data, &p.state) + } + return nil +} + +func (p *MyPlugin) Shutdown(ctx plugin_sdk.Context) error { + // Save state to KV on shutdown + data, _ := json.Marshal(p.state) + ctx.Services.KV().Write(ctx, "plugin_state", data) + return nil +} +``` + +## Deployment + +### Version Management + +Use semantic versioning: + +```go +plugin_sdk.NewBasePlugin( + "my-plugin", + "1.2.3", // MAJOR.MINOR.PATCH + "Description", +) +``` + +- **MAJOR**: Breaking changes (incompatible API changes) +- **MINOR**: New features (backward-compatible) +- **PATCH**: Bug fixes (backward-compatible) + +### Resource Cleanup + +Always clean up in `Shutdown()`: + +```go Expandable +func (p *MyPlugin) Shutdown(ctx plugin_sdk.Context) error { + // Close database connections + if p.db != nil { + p.db.Close() + } + + // Close HTTP clients + if p.httpClient != nil { + p.httpClient.CloseIdleConnections() + } + + // Flush any pending operations + p.flushPendingData(ctx) + + ctx.Services.Logger().Info("Plugin shutdown complete") + return nil +} +``` + +### Health Checks + +Implement health check methods: + +```go Expandable +func (p *MyPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + if method == "health" { + status := map[string]interface{}{ + "status": "healthy", + "uptime": time.Since(p.startTime).Seconds(), + } + + // Check external dependencies + if err := p.checkDependencies(); err != nil { + status["status"] = "unhealthy" + status["error"] = err.Error() + } + + return json.Marshal(status) + } + // ... other methods +} +``` + +## Common Pitfalls + +### AUTH Plugins: App Linking Requirement + +The most common AUTH plugin pitfall is **not linking credentials to App objects**. A valid credential alone is insufficient—the system needs the App context for access control. + +**Why This Matters:** + +Apps provide the access control context that governs what authenticated requests can do: +- Policy enforcement (rate limits, quotas) +- Tool and datasource permissions +- LLM access restrictions +- Budget controls + +**Required Interface Methods:** + +AUTH plugins must implement all three methods of the `AuthHandler` interface: + +```go +type AuthHandler interface { + HandleAuth(ctx Context, req *pb.AuthRequest) (*pb.AuthResponse, error) + GetAppByCredential(ctx Context, credential string) (*pb.App, error) // Often forgotten! + GetUserByCredential(ctx Context, credential string) (*pb.User, error) +} +``` + +**Common Mistakes:** + +```go Expandable +// BAD: Only validates token, doesn't return App ID +func (p *MyPlugin) HandleAuth(ctx plugin_sdk.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) { + if isValidToken(req.Credential) { + return &pb.AuthResponse{ + Authenticated: true, + // Missing AppId and UserId! Request will fail. + }, nil + } + return &pb.AuthResponse{Authenticated: false}, nil +} + +// GOOD: Returns both App ID and User ID +func (p *MyPlugin) HandleAuth(ctx plugin_sdk.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) { + tokenConfig, valid := p.lookupToken(req.Credential) + if !valid { + return &pb.AuthResponse{Authenticated: false, ErrorMessage: "Invalid token"}, nil + } + return &pb.AuthResponse{ + Authenticated: true, + AppId: tokenConfig.AppID, // Links to access control + UserId: tokenConfig.UserID, // Links to identity + }, nil +} +``` + +**Validation Checklist:** + +- [ ] `HandleAuth` returns a valid `AppId` that exists in the database +- [ ] `HandleAuth` returns a valid `UserId` that exists in the database +- [ ] `GetAppByCredential` fetches the complete App object via Service API +- [ ] `GetUserByCredential` fetches the complete User object via Service API +- [ ] The App has the required permissions for the tools/LLMs the user needs + +See [Plugin SDK Reference - AuthHandler](/ai-management/ai-studio/plugins/sdk#2-authhandler) for complete documentation. + +### 1. Modifying Shared Data Without Locking + +```go Expandable +// Bad: Race condition +type MyPlugin struct { + counter int // Not thread-safe! +} + +// Good: Use sync primitives +type MyPlugin struct { + mu sync.Mutex + counter int +} + +func (p *MyPlugin) increment() { + p.mu.Lock() + defer p.mu.Unlock() + p.counter++ +} +``` + +### 2. Blocking in Defer + +```go +// Bad: Blocking operations in defer +defer func() { + p.sendAnalytics(data) // Blocks shutdown! +}() + +// Good: Use timeouts +defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + p.sendAnalyticsWithContext(ctx, data) +}() +``` + +### 3. Not Handling Context Cancellation + +```go +// Good: Respect context cancellation +func (p *MyPlugin) longRunningOperation(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + // Do work + } + } +} +``` + +### 4. Excessive Logging + +```go +// Bad: Log spam +for _, item := range items { + ctx.Services.Logger().Debug("Processing item", "item", item) +} + +// Good: Batch logging +ctx.Services.Logger().Info("Processing items", + "count", len(items), + "batch_id", batchID, +) +``` diff --git a/ai-management/ai-studio/plugins/custom-endpoints.mdx b/ai-management/ai-studio/plugins/custom-endpoints.mdx new file mode 100644 index 0000000000..cc431f0a0d --- /dev/null +++ b/ai-management/ai-studio/plugins/custom-endpoints.mdx @@ -0,0 +1,607 @@ +--- +title: "Tyk AI Studio Custom Endpoint Plugins" +description: "Learn how to register and serve arbitrary HTTP endpoints on the Edge Gateway using Custom Endpoint plugins for use cases like OAuth providers and webhooks." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Custom Endpoint" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph LR + A[Client] -->|HTTP Request| B[Edge Gateway] + B -->|gRPC| C[Plugin Custom Endpoint] + C -->|HTTP Response| B + B -->|HTTP Response| A +``` + +Custom Endpoint plugins allow you to **register and serve arbitrary HTTP endpoints** on the Edge Gateway under the `/plugins/{slug}/` URL namespace. This gives plugins full control over request handling, enabling use cases like OAuth identity providers, MCP proxy servers, webhook receivers, and custom protocol-specific APIs. + +## Overview + +Custom Endpoints provide plugins with the ability to: + +- **Serve custom HTTP APIs** alongside the standard LLM/Tool/Datasource proxy endpoints +- **Handle arbitrary URL paths** with pre-split path segments for easy routing +- **Stream responses** via Server-Sent Events (SSE) for protocols like MCP Streamable HTTP +- **Authenticate requests** using the gateway's existing token system, with full App context (including metadata) +- **Support any HTTP method** (GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD) + +### Working Example + +See [`examples/plugins/gateway/custom-echo-endpoint/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/custom-echo-endpoint) for a complete, working example that combines **CustomEndpointHandler** + **UIProvider** + **ConfigProvider**. It echoes request metadata and serves content configurable via the Studio admin UI. + +### URL Pattern + +All custom endpoints are mounted under: + +``` +/plugins/{slug}/{path...} +``` + +Where `{slug}` is declared in the plugin's configuration (the `slug` key in the config map), and `{path...}` is the sub-path handled by the plugin. + +### How It Works + +``` +```mermaid +sequenceDiagram + participant Client as HTTP Client + participant Gateway as Edge Gateway (Gin Router) + participant Plugin as Plugin Process + + Client->>Gateway: POST /plugins/my-mcp/mcp + Note over Gateway: 1. Match /plugins/*path catch-all route
2. Parse slug + relative path
3. Look up EndpointRoute
4. Validate auth & fetch App
5. Build EndpointRequest + Gateway->>Plugin: gRPC HandleEndpointRequest() + Note over Plugin: CustomEndpointHandler
Full control over response
Access to App metadata + Plugin-->>Gateway: EndpointResponse + Gateway-->>Client: HTTP Response +``` + +## Implementing Custom Endpoints + +### Step 1: Implement CustomEndpointHandler Interface + +```go +type CustomEndpointHandler interface { + Plugin + GetEndpointRegistrations() ([]*pb.EndpointRegistration, error) + HandleEndpointRequest(ctx Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error) + HandleEndpointRequestStream(ctx Context, req *pb.EndpointRequest, stream grpc.ServerStreamingServer[pb.EndpointResponseChunk]) error +} +``` + +### Step 2: Register Your Endpoints + +Declare which paths and HTTP methods your plugin handles: + +```go +func (p *MyPlugin) GetEndpointRegistrations() ([]*pb.EndpointRegistration, error) { + return []*pb.EndpointRegistration{ + { + Path: "/*", // Catch-all wildcard + Methods: []string{"GET", "POST", "DELETE"}, + Description: "MCP Streamable HTTP endpoint", + RequireAuth: true, // Gateway enforces auth + StreamResponse: true, // Use streaming RPC + }, + }, nil +} +``` + +**Registration Options:** + +| Field | Type | Description | +|-------|------|-------------| +| `path` | string | Relative path under the plugin slug. Use `/*` for catch-all. | +| `methods` | []string | HTTP methods to handle. Valid: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD. | +| `description` | string | Human-readable description of the endpoint. | +| `require_auth` | bool | If true, gateway validates the token and passes the full `App` object to the plugin. | +| `stream_response` | bool | If true, gateway uses `HandleEndpointRequestStream` (SSE). Otherwise uses `HandleEndpointRequest`. | +| `metadata` | map[string]string | Plugin-defined metadata. | + +### Step 3: Handle Requests + +#### Unary (Non-Streaming) Endpoints + +For standard request/response: + +```go +func (p *MyPlugin) HandleEndpointRequest(ctx plugin_sdk.Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error) { + // Route based on path segments + segments := req.PathSegments + + switch { + case len(segments) == 0: + return p.handleRoot(req) + case segments[0] == ".well-known" && len(segments) >= 2: + return p.handleWellKnown(segments[1], req) + case segments[0] == "users" && len(segments) >= 2: + return p.handleUser(segments[1], req) + default: + return &pb.EndpointResponse{ + StatusCode: 404, + Headers: map[string]string{"Content-Type": "application/json"}, + Body: []byte(`{"error": "not found"}`), + }, nil + } +} +``` + +#### Streaming (SSE) Endpoints + +For Server-Sent Events or MCP Streamable HTTP: + +```go +func (p *MyPlugin) HandleEndpointRequestStream( + ctx plugin_sdk.Context, + req *pb.EndpointRequest, + stream grpc.ServerStreamingServer[pb.EndpointResponseChunk], +) error { + // Send headers first + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_HEADERS, + StatusCode: 200, + Headers: map[string]string{ + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + "Connection": "keep-alive", + }, + }) + + // Send SSE data chunks — each is flushed immediately to the HTTP client + for i := 0; i < 5; i++ { + data := fmt.Sprintf("data: {\"count\": %d}\n\n", i) + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_BODY, + Data: []byte(data), + }) + time.Sleep(1 * time.Second) + } + + // Signal stream completion + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_DONE, + }) + + return nil +} +``` + +**Chunk Protocol:** HEADERS → BODY* → DONE + +| Chunk Type | When | Fields Used | +|------------|------|-------------| +| `HEADERS` | First chunk | `status_code`, `headers` | +| `BODY` | Zero or more times | `data` (raw bytes flushed to client) | +| `DONE` | Final chunk | — | +| `ERROR` | On failure | `error_message` | + +### Step 4: Configure the Plugin Slug and Register + +The plugin slug (used in the URL path) must be set explicitly in the plugin's config map. The slug determines the URL namespace: `/plugins/{slug}/...`. + +#### Manifest + +Declare `custom_endpoint` in the manifest's capabilities: + +```json +{ + "id": "com.example.my-plugin", + "name": "My Custom Endpoint", + "version": "1.0.0", + "capabilities": { + "hooks": ["custom_endpoint"], + "primary_hook": "custom_endpoint" + } +} +``` + +If the plugin also provides a Studio UI, include `"studio_ui"` in hooks: + +```json +"hooks": ["custom_endpoint", "studio_ui"] +``` + +#### Registration via AI Studio + +Register the plugin in AI Studio (Admin > Plugins) with: + +| Field | Value | +|-------|-------| +| **Hook type** | `custom_endpoint` | +| **Hook types** | `["custom_endpoint"]` (add `"studio_ui"` if plugin has UI) | +| **Config** | Must include `"slug"` key — e.g., `{"slug": "my-mcp"}` | + +The `slug` in the config determines the URL path on the gateway. For example, `{"slug": "my-mcp"}` makes endpoints available at `http://gateway:8081/plugins/my-mcp/...`. + +**Important:** The `slug` must be set in the config map. Without it, endpoints will not be registered and you'll see a warning in the gateway logs: +``` +Plugin has endpoint registrations but no 'slug' in config +``` + +#### Building and Deploying + +```bash +# Build the plugin +cd examples/plugins/gateway/custom-echo-endpoint +go build -o custom-echo-endpoint + +# Register with file:// for local development +# Command: file:///path/to/custom-echo-endpoint +# Config: {"slug": "custom-echo-endpoint", "custom_content": "Hello World"} +``` + +#### Gateway Loading + +Custom endpoint plugins are loaded automatically at gateway startup via the pre-warming system. The gateway: +1. Queries all active plugins with `custom_endpoint` hook type +2. Loads each plugin (starts the binary, initializes via gRPC) +3. Calls `GetEndpointRegistrations()` to discover endpoints +4. Registers routes using the `slug` from config + +After a control plane config sync, routes are refreshed automatically. + +## EndpointRequest Fields + +When a request arrives, the plugin receives a rich `EndpointRequest`: + +| Field | Type | Description | +|-------|------|-------------| +| `method` | string | HTTP method (GET, POST, etc.) | +| `path` | string | Full request path (`/plugins/my-mcp/users/123`) | +| `relative_path` | string | Path relative to plugin mount (`/users/123`) | +| `path_segments` | []string | Pre-split segments: `["users", "123"]` | +| `headers` | map[string]string | Request headers | +| `body` | bytes | Request body | +| `query_string` | string | Raw query string (`foo=bar&baz=1`) | +| `remote_addr` | string | Client IP address | +| `host` | string | Request Host header | +| `protocol` | string | `"http"` (future: `"websocket"`, `"sse"`) | +| `context` | PluginContext | Request ID, metadata | +| `authenticated` | bool | Whether request was authenticated | +| `app` | App | Full App object (when authenticated) | +| `scopes` | []string | Token scopes (when authenticated) | + +### Path Segments + +The `path_segments` field pre-splits the relative path for easy pattern matching: + +| Request URL | `relative_path` | `path_segments` | +|-------------|-----------------|-----------------| +| `/plugins/my-mcp/` | `/` | `[]` | +| `/plugins/my-mcp/mcp` | `/mcp` | `["mcp"]` | +| `/plugins/my-mcp/users/123/profile` | `/users/123/profile` | `["users", "123", "profile"]` | +| `/plugins/my-mcp/.well-known/openid-configuration` | `/.well-known/openid-configuration` | `[".well-known", "openid-configuration"]` | + +## Authentication and App Context + +When `require_auth: true`, the gateway: + +1. Extracts the token from `Authorization: Bearer ` header or `?token=` query param +2. Validates the token via the gateway's auth provider +3. Fetches the **full App object** linked to the token +4. Populates `EndpointRequest` with `authenticated=true`, `app`, and `scopes` + +The `App` object includes: + +| Field | Type | Description | +|-------|------|-------------| +| `id` | uint32 | App ID | +| `name` | string | App name | +| `description` | string | App description | +| `owner_email` | string | Owner email address | +| `is_active` | bool | Whether app is active | +| `monthly_budget` | double | Monthly budget limit | +| `rate_limit` | int32 | Rate limit (requests per minute) | +| `metadata` | map[string]string | Custom key-value metadata | + +### Access Control via App Metadata + +The recommended pattern for per-app access control is to store ACL rules in App metadata, which admins configure per-app: + +```go +func (p *MyPlugin) HandleEndpointRequest(ctx plugin_sdk.Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error) { + if !req.Authenticated || req.App == nil { + return &pb.EndpointResponse{StatusCode: 401, Body: []byte("Unauthorized")}, nil + } + + // Check custom ACL in App metadata + if req.App.Metadata["mcp_access"] != "allowed" { + return &pb.EndpointResponse{StatusCode: 403, Body: []byte("Forbidden")}, nil + } + + // Check allowed operations + allowedOps := req.App.Metadata["allowed_operations"] + if allowedOps != "" && !strings.Contains(allowedOps, req.Method) { + return &pb.EndpointResponse{StatusCode: 405, Body: []byte("Method not allowed for this app")}, nil + } + + // Access granted — handle request + return p.processRequest(req) +} +``` + +## Route Matching + +The gateway matches routes in this order: + +1. **Exact match** — `GET:/plugins/my-oauth/.well-known/openid-configuration` +2. **Wildcard catch-all** — `GET:/plugins/my-oauth/*` + +**Recommended:** Register a single `/*` catch-all and handle routing internally using `path_segments`. This is the simplest and most flexible approach. + +A plugin can also register multiple specific paths: + +```go +func (p *MyPlugin) GetEndpointRegistrations() ([]*pb.EndpointRegistration, error) { + return []*pb.EndpointRegistration{ + { + Path: "/.well-known/openid-configuration", + Methods: []string{"GET"}, + Description: "OpenID Connect discovery", + }, + { + Path: "/token", + Methods: []string{"POST"}, + Description: "Token endpoint", + RequireAuth: false, + }, + { + Path: "/userinfo", + Methods: []string{"GET"}, + Description: "UserInfo endpoint", + RequireAuth: true, + }, + }, nil +} +``` + +## MCP Streamable HTTP Support + +Custom endpoints are designed to support MCP (Model Context Protocol) Streamable HTTP out of the box. + +### MCP Protocol Summary + +MCP Streamable HTTP uses a single endpoint with: +- **POST**: Client sends JSON-RPC messages; server responds with `application/json` or `text/event-stream` +- **GET**: Client opens an inbound SSE stream for server-initiated messages +- **DELETE**: Client terminates the session +- Session tracking via `Mcp-Session-Id` header + +### MCP Proxy Plugin Pattern + +```go +type MCPProxyPlugin struct { + plugin_sdk.BasePlugin + sessions sync.Map // sessionID → session state +} + +func (p *MCPProxyPlugin) GetEndpointRegistrations() ([]*pb.EndpointRegistration, error) { + return []*pb.EndpointRegistration{ + { + Path: "/*", + Methods: []string{"POST", "GET", "DELETE"}, + Description: "MCP Streamable HTTP proxy", + RequireAuth: true, + StreamResponse: true, // Use streaming for all requests + }, + }, nil +} + +func (p *MCPProxyPlugin) HandleEndpointRequestStream( + ctx plugin_sdk.Context, + req *pb.EndpointRequest, + stream grpc.ServerStreamingServer[pb.EndpointResponseChunk], +) error { + switch req.Method { + case "POST": + return p.handleMCPPost(ctx, req, stream) + case "GET": + return p.handleMCPGet(ctx, req, stream) + case "DELETE": + return p.handleMCPDelete(ctx, req, stream) + default: + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_HEADERS, + StatusCode: 405, + }) + stream.Send(&pb.EndpointResponseChunk{Type: pb.EndpointResponseChunk_DONE}) + return nil + } +} + +func (p *MCPProxyPlugin) handleMCPPost(ctx plugin_sdk.Context, req *pb.EndpointRequest, stream grpc.ServerStreamingServer[pb.EndpointResponseChunk]) error { + // Parse JSON-RPC request + var rpcReq map[string]interface{} + json.Unmarshal(req.Body, &rpcReq) + + // Check if client accepts SSE + acceptsSSE := strings.Contains(req.Headers["Accept"], "text/event-stream") + + if acceptsSSE { + // Stream response as SSE + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_HEADERS, + StatusCode: 200, + Headers: map[string]string{ + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + }, + }) + + // Send SSE events... + for _, event := range p.processRPCRequest(rpcReq) { + data := fmt.Sprintf("data: %s\n\n", event) + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_BODY, + Data: []byte(data), + }) + } + } else { + // Single JSON response + result := p.processRPCRequestSync(rpcReq) + body, _ := json.Marshal(result) + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_HEADERS, + StatusCode: 200, + Headers: map[string]string{"Content-Type": "application/json"}, + }) + stream.Send(&pb.EndpointResponseChunk{ + Type: pb.EndpointResponseChunk_BODY, + Data: body, + }) + } + + stream.Send(&pb.EndpointResponseChunk{Type: pb.EndpointResponseChunk_DONE}) + return nil +} +``` + +The gateway is a transparent pipe — all MCP protocol logic (JSON-RPC, sessions, resumability) lives in the plugin. + +## Complete Example: Webhook Receiver + +```go +package main + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +type WebhookPlugin struct { + plugin_sdk.BasePlugin + secret string +} + +func NewWebhookPlugin() *WebhookPlugin { + return &WebhookPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin("webhook-receiver", "1.0.0", "Receives and validates webhooks"), + } +} + +func (p *WebhookPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + p.secret = config["webhook_secret"] + if p.secret == "" { + return fmt.Errorf("webhook_secret is required") + } + ctx.Services.Logger().Info("Webhook receiver initialized") + return nil +} + +func (p *WebhookPlugin) GetEndpointRegistrations() ([]*pb.EndpointRegistration, error) { + return []*pb.EndpointRegistration{ + { + Path: "/*", + Methods: []string{"POST"}, + Description: "Webhook receiver endpoint", + RequireAuth: false, // Webhooks use their own auth (HMAC signature) + }, + }, nil +} + +func (p *WebhookPlugin) HandleEndpointRequest(ctx plugin_sdk.Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error) { + // Validate HMAC signature + signature := req.Headers["X-Webhook-Signature"] + if !p.validateSignature(req.Body, signature) { + return &pb.EndpointResponse{ + StatusCode: 401, + Headers: map[string]string{"Content-Type": "application/json"}, + Body: []byte(`{"error": "invalid signature"}`), + }, nil + } + + // Route by path segments + segments := req.PathSegments + if len(segments) == 0 { + return &pb.EndpointResponse{StatusCode: 400, Body: []byte(`{"error": "missing event type"}`)}, nil + } + + eventType := segments[0] + ctx.Services.Logger().Info("Webhook received", "event", eventType, "body_size", len(req.Body)) + + // Process webhook + switch eventType { + case "payment": + return p.handlePaymentWebhook(ctx, req) + case "user": + return p.handleUserWebhook(ctx, req) + default: + return &pb.EndpointResponse{ + StatusCode: 200, + Headers: map[string]string{"Content-Type": "application/json"}, + Body: []byte(`{"status": "acknowledged"}`), + }, nil + } +} + +func (p *WebhookPlugin) HandleEndpointRequestStream(ctx plugin_sdk.Context, req *pb.EndpointRequest, stream pb.PluginService_HandleEndpointRequestStreamServer) error { + return fmt.Errorf("streaming not supported for webhooks") +} + +func (p *WebhookPlugin) validateSignature(body []byte, signature string) bool { + mac := hmac.New(sha256.New, []byte(p.secret)) + mac.Write(body) + expected := hex.EncodeToString(mac.Sum(nil)) + return hmac.Equal([]byte(expected), []byte(signature)) +} + +func main() { + plugin_sdk.Serve(NewWebhookPlugin()) +} +``` + +## Lifecycle Management + +Custom endpoint routes are managed automatically across all plugin lifecycle events: + +| Event | Route Behavior | +|-------|----------------| +| **Plugin loaded** | Endpoints registered after `Initialize()` | +| **Plugin unloaded** | All routes for this plugin removed | +| **Plugin reloaded** | Routes cleared then re-registered | +| **Plugin deactivated** | Plugin unloaded, routes removed | +| **Plugin deleted** | Plugin unloaded, routes removed | +| **Gateway shutdown** | All routes cleared | +| **Health check failure** | Plugin auto-restarted, routes re-registered | +| **Control plane sync** | Routes refreshed to match new config | + +## Error Handling + +| Scenario | HTTP Status | +|----------|-------------| +| No route match | 404 Not Found | +| Plugin not loaded / unhealthy | 503 Service Unavailable | +| Plugin returns error via gRPC | 502 Bad Gateway | +| gRPC timeout (60s unary / configurable stream) | 504 Gateway Timeout | +| Auth required but missing/invalid | 401 Unauthorized | +| Streaming error after headers sent | Connection closed, error logged | + +## Configuration + +### Streaming Timeout + +The streaming endpoint timeout is configurable via environment variable: + +| Environment Variable | Default | Description | +|---------------------|---------|-------------| +| `PLUGIN_ENDPOINT_STREAM_TIMEOUT` | `5m` | Maximum duration for streaming endpoint responses (SSE) | +| `PLUGIN_ENDPOINT_MAX_BODY_SIZE` | `1048576` (1MB) | Maximum request body size for custom plugin endpoints | + +For long-running streaming connections (e.g., MCP proxy, real-time data feeds), increase the timeout: + +```bash +PLUGIN_ENDPOINT_STREAM_TIMEOUT=30m +``` + +The upstream server should send periodic data to keep the connection alive within the timeout window. diff --git a/ai-management/ai-studio/plugins/deployment.mdx b/ai-management/ai-studio/plugins/deployment.mdx new file mode 100644 index 0000000000..75de68ecb2 --- /dev/null +++ b/ai-management/ai-studio/plugins/deployment.mdx @@ -0,0 +1,583 @@ +--- +title: "Tyk AI Studio Plugin Deployment Options" +description: "Explore the three plugin deployment methods supported by Tyk AI Studio: local filesystem, remote gRPC, and OCI registry." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Deployment Options" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph TD + A[Deployment Methods] --> B[Local File] + A --> C[Remote gRPC] + A --> D[OCI Registry] +``` + + +Tyk AI Studio supports three plugin deployment methods: local filesystem (`file://`), remote gRPC (`grpc://`), and OCI registry (`oci://`). Choose the deployment method based on your environment and requirements. + +## Deployment Methods Comparison + +| Method | Use Case | Pros | Cons | +|--------|----------|------|------| +| `file://` | Development, testing | Fast, simple, easy debugging | Not suitable for production, requires filesystem access | +| `grpc://` | Production, distributed systems | Remote deployment, scalable | Requires network setup, more complex | +| `oci://` | Production, containerized | Version control, registry management | Requires OCI registry, packaging overhead | + +## file:// - Local Filesystem + +Deploy plugins from the local filesystem. + +> **Safety settings:** By default, local filesystem plugin loading has safety restrictions that prevent loading from arbitrary paths. For development, you may need to adjust these settings in your configuration to allow local path loading and absolute paths. See the Security Considerations section below for details on `ALLOW_INTERNAL_NETWORK_ACCESS` and `PLUGIN_COMMAND_ALLOWLIST`. + +### Building Your Plugin + +```bash +# Build for current platform +go build -o my-plugin main.go + +# Build for Linux (Docker/K8s deployment) +GOOS=linux GOARCH=amd64 go build -o my-plugin-linux main.go + +# Make executable +chmod +x my-plugin +``` + +### Creating Plugin via API + +```bash +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Plugin", + "slug": "my-plugin", + "command": "file:///absolute/path/to/my-plugin", + "hook_type": "pre_auth", + "plugin_type": "gateway", + "is_active": true + }' +``` + +**Important**: Use absolute paths with `file://`: + +```bash +✅ file:///usr/local/bin/my-plugin +✅ file:///home/user/plugins/my-plugin +❌ file://./my-plugin # Relative paths not supported +❌ /usr/local/bin/my-plugin # Missing file:// prefix +``` + +### Docker Deployment + +When deploying with Docker, mount plugins into the container: + +```yaml +# docker-compose.yml +services: + ai-studio: + image: tykio/ai-studio:latest + volumes: + - ./plugins:/plugins + environment: + - ALLOW_INTERNAL_NETWORK_ACCESS=true # For development only +``` + +Then register with container path: + +```bash +curl -X POST http://localhost:3000/api/v1/plugins \ + -d '{"command": "file:///plugins/my-plugin", ...}' +``` + +### Kubernetes Deployment + +Mount plugins via ConfigMap or PersistentVolume: + +```yaml Expandable +apiVersion: v1 +kind: ConfigMap +metadata: + name: plugins +binaryData: + my-plugin: +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ai-studio +spec: + template: + spec: + containers: + - name: ai-studio + image: tykio/ai-studio:latest + volumeMounts: + - name: plugins + mountPath: /plugins + volumes: + - name: plugins + configMap: + name: plugins + defaultMode: 0755 +``` + +## grpc:// - Remote gRPC + +Deploy plugins as remote gRPC services. + +### Running Plugin as gRPC Server + +Your plugin already implements gRPC via go-plugin. To run it as a remote service, you need a gRPC wrapper: + +```go Expandable +package main + +import ( + "log" + "net" + + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "google.golang.org/grpc" +) + +func main() { + // Create gRPC server + lis, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } + + grpcServer := grpc.NewServer() + + // Register your plugin + plugin := &MyPlugin{} + ai_studio_sdk.RegisterPluginServer(grpcServer, plugin) + + log.Printf("Plugin gRPC server listening on :50051") + if err := grpcServer.Serve(lis); err != nil { + log.Fatalf("Failed to serve: %v", err) + } +} +``` + +### Deploying with Docker + +```dockerfile +FROM golang:1.21-alpine AS builder +WORKDIR /build +COPY . . +RUN go build -o plugin-server main.go + +FROM alpine:latest +COPY --from=builder /build/plugin-server /usr/local/bin/ +EXPOSE 50051 +CMD ["/usr/local/bin/plugin-server"] +``` + +```bash +# Build and run +docker build -t my-plugin-server . +docker run -d -p 50051:50051 my-plugin-server +``` + +### Register Remote Plugin + +```bash +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "name": "My Remote Plugin", + "slug": "my-remote-plugin", + "command": "grpc://plugin-server:50051", + "hook_type": "pre_auth", + "plugin_type": "gateway", + "is_active": true + }' +``` + +### Network Configuration + +#### Internal Network Access + +By default, plugins cannot access internal networks. For development: + +```bash +export ALLOW_INTERNAL_NETWORK_ACCESS=true +``` + +For production, use allowlist: + +```bash +export PLUGIN_COMMAND_ALLOWLIST="grpc://10.0.0.0/8,grpc://172.16.0.0/12" +``` + +#### Load Balancing + +Use Kubernetes services for load balancing: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: my-plugin +spec: + selector: + app: my-plugin + ports: + - port: 50051 + targetPort: 50051 + type: ClusterIP +``` + +Register with service DNS: + +```bash +"command": "grpc://my-plugin.default.svc.cluster.local:50051" +``` + +## oci:// - OCI Registry + +Deploy plugins as OCI artifacts in container registries. + +### Prerequisites + +- Docker or Podman +- OCI-compatible registry (Docker Hub, GHCR, ECR, GCR, Harbor, etc.) +- Registry credentials configured + +### Packaging Plugin as OCI Artifact + +Create a simple OCI image: + +```dockerfile +FROM scratch +COPY my-plugin /plugin +ENTRYPOINT ["/plugin"] +``` + +Build and push: + +```bash +# Build plugin binary +go build -o my-plugin main.go + +# Build OCI image +docker build -t registry.example.com/plugins/my-plugin:v1.0.0 . + +# Push to registry +docker push registry.example.com/plugins/my-plugin:v1.0.0 +``` + +### Registering OCI Plugin + +```bash +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "name": "My OCI Plugin", + "slug": "my-oci-plugin", + "command": "oci://registry.example.com/plugins/my-plugin:v1.0.0", + "oci_reference": "registry.example.com/plugins/my-plugin:v1.0.0", + "hook_type": "pre_auth", + "plugin_type": "gateway", + "is_active": true + }' +``` + +### Registry Authentication + +OCI registry authentication is configured via environment variables using the pattern: + +``` +OCI_PLUGINS_REGISTRY__= +``` + +Where `` is the registry hostname with dots replaced by underscores (e.g., `docker.tyk.io` → `DOCKER_TYK_IO`). + +#### Authentication Methods + +**Entitlement Token** (Cloudsmith and similar registries): + +```bash +# Entitlement token — sent as Basic auth (username: "token", password: entitlement) +OCI_PLUGINS_REGISTRY_DOCKER_TYK_IO_ENTITLEMENT=your-entitlement-token + +# Custom username for entitlement (default: "token") +OCI_PLUGINS_REGISTRY_DOCKER_TYK_IO_ENTITLEMENTUSERNAME=custom-user +``` + +**Username + Password**: + +```bash +OCI_PLUGINS_REGISTRY_GHCR_IO_USERNAME=github-user +OCI_PLUGINS_REGISTRY_GHCR_IO_PASSWORDENV=GITHUB_TOKEN # reads from this env var +``` + +**OAuth2/Bearer Token**: + +```bash +OCI_PLUGINS_REGISTRY_REGISTRY_EXAMPLE_COM_TOKEN=your-access-token +# Or read from env var: +OCI_PLUGINS_REGISTRY_REGISTRY_EXAMPLE_COM_TOKENENV=MY_REGISTRY_TOKEN +``` + +#### AI Studio vs Edge Gateway + +Both runtimes use the same `OCI_PLUGINS_REGISTRY_*` environment variables for auth. The difference is in how OCI support is enabled: + +| Setting | AI Studio | Edge Gateway | +|---------|-----------|-------------| +| **Enable OCI** | `AI_STUDIO_OCI_CACHE_DIR=/path` | `OCI_PLUGINS_CACHE_DIR=/path` (default: `/var/lib/microgateway/plugins`) | +| **Require signatures** | `AI_STUDIO_OCI_REQUIRE_SIGNATURE=true` | `OCI_PLUGINS_REQUIRE_SIGNATURE=true` | +| **Registry auth** | `OCI_PLUGINS_REGISTRY_*` (shared) | `OCI_PLUGINS_REGISTRY_*` (shared) | + +**Important**: `AI_STUDIO_OCI_CACHE_DIR` must be set for OCI plugin support to be enabled in AI Studio. Without it, OCI plugins cannot be installed or loaded, and registry auth configuration is ignored. + +#### Signature Verification + +When signature verification is enabled (Enterprise Edition), the `cosign` tool verifies plugin signatures against the registry. Cosign uses the standard Docker credential store (`~/.docker/config.json`) for registry authentication — it does **not** use the `OCI_PLUGINS_REGISTRY_*` environment variables. + +To configure cosign auth in containerized environments: + +```bash +# Create Docker config with registry credentials +mkdir -p ~/.docker +echo '{"auths":{"docker.tyk.io":{"auth":"'$(echo -n 'token:YOUR_ENTITLEMENT' | base64)'"}}}' > ~/.docker/config.json +``` + +For Kubernetes, mount a Docker config secret: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: oci-registry-creds +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: +``` + +Configure public keys for signature verification: + +```bash +# Public key for verifying plugin signatures +OCI_PLUGINS_PUBLIC_KEY_1=/path/to/cosign.pub +``` + +#### Cloudsmith-Specific Notes + +When using Cloudsmith with a custom Docker domain (e.g., `docker.tyk.io`): + +- **Entitlement auth** is the recommended method for programmatic access +- Entitlements bypass the Docker token exchange, which avoids scope issues with Cloudsmith's namespace-level scope requirements +- Each Cloudsmith repository has its own entitlement tokens — use repository-level entitlements +- The `ENTITLEMENT` auth type sends Basic auth directly on every request, which Cloudsmith supports natively + +### Version Management + +Use tags for version management: + +```bash +# Development +oci://registry.example.com/plugins/my-plugin:latest + +# Staging +oci://registry.example.com/plugins/my-plugin:v1.2.3-rc.1 + +# Production +oci://registry.example.com/plugins/my-plugin:v1.2.3 + +# Immutable digest +oci://registry.example.com/plugins/my-plugin@sha256:abc123... +``` + +### Caching + +OCI plugins are pulled and cached locally: + +```bash +# AI Studio +AI_STUDIO_OCI_CACHE_DIR=/var/cache/ai-studio/plugins + +# Edge Gateway (defaults to /var/lib/microgateway/plugins) +OCI_PLUGINS_CACHE_DIR=/var/cache/microgateway/plugins +``` + +## Security Considerations + +### Command Validation + +The platform validates plugin commands for security: + +1. **Absolute Paths**: `file://` commands must use absolute paths +2. **Internal Network Block**: By default, `grpc://` commands cannot target internal IPs (10.x.x.x, 172.16-31.x.x, 192.168.x.x, 127.x.x.x, localhost) +3. **Allowlist**: Configure allowed commands via `PLUGIN_COMMAND_ALLOWLIST` + +Example warnings: + +``` +⚠️ PLUGIN SECURITY WARNING: Plugin command uses absolute path outside standard directories. +⚠️ PLUGIN SECURITY WARNING: Plugin command targets internal network address +``` + +### Production Security + +For production deployments: + +1. **Disable Internal Network Access**: + ```bash + export ALLOW_INTERNAL_NETWORK_ACCESS=false + export PLUGIN_BLOCK_INTERNAL_URLS=true + ``` + +2. **Use Allowlist**: + ```bash + export PLUGIN_COMMAND_ALLOWLIST="/usr/local/plugins/*,grpc://plugins.prod.svc/*" + ``` + +3. **Use OCI with Signed Images**: + - Sign images with Cosign or Notary + - Verify signatures before deployment + - Use content trust + +4. **Principle of Least Privilege**: + - Run plugins with minimal permissions + - Use read-only filesystems where possible + - Implement network policies in Kubernetes + +## Troubleshooting + + + + + + +**Symptoms**: Plugin shows as inactive, errors in logs + +**Solutions**: +- Verify plugin binary has execute permissions (`chmod +x`) +- Check absolute path is correct for `file://` +- Verify network connectivity for `grpc://` +- Check registry authentication for `oci://` +- Review plugin logs for initialization errors + + + + + +**Symptoms**: "Permission denied" error when loading plugin + +**Solutions**: + +```bash +# Check file permissions +ls -la /path/to/plugin + +# Make executable +chmod +x /path/to/plugin + +# Check SELinux context (if applicable) +chcon -t container_file_t /path/to/plugin +```` + + + + + +**Symptoms**: "connection refused", "no route to host" for `grpc://` + +**Solutions**: + +* Verify plugin server is running: `telnet plugin-host 50051` +* Check firewall rules +* Verify Kubernetes service is created +* Check DNS resolution +* Enable internal network access if needed (development only) + + + + + +**Symptoms**: "Failed to pull image", "authentication required" + +**Solutions**: + +* Verify registry URL is correct +* Check credentials are configured +* Test manual pull: `docker pull registry.example.com/plugins/my-plugin:v1.0.0` +* Check registry permissions +* Verify image exists with correct tag + + + + + +**Symptoms**: Plugin loads but immediately crashes + +**Solutions**: + +* Check plugin logs for panics +* Verify Go version compatibility +* Check for missing dependencies +* Test plugin standalone: `./my-plugin` +* Review initialization code for errors + + + + + +## Best Practices + +### Development Workflow + +1. **Local Development**: Use `file://` for fast iteration +2. **Reload Loop**: Use `POST /api/v1/plugins/{id}/reload` to test changes instantly +3. **Testing**: Deploy to staging with `grpc://` or `oci://` +4. **Production**: Use `oci://` with versioned tags + +**See [Plugin Development Workflow](/ai-management/ai-studio/plugins/development-workflow)** for detailed setup, helper scripts, and the fastest development loop. + +### Version Management + +1. Use semantic versioning +2. Tag releases in Git and OCI registry +3. Never reuse tags (immutable releases) +4. Document breaking changes + +### Deployment Pipeline + +```bash Expandable +# 1. Build +go build -o plugin main.go + +# 2. Test locally +./plugin # Verify it runs + +# 3. Package +docker build -t registry.example.com/plugins/my-plugin:v1.2.3 . + +# 4. Push +docker push registry.example.com/plugins/my-plugin:v1.2.3 + +# 5. Deploy to staging +curl -X POST .../plugins -d '{"command": "oci://registry.../my-plugin:v1.2.3-rc.1", ...}' + +# 6. Test in staging +# Run integration tests + +# 7. Deploy to production +curl -X POST .../plugins -d '{"command": "oci://registry.../my-plugin:v1.2.3", ...}' +``` + +### Monitoring + +- Monitor plugin health via `/api/v1/plugins/{id}/health` +- Track plugin performance metrics +- Set up alerts for plugin failures +- Log all plugin operations + diff --git a/ai-management/ai-studio/plugins/development-workflow.mdx b/ai-management/ai-studio/plugins/development-workflow.mdx new file mode 100644 index 0000000000..66e9400edd --- /dev/null +++ b/ai-management/ai-studio/plugins/development-workflow.mdx @@ -0,0 +1,453 @@ +--- +title: "Tyk AI Studio Plugin Development Workflow" +description: "A guide to develop and test Tyk AI Studio plugins locally using file:// paths and the reload API for instant iteration." +keywords: "AI Studio, AI Management" +sidebarTitle: "Development Workflow" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +This page covers the fastest way to develop and test plugins locally. The key insight: **use `file://` paths and the reload API for instant iteration without reinstalling the plugin**. + +## Quick Start: 5-Minute Setup + +### 1. Create Your Plugin + +```bash +mkdir my-plugin && cd my-plugin +go mod init my-plugin +``` + +Create `main.go`: + +```go Expandable +package main + +import ( + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +type MyPlugin struct { + plugin_sdk.BasePlugin +} + +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin("my-plugin", "1.0.0", "My test plugin"), + } +} + +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + ctx.Services.Logger().Info("Plugin initialized!") + return nil +} + +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + ctx.Services.Logger().Info("Request intercepted", "path", req.Request.Path) + return &pb.PluginResponse{Modified: false}, nil +} + +func main() { + plugin_sdk.Serve(NewMyPlugin()) +} +``` + +### 2. Build and Get Absolute Path + +```bash +go build -o my-plugin . +PLUGIN_PATH="$(pwd)/my-plugin" +echo $PLUGIN_PATH # e.g., /Users/you/projects/my-plugin/my-plugin +``` + +### 3. Register Plugin Once + +```bash Expandable +# Get your auth token (login first) +TOKEN="your-auth-token" + +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Plugin", + "slug": "my-plugin", + "command": "file://'$PLUGIN_PATH'", + "hook_type": "post_auth", + "plugin_type": "gateway", + "is_active": true + }' +``` + +**Save the plugin ID from the response!** + +### 4. Development Loop + +Now you can iterate without reinstalling: + +```bash +# Make changes to main.go, then: +go build -o my-plugin . && curl -X POST http://localhost:3000/api/v1/plugins/{PLUGIN_ID}/reload \ + -H "Authorization: Bearer $TOKEN" +``` + +That's it! Your changes are live instantly. + +--- + +## The Development Loop Explained + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ PLUGIN DEVELOPMENT LOOP │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Edit Code │ +│ └── main.go, manifest.json, etc. │ +│ ↓ │ +│ 2. Build │ +│ └── go build -o my-plugin . │ +│ ↓ │ +│ 3. Reload (ONE command!) │ +│ └── curl -X POST .../plugins/{id}/reload │ +│ ↓ │ +│ 4. Test │ +│ └── Make requests, check logs, verify behavior │ +│ ↓ │ +│ 5. Repeat from Step 1 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### When Do You Need to Reinstall? + +You **only** need to update the plugin registration (not just reload) when: + +| Change | Reload Sufficient? | Action Needed | +|--------|-------------------|---------------| +| Code logic changes | ✅ Yes | Just rebuild + reload | +| Adding new log statements | ✅ Yes | Just rebuild + reload | +| Configuration handling changes | ✅ Yes | Just rebuild + reload | +| Adding new capabilities (interfaces) | ✅ Yes | Just rebuild + reload | +| **Manifest permission changes** | ❌ No | Update plugin or recreate | +| **Hook type changes** | ❌ No | Update plugin `hook_type` field | +| **Plugin type changes** | ❌ No | Recreate plugin | + + +## Development Environment Setup + +### Option 1: Local AI Studio (Recommended for Speed) + +Run AI Studio locally for fastest iteration: + +```bash +# Start AI Studio +make start-dev + +# Set environment for plugin development +export ALLOW_INTERNAL_NETWORK_ACCESS=true +``` + +### Option 2: Docker Compose + +Mount your plugin directory: + +```yaml +# docker-compose.override.yml +services: + ai-studio: + volumes: + - ./my-plugins:/plugins + environment: + - ALLOW_INTERNAL_NETWORK_ACCESS=true +``` + +Then use container paths: +```bash +"command": "file:///plugins/my-plugin" +``` + +### Option 3: Remote AI Studio + +For remote instances, use `grpc://` deployment during development: + +```bash +# Run plugin as gRPC server locally +go run main.go --grpc-server :50051 + +# Register with remote instance +"command": "grpc://your-local-ip:50051" +``` + +--- + +## Helper Scripts + +### `dev.sh` - One-Command Development + +Create this script in your plugin directory: + +```bash Expandable +#!/bin/bash +# dev.sh - Build and reload plugin in one command + +PLUGIN_ID="${PLUGIN_ID:-your-plugin-id}" +API_URL="${API_URL:-http://localhost:3000}" +TOKEN="${TOKEN:-your-token}" + +echo "Building..." +go build -o my-plugin . || exit 1 + +echo "Reloading plugin $PLUGIN_ID..." +curl -s -X POST "$API_URL/api/v1/plugins/$PLUGIN_ID/reload" \ + -H "Authorization: Bearer $TOKEN" | jq . + +echo "Done! Check logs with: tail -f /path/to/ai-studio.log | grep my-plugin" +``` + +Usage: +```bash +chmod +x dev.sh +export PLUGIN_ID=123 TOKEN=xxx +./dev.sh +``` + +### `watch.sh` - Auto-Rebuild on Save + +```bash Expandable +#!/bin/bash +# watch.sh - Watch for changes and auto-reload + +PLUGIN_ID="${PLUGIN_ID:-your-plugin-id}" +API_URL="${API_URL:-http://localhost:3000}" +TOKEN="${TOKEN:-your-token}" + +echo "Watching for changes... (Ctrl+C to stop)" + +# Using fswatch (macOS: brew install fswatch) +fswatch -o *.go | while read; do + echo "Change detected, rebuilding..." + go build -o my-plugin . && \ + curl -s -X POST "$API_URL/api/v1/plugins/$PLUGIN_ID/reload" \ + -H "Authorization: Bearer $TOKEN" > /dev/null && \ + echo "✓ Reloaded at $(date +%H:%M:%S)" +done +``` + + +## Project Structure Recommendations + +``` +my-plugin/ +├── main.go # Plugin entry point +├── plugin.go # Core plugin logic +├── config.go # Configuration handling +├── manifest.json # Plugin manifest (embed with //go:embed) +├── config.schema.json # Config JSON schema (embed) +├── dev.sh # Development helper script +├── Makefile # Build targets +└── ui/ # UI assets (if UI plugin) + ├── index.html + └── bundle.js +``` + +### Makefile Example + +```makefile Expandable +PLUGIN_ID ?= your-plugin-id +API_URL ?= http://localhost:3000 +TOKEN ?= your-token + +.PHONY: build reload dev clean + +build: + go build -o my-plugin . + +reload: + curl -s -X POST $(API_URL)/api/v1/plugins/$(PLUGIN_ID)/reload \ + -H "Authorization: Bearer $(TOKEN)" + +dev: build reload + @echo "Plugin reloaded!" + +clean: + rm -f my-plugin + +# First-time setup +install: build + curl -X POST $(API_URL)/api/v1/plugins \ + -H "Authorization: Bearer $(TOKEN)" \ + -H "Content-Type: application/json" \ + -d '{"name":"My Plugin","slug":"my-plugin","command":"file://$(PWD)/my-plugin","hook_type":"post_auth","plugin_type":"gateway","is_active":true}' +``` + +Usage: +```bash +make dev # Build and reload in one command +``` + +## Debugging Tips + + + + + +Plugin logs go to AI Studio's log output: + +```bash +# If running locally +tail -f /path/to/logs | grep "my-plugin" + +# Docker +docker logs -f ai-studio 2>&1 | grep "my-plugin" + +# Filter by plugin name +... | grep "\[my-plugin\]" +```` + + + + + +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Debug logging - shows in AI Studio logs + ctx.Services.Logger().Debug("Request details", + "method", req.Request.Method, + "path", req.Request.Path, + "headers", req.Request.Headers, + "body_length", len(req.Request.Body), + ) + + // Add more detailed logging during development + ctx.Services.Logger().Info("Processing request", + "app_id", ctx.AppID, + "user_id", ctx.UserID, + "runtime", ctx.Runtime, + ) + + return &pb.PluginResponse{Modified: false}, nil +} +``` + + + + + +```bash +# Get plugin status +curl http://localhost:3000/api/v1/plugins/$PLUGIN_ID/status \ + -H "Authorization: Bearer $TOKEN" | jq . + +# List all loaded plugins +curl http://localhost:3000/api/v1/plugins/loaded \ + -H "Authorization: Bearer $TOKEN" | jq . +``` + + + + + +Before registering, test your plugin runs: + +```bash +# This should start and wait for gRPC connection +./my-plugin + +# If it crashes immediately, check for: +# - Missing dependencies +# - Invalid manifest +# - Initialization errors +``` + + + + + +## Common Issues and Resolution + + + + + +**Symptoms**: Reload returns success but changes aren't reflected + +**Solutions**: + +```bash +# 1. Verify build succeeded +go build -o my-plugin . && echo "Build OK" + +# 2. Check the binary was actually updated +ls -la my-plugin + +# 3. Verify reload endpoint returned success +curl -X POST .../reload -H "..." | jq .status + +# 4. Check AI Studio logs for errors +tail -100 /path/to/logs | grep -i error +```` + + + + + +**Symptoms**: Reload fails with permission error + +**Solutions**: + +```bash +# Make binary executable +chmod +x my-plugin + +# Check file ownership (Docker) +ls -la my-plugin +# May need: chown 1000:1000 my-plugin (for Docker user) +``` + + + + + +**Symptoms**: Old behavior persists after reload + +**Solutions**: + +```bash +# 1. Verify you're building to the right path +which my-plugin # vs ./my-plugin + +# 2. Check plugin command points to your binary +curl http://localhost:3000/api/v1/plugins/$PLUGIN_ID | jq .command + +# 3. Force deactivate and reactivate +curl -X PATCH .../plugins/$PLUGIN_ID -d '{"is_active": false}' +curl -X PATCH .../plugins/$PLUGIN_ID -d '{"is_active": true, "load_immediately": true}' +``` + + + + + +**Symptoms**: New UI components or permissions not appearing + +**Solutions**: + +```bash +# Manifest requires explicit re-parsing after reload +curl -X POST http://localhost:3000/api/v1/plugins/$PLUGIN_ID/manifest/parse \ + -H "Authorization: Bearer $TOKEN" + +# Or update the plugin entirely +curl -X PATCH http://localhost:3000/api/v1/plugins/$PLUGIN_ID \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"manifest": {...}}' +``` + + + + diff --git a/ai-management/ai-studio/plugins/edge-gateway.mdx b/ai-management/ai-studio/plugins/edge-gateway.mdx new file mode 100644 index 0000000000..042f324c29 --- /dev/null +++ b/ai-management/ai-studio/plugins/edge-gateway.mdx @@ -0,0 +1,901 @@ +--- +title: "Tyk AI Studio Edge Gateway Plugins" +description: "Learn how to use Edge Gateway plugins to provide middleware hooks in the LLM proxy request/response pipeline for custom authentication, transformation, and data collection." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Edge Gateway" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph LR + A[Request] --> B[PreAuth] + B --> C[Auth] + C --> D[PostAuth] + D --> E[Upstream LLM] + E --> F[Response Hook] + F --> G[Client] +``` + +Edge Gateway plugins provide middleware hooks in the LLM proxy request/response pipeline using the **Unified Plugin SDK**. Use them for custom authentication, request/response transformation, content filtering, and data collection to external systems. + +All Edge Gateway plugins now use `pkg/plugin_sdk`, which automatically detects the Gateway runtime and provides access to universal services (KV storage, logging) and Gateway-specific services (app management, budget status). + +## Plugin Capabilities + +Edge Gateway plugins implement one or more of these capability interfaces: + +### 1. PreAuthHandler + +**Interface**: `PreAuthHandler` +**Method**: `HandlePreAuth(ctx Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error)` + +Executes **before** authentication. Use for: +- Request validation and early rejection +- Request enrichment with metadata +- Header modification +- Logging and auditing + +**Working Example**: [`examples/plugins/gateway/request_enricher/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/request_enricher) + +### 2. AuthHandler + +**Interface**: `AuthHandler` +**Method**: `HandleAuth(ctx Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error)` + +**Replaces** default token authentication. Use for: +- Custom authentication schemes (OAuth, JWT, API keys) +- Integration with external identity providers +- Multi-factor authentication +- Custom authorization logic + +**Note**: Unified SDK provides credential validation via `ctx.Services.Gateway().ValidateCredential()` + +### 3. PostAuthHandler + +**Interface**: `PostAuthHandler` +**Method**: `HandlePostAuth(ctx Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error)` + +Executes **after** authentication. Most common capability for gateway plugins. Use for: +- Enriching requests with user-specific data +- Per-user request transformation +- Access control enforcement +- Usage quota checks + +**Working Example**: [`examples/plugins/gateway/request_enricher/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/request_enricher) + +### 4. ResponseHandler + +**Interface**: `ResponseHandler` +**Methods**: +- `OnBeforeWriteHeaders(ctx Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error)` +- `OnBeforeWrite(ctx Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error)` + +Modifies LLM responses before returning to client. Two-phase processing: +- **OnBeforeWriteHeaders**: Modify response headers +- **OnBeforeWrite**: Modify response body + +Use for: +- Response filtering and content moderation +- Response transformation and formatting +- Injecting additional metadata +- Response validation + +**Working Example**: [`examples/plugins/gateway/response_modifier/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/response_modifier) + +### 5. DataCollector + +**Interface**: `DataCollector` +**Methods**: +- `HandleProxyLog(ctx Context, log *pb.ProxyLogData) error` +- `HandleAnalytics(ctx Context, analytics *pb.AnalyticsData) error` +- `HandleBudgetUsage(ctx Context, usage *pb.BudgetUsageData) error` + +Intercepts data before database storage. Use for: +- Exporting proxy logs to external systems +- Sending analytics to data warehouses +- Custom budget tracking +- Real-time monitoring and alerting + +**Working Examples**: +- [`examples/plugins/data-collectors/file-analytics-collector/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/data-collectors/file-analytics-collector) (unified SDK) +- [`examples/plugins/data-collectors/file-budget-collector/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/data-collectors/file-budget-collector) (unified SDK) +- [`examples/plugins/data-collectors/file-proxy-collector/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/data-collectors/file-proxy-collector) (unified SDK) +- [`examples/plugins/gateway/legacy-collectors/elasticsearch_collector/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/legacy-collectors/elasticsearch_collector) + +### 6. CustomEndpointHandler + +**Interface**: `CustomEndpointHandler` +**Methods**: +- `GetEndpointRegistrations() ([]*pb.EndpointRegistration, error)` +- `HandleEndpointRequest(ctx Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error)` +- `HandleEndpointRequestStream(ctx Context, req *pb.EndpointRequest, stream grpc.ServerStreamingServer[pb.EndpointResponseChunk]) error` + +Registers and serves custom HTTP endpoints under `/plugins/{slug}/`. Plugins have full control over the response. Use for: +- Custom APIs (OAuth endpoints, webhooks, health checks) +- MCP Streamable HTTP proxy servers +- Protocol-specific proxies +- Any endpoint that doesn't fit the LLM/Tool/Datasource model + +Supports both unary responses and streaming (SSE) via the `stream_response` flag on endpoint registrations. + +**Full Guide**: [Custom Endpoints Guide](/ai-management/ai-studio/plugins/custom-endpoints) + +## Quick Start + +### 1. Project Setup + +```bash +# Create plugin directory +mkdir my-gateway-plugin && cd my-gateway-plugin + +# Initialize Go module +go mod init github.com/myorg/my-gateway-plugin + +# Add unified SDK dependency +go get github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk +``` + +### 2. Implement Plugin Structure + +Use the unified SDK with `BasePlugin` convenience struct: + +```go Expandable +package main + +import ( + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +type MyGatewayPlugin struct { + plugin_sdk.BasePlugin + apiKey string +} + +func NewMyGatewayPlugin() *MyGatewayPlugin { + return &MyGatewayPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-gateway-plugin", + "1.0.0", + "Custom gateway middleware", + ), + } +} + +// Initialize is called when plugin starts +func (p *MyGatewayPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Parse configuration + p.apiKey = config["api_key"] + + ctx.Services.Logger().Info("Plugin initialized", + "runtime", ctx.Runtime, + ) + + return nil +} + +// Shutdown performs cleanup +func (p *MyGatewayPlugin) Shutdown() error { + return nil +} + +func main() { + plugin_sdk.Serve(NewMyGatewayPlugin()) +} +``` + +### 3. Implement Capability Interfaces + +Implement one or more capability interfaces based on your needs: + +#### PostAuthHandler (Most Common) + +```go Expandable +// Implement PostAuthHandler interface +func (p *MyGatewayPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Log request + ctx.Services.Logger().Info("Processing request", + "app_id", ctx.AppID, + "user_id", ctx.UserID, + "path", req.Path, + ) + + // Enrich request with custom header + if req.Headers == nil { + req.Headers = make(map[string]string) + } + req.Headers["X-Custom-Header"] = "gateway-plugin" + req.Headers["X-App-ID"] = fmt.Sprintf("%d", ctx.AppID) + + return &pb.PluginResponse{ + Modified: true, + Request: req, + }, nil +} +``` + +#### PreAuthHandler + +```go Expandable +// Implement PreAuthHandler interface +func (p *MyGatewayPlugin) HandlePreAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Validate request early + if req.Method != "POST" { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Only POST requests allowed", + }, nil + } + + // Check budget before processing + if ctx.Runtime == plugin_sdk.RuntimeGateway { + status, err := ctx.Services.Gateway().GetBudgetStatus(ctx, ctx.AppID) + if err == nil { + budgetResp := status.(*gwmgmt.GetBudgetStatusResponse) + if budgetResp.RemainingBudget <= 0 { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Budget exceeded", + }, nil + } + } + } + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +#### ResponseHandler + +```go Expandable +// Implement ResponseHandler interface +func (p *MyGatewayPlugin) OnBeforeWriteHeaders(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + // Add custom response headers + if req.Headers == nil { + req.Headers = make(map[string]string) + } + req.Headers["X-Processed-By"] = "gateway-plugin" + req.Headers["X-Request-ID"] = req.RequestId + + return &pb.ResponseWriteResponse{ + Modified: true, + Headers: req.Headers, + }, nil +} + +func (p *MyGatewayPlugin) OnBeforeWrite(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + // Modify response body if needed + modifiedBody := transformResponse(req.Body) + + return &pb.ResponseWriteResponse{ + Modified: true, + Body: modifiedBody, + }, nil +} +``` + +#### DataCollector + +```go Expandable +// Implement DataCollector interface +func (p *MyGatewayPlugin) HandleProxyLog(ctx plugin_sdk.Context, log *pb.ProxyLogData) error { + // Export to external system + ctx.Services.Logger().Debug("Proxy log received", + "app_id", log.AppId, + "vendor", log.Vendor, + "status", log.ResponseCode, + ) + + return p.sendToElasticsearch(ctx, log) +} + +func (p *MyGatewayPlugin) HandleAnalytics(ctx plugin_sdk.Context, analytics *pb.AnalyticsData) error { + // Process analytics + ctx.Services.Logger().Debug("Analytics received", + "llm_id", analytics.LlmId, + "tokens", analytics.TotalTokens, + "cost", analytics.Cost, + ) + + return p.sendAnalytics(ctx, analytics) +} + +func (p *MyGatewayPlugin) HandleBudgetUsage(ctx plugin_sdk.Context, usage *pb.BudgetUsageData) error { + // Track budget usage + ctx.Services.Logger().Debug("Budget usage", + "app_id", usage.AppId, + "cost", usage.Cost, + ) + + return p.trackBudget(ctx, usage) +} +``` + +### 4. Build Plugin + +```bash +# Build for current platform +go build -o my-plugin main.go + +# Build for Linux (if deploying to Docker/K8s) +GOOS=linux GOARCH=amd64 go build -o my-plugin-linux main.go +``` + +### 5. Deploy Plugin + +Create plugin in AI Studio dashboard or via API: + +```bash +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Plugin", + "slug": "my-plugin", + "description": "Custom plugin", + "command": "file:///path/to/my-plugin", + "hook_type": "pre_auth", + "is_active": true, + "plugin_type": "gateway" + }' +``` + +### 6. Attach to LLM + +Associate the plugin with an LLM to activate it: + +```bash +curl -X PUT http://localhost:3000/api/v1/llms/1/plugins \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "plugin_ids": [1, 2, 3] + }' +``` + +## Configuration Schema + +Provide JSON Schema for plugin configuration using the `ConfigSchemaProvider` interface: + +```go +//go:embed config.schema.json +var configSchema []byte + +func (p *MyPlugin) GetConfigSchema() ([]byte, error) { + return configSchema, nil +} +``` + +Example `config.schema.json`: + +```json Expandable +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "api_key": { + "type": "string", + "description": "API key for external service", + "minLength": 1 + }, + "endpoint": { + "type": "string", + "format": "uri", + "description": "External service endpoint", + "default": "https://api.example.com" + }, + "batch_size": { + "type": "integer", + "description": "Batch size for data collection", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + }, + "required": ["api_key"] +} +``` + +Configuration values are passed to `Initialize()` and can be updated via the API. + +## Complete Examples + +### Example 1: Custom Authentication Plugin + +This example uses the **Unified SDK** for consistency with other gateway plugins like `llm-firewall` and `llm-cache`. + +```go Expandable +package main + +import ( + "strings" + + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +type CustomAuthPlugin struct { + plugin_sdk.BasePlugin + validToken string +} + +func NewCustomAuthPlugin() *CustomAuthPlugin { + return &CustomAuthPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "custom-auth", + "1.0.0", + "Custom token authentication plugin", + ), + } +} + +func (p *CustomAuthPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Parse configuration + if token, ok := config["valid_token"]; ok && token != "" { + p.validToken = token + } else { + p.validToken = "default-token" + } + + ctx.Services.Logger().Info("CustomAuthPlugin initialized", + "runtime", ctx.Runtime, + ) + + return nil +} + +func (p *CustomAuthPlugin) Shutdown(ctx plugin_sdk.Context) error { + return nil +} + +// HandleAuth implements the AuthHandler interface +func (p *CustomAuthPlugin) HandleAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Extract token from Authorization header + authHeader := "" + if req.Request != nil && req.Request.Headers != nil { + authHeader = req.Request.Headers["Authorization"] + } + + token := strings.TrimPrefix(authHeader, "Bearer ") + + if token == p.validToken { + ctx.Services.Logger().Info("Authentication successful", + "request_id", ctx.RequestID, + ) + + return &pb.PluginResponse{ + Modified: true, + Credential: &pb.Credential{ + UserID: "plugin-user", + Username: "Plugin User", + Claims: map[string]string{ + "source": "custom-auth-plugin", + }, + }, + }, nil + } + + ctx.Services.Logger().Warn("Authentication failed", + "request_id", ctx.RequestID, + "token_provided", token != "", + ) + + return &pb.PluginResponse{ + Block: true, + StatusCode: 401, + ErrorMessage: "Invalid token", + Headers: map[string]string{ + "WWW-Authenticate": "Bearer", + }, + }, nil +} + +func main() { + plugin := NewCustomAuthPlugin() + plugin_sdk.Serve(plugin) +} +``` + +**Working Example**: See [`plugins/llm-firewall/`](https://github.com/TykTechnologies/ai-studio-community-plugins/tree/main/plugins/llm-firewall) for a production-ready content filtering plugin using this pattern. + +### Example 2: Elasticsearch Data Collector + +```go Expandable +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/TykTechnologies/midsommar/microgateway/plugins/sdk" +) + +type ElasticsearchCollector struct { + esURL string + client *http.Client +} + +func (p *ElasticsearchCollector) Initialize(config map[string]interface{}) error { + if url, ok := config["elasticsearch_url"].(string); ok { + p.esURL = url + } else { + p.esURL = "http://localhost:9200" + } + + p.client = &http.Client{Timeout: 10 * time.Second} + return nil +} + +func (p *ElasticsearchCollector) GetHookType() sdk.HookType { + return sdk.HookTypeDataCollection +} + +func (p *ElasticsearchCollector) GetName() string { + return "elasticsearch-collector" +} + +func (p *ElasticsearchCollector) GetVersion() string { + return "1.0.0" +} + +func (p *ElasticsearchCollector) Shutdown() error { + return nil +} + +func (p *ElasticsearchCollector) HandleProxyLog(ctx context.Context, + req *sdk.ProxyLogData, + pluginCtx *sdk.PluginContext) (*sdk.DataCollectionResponse, error) { + + doc := map[string]interface{}{ + "@timestamp": req.Timestamp.Format(time.RFC3339), + "app_id": req.AppID, + "user_id": req.UserID, + "vendor": req.Vendor, + "request_body": string(req.RequestBody), + "response_body": string(req.ResponseBody), + "response_code": req.ResponseCode, + "request_id": req.RequestID, + } + + indexName := fmt.Sprintf("microgateway-proxy-logs-%s", + req.Timestamp.Format("2006.01.02")) + + if err := p.indexDocument(ctx, indexName, doc); err != nil { + return &sdk.DataCollectionResponse{ + Success: false, + Handled: false, + ErrorMessage: err.Error(), + }, nil + } + + return &sdk.DataCollectionResponse{ + Success: true, + Handled: true, // Don't store in database + }, nil +} + +func (p *ElasticsearchCollector) HandleAnalytics(ctx context.Context, + req *sdk.AnalyticsData, + pluginCtx *sdk.PluginContext) (*sdk.DataCollectionResponse, error) { + + doc := map[string]interface{}{ + "@timestamp": req.Timestamp.Format(time.RFC3339), + "llm_id": req.LLMID, + "model_name": req.ModelName, + "vendor": req.Vendor, + "prompt_tokens": req.PromptTokens, + "response_tokens": req.ResponseTokens, + "total_tokens": req.TotalTokens, + "cost": req.Cost, + "request_id": req.RequestID, + } + + indexName := fmt.Sprintf("microgateway-analytics-%s", + req.Timestamp.Format("2006.01.02")) + + if err := p.indexDocument(ctx, indexName, doc); err != nil { + return &sdk.DataCollectionResponse{ + Success: false, + Handled: false, + ErrorMessage: err.Error(), + }, nil + } + + return &sdk.DataCollectionResponse{ + Success: true, + Handled: true, + }, nil +} + +func (p *ElasticsearchCollector) HandleBudgetUsage(ctx context.Context, + req *sdk.BudgetUsageData, + pluginCtx *sdk.PluginContext) (*sdk.DataCollectionResponse, error) { + + doc := map[string]interface{}{ + "@timestamp": req.Timestamp.Format(time.RFC3339), + "app_id": req.AppID, + "llm_id": req.LLMID, + "tokens_used": req.TokensUsed, + "cost": req.Cost, + "requests_count": req.RequestsCount, + "period_start": req.PeriodStart.Format(time.RFC3339), + "period_end": req.PeriodEnd.Format(time.RFC3339), + } + + indexName := fmt.Sprintf("microgateway-budget-%s", + req.Timestamp.Format("2006.01.02")) + + if err := p.indexDocument(ctx, indexName, doc); err != nil { + return &sdk.DataCollectionResponse{ + Success: false, + Handled: false, + ErrorMessage: err.Error(), + }, nil + } + + return &sdk.DataCollectionResponse{ + Success: true, + Handled: true, + }, nil +} + +func (p *ElasticsearchCollector) indexDocument(ctx context.Context, + indexName string, doc map[string]interface{}) error { + + jsonDoc, err := json.Marshal(doc) + if err != nil { + return err + } + + url := fmt.Sprintf("%s/%s/_doc", p.esURL, indexName) + req, err := http.NewRequestWithContext(ctx, "POST", url, + bytes.NewBuffer(jsonDoc)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := p.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return fmt.Errorf("elasticsearch returned status %d", resp.StatusCode) + } + + return nil +} + +func main() { + plugin := &ElasticsearchCollector{} + sdk.ServePlugin(plugin) +} +``` + +## Plugin Context + +The `PluginContext` provides contextual information about the request: + +```go +type PluginContext struct { + RequestID string // Unique request ID + LLMID uint // LLM being called + LLMSlug string // LLM slug identifier + Vendor string // LLM vendor (openai, anthropic, etc.) + AppID uint // App making the request + UserID uint // User making the request + Metadata map[string]interface{} // Additional metadata + TraceContext map[string]string // Distributed tracing headers +} +``` + +Use this context for logging, tracing, and per-request customization. + +## Testing Your Plugin + +### Unit Testing + +```go Expandable +func TestProcessPreAuth(t *testing.T) { + plugin := &MyPlugin{} + plugin.Initialize(map[string]interface{}{ + "setting": "value", + }) + + req := &sdk.PluginRequest{ + Method: "POST", + Path: "/v1/chat/completions", + Body: []byte(`{"messages": []}`), + } + + ctx := &sdk.PluginContext{ + RequestID: "test-123", + LLMID: 1, + } + + resp, err := plugin.ProcessPreAuth(context.Background(), req, ctx) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !resp.Modified { + t.Error("expected response to be modified") + } +} +``` + +### Integration Testing + +Use `file://` deployment to test with real LLM requests: + +```bash Expandable +# Build plugin +go build -o my-plugin main.go + +# Create plugin in AI Studio +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "command": "file:///full/path/to/my-plugin", + ... + }' + +# Test LLM request +curl -X POST http://localhost:3000/api/v1/llms/1/proxy/v1/chat/completions \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"messages": [{"role": "user", "content": "test"}]}' +``` + +## Best Practices + +### Performance + +- Keep plugin logic lightweight and fast +- Use timeouts for external API calls +- Implement connection pooling for external services +- Cache frequently accessed data +- Return early for requests that don't need processing + +### Error Handling + +- Log errors with context (request ID, LLM ID, etc.) +- Return descriptive error messages +- Don't panic - return errors properly +- Implement graceful degradation + +### Security + +- Validate all configuration inputs +- Sanitize user-provided data +- Use secure connections for external services +- Don't log sensitive data (tokens, PII) +- Implement rate limiting for external calls + +### Configuration + +- Provide sensible defaults +- Use JSON Schema for validation +- Document all configuration options +- Support configuration updates without restart + +## Troubleshooting + + + + + +- Check plugin command path is absolute with `file://` +- Verify plugin binary has execute permissions +- Check logs for initialization errors +- Ensure plugin implements all required interfaces + + + + + +- Check plugin logs for panics +- Verify external service connectivity +- Test with minimal configuration +- Use defensive error handling + + + + + +- Profile plugin with Go profiler +- Check for blocking operations +- Monitor external service latency +- Review resource usage (CPU, memory) + + + + + +## Gateway Services + +Gateway plugins have access to Gateway-specific services via `ctx.Services.Gateway()`: + +```go +if ctx.Runtime == plugin_sdk.RuntimeGateway { + // Get app configuration + app, err := ctx.Services.Gateway().GetApp(ctx, ctx.AppID) + + // Check budget status + status, err := ctx.Services.Gateway().GetBudgetStatus(ctx, ctx.AppID) + + // Validate credentials + valid, err := ctx.Services.Gateway().ValidateCredential(ctx, token) + + // Get LLM configuration + llm, err := ctx.Services.Gateway().GetLLM(ctx, llmID) +} +``` + +See [Service API Reference](/ai-management/ai-studio/plugins/service-api) for complete Gateway Services documentation. + +## Sending Data to Control Plane + +Gateway plugins can send data back to AI Studio (the control plane) using the `SendToControl` API. This is useful for: +- Aggregating statistics from edge instances +- Synchronizing state across the hub-and-spoke architecture +- Sending alerts or notifications to central plugins + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/microgateway/plugins/sdk" + +func (p *MyPlugin) HandlePostAuth(ctx sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Send JSON data to control plane + stats := map[string]interface{}{ + "requests": p.requestCount.Load(), + "errors": p.errorCount.Load(), + } + + pendingCount, err := sdk.SendToControlJSON(ctx, stats, "", map[string]string{ + "metric_type": "gateway_stats", + }) + if err != nil { + ctx.Services.Logger().Warn("Failed to queue stats", "error", err) + } + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +The control plane plugin receives this data via the `EdgePayloadReceiver` interface. + +See [Edge-to-Control Communication](/ai-management/ai-studio/plugins/edge-to-control) for complete documentation. + +## Working with Both Runtimes + +Plugins using the unified SDK can work in both Gateway and Studio: + +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Universal services (always available) + ctx.Services.Logger().Info("Processing request") + data, _ := ctx.Services.KV().Read(ctx, "config") + + // Runtime-specific logic + if ctx.Runtime == plugin_sdk.RuntimeGateway { + // Gateway-specific code + app, _ := ctx.Services.Gateway().GetApp(ctx, ctx.AppID) + } else if ctx.Runtime == plugin_sdk.RuntimeStudio { + // Studio-specific code + llms, _ := ctx.Services.Studio().ListLLMs(ctx, 1, 10) + } + + return &pb.PluginResponse{Modified: false}, nil +} +``` + diff --git a/ai-management/ai-studio/plugins/edge-to-control.mdx b/ai-management/ai-studio/plugins/edge-to-control.mdx new file mode 100644 index 0000000000..f588534400 --- /dev/null +++ b/ai-management/ai-studio/plugins/edge-to-control.mdx @@ -0,0 +1,542 @@ +--- +title: "Tyk AI Studio Plugin Edge to Control Communication" +description: "Understand the Edge-to-Control communication system that allows plugins running on Edge Gateway instances to send data back to plugins on the AI Studio control plane." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Edge to Control Communication" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +This page covers the **Edge-to-Control** communication system that allows plugins running on Edge Gateway (edge) instances to send data back to plugins running on AI Studio (control plane). + +## Overview + +In the hub-and-spoke architecture: +- **AI Studio** is the control plane (hub) +- **Edge Gateway** instances are edge nodes (spokes) + +Edge plugins may need to send data back to the control plane for: +- Cache statistics aggregation +- Shared state synchronization +- Audit log centralization +- Custom analytics collection +- Alert and notification routing + +The Edge-to-Control system provides a reliable, batched mechanism for this communication. + +## Architecture + +```mermaid +flowchart TD + subgraph ControlPlane ["AI Studio (Control Plane)"] + direction LR + ControlServer["Control Server
(gRPC)"] --- PluginManager["Plugin Manager"] + PluginManager --- StudioPlugin["Studio Plugin
EdgePayloadReceiver"] + end + + subgraph Edge ["Edge Gateway"] + direction LR + SimpleClient["Simple Client
(heartbeat)"] --- PayloadQueue["Payload Queue
(SQLite)"] + EdgePlugin["Edge Plugin
SendToControl()"] --> PayloadQueue + end + + ControlServer -->|"gRPC (SendPluginControlBatch)"| SimpleClient +``` + +### Data Flow + +1. **Edge Plugin** calls `SendToControl()` or `SendToControlJSON()` +2. Payload is **queued** to SQLite database (survives gateway restarts) +3. During **heartbeat** (every 30s), pending payloads are batched +4. Batch is sent via **gRPC** `SendPluginControlBatch` RPC +5. Control Server **routes** each payload to the target plugin +6. Studio plugin's `AcceptEdgePayload()` is called with the data + +## Edge Plugin: Sending Data + +### SDK Functions + +The Edge Gateway SDK provides two functions for sending data to the control plane: + +#### SendToControl + +Send raw byte payloads: + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/microgateway/plugins/sdk" + +func (p *MyPlugin) HandlePostAuth(ctx context.Context, req *sdk.EnrichedRequest) (*sdk.PluginResponse, error) { + // Send raw bytes to control plane + pendingCount, err := sdk.SendToControl( + ctx, + []byte(`{"event": "request_processed", "count": 1}`), // payload (max 1MB) + "req-12345", // correlation ID (optional) + map[string]string{ // metadata (optional) + "source": "edge-us-west-1", + "type": "analytics", + }, + ) + if err != nil { + p.logger.Error("Failed to queue payload", "error", err) + } + + p.logger.Info("Payload queued", "pending_count", pendingCount) + return &sdk.PluginResponse{Modified: false}, nil +} +``` + +#### SendToControlJSON + +Convenience function for JSON payloads: + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/microgateway/plugins/sdk" + +type CacheStats struct { + Hits int64 `json:"hits"` + Misses int64 `json:"misses"` + Size int64 `json:"size_bytes"` +} + +func (p *MyPlugin) sendCacheStats(ctx context.Context) error { + stats := CacheStats{ + Hits: p.cacheHits.Load(), + Misses: p.cacheMisses.Load(), + Size: p.cacheSize.Load(), + } + + pendingCount, err := sdk.SendToControlJSON( + ctx, + stats, // automatically marshaled to JSON + "", // no correlation ID + map[string]string{ + "metric_type": "cache", + }, + ) + if err != nil { + return fmt.Errorf("failed to queue stats: %w", err) + } + + p.logger.Debug("Cache stats queued", "pending", pendingCount) + return nil +} +``` + +### Function Signatures + +```go Expandable +// SendToControl queues a raw payload for delivery to the control plane plugin +func SendToControl( + ctx context.Context, + payload []byte, // Raw payload data (max 1MB) + correlationID string, // Optional tracking ID + metadata map[string]string, // Optional key-value metadata +) (pendingCount int64, err error) + +// SendToControlJSON marshals value to JSON and queues for delivery +func SendToControlJSON( + ctx context.Context, + value interface{}, // Any JSON-serializable value + correlationID string, // Optional tracking ID + metadata map[string]string, // Optional key-value metadata +) (pendingCount int64, err error) +``` + +### Payload Limits + +| Limit | Value | +|-------|-------| +| Maximum payload size | 1 MB | +| Maximum metadata entries | 50 | +| Maximum metadata key length | 256 bytes | +| Maximum metadata value length | 4096 bytes | +| Maximum correlation ID length | 256 bytes | + +### Queue Behavior + +- **Persistence**: Payloads are stored in SQLite and survive gateway restarts +- **Batching**: Payloads are batched during heartbeat (default: every 30s) +- **Batch size**: Up to 100 payloads per batch +- **Retention**: Successfully sent payloads are deleted; failed payloads are retried +- **Cleanup**: Payloads older than 24 hours are automatically cleaned up + +### Example: Complete Edge Plugin + +```go Expandable +package main + +import ( + "context" + "sync/atomic" + "time" + + "github.com/TykTechnologies/midsommar/v2/microgateway/plugins/sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +type CachePlugin struct { + sdk.BasePlugin + hits atomic.Int64 + misses atomic.Int64 +} + +func NewCachePlugin() *CachePlugin { + return &CachePlugin{ + BasePlugin: sdk.NewBasePlugin( + "llm-cache", + "1.0.0", + "LLM response caching with stats reporting", + ), + } +} + +func (p *CachePlugin) Initialize(ctx sdk.Context, config map[string]string) error { + // Start background stats reporter + go p.reportStats(ctx) + return nil +} + +func (p *CachePlugin) HandlePostAuth(ctx sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Check cache + cacheKey := p.computeCacheKey(req) + if cached, found := p.lookupCache(cacheKey); found { + p.hits.Add(1) + return &pb.PluginResponse{ + Block: true, + ResponseBody: cached, + StatusCode: 200, + }, nil + } + + p.misses.Add(1) + return &pb.PluginResponse{Modified: false}, nil +} + +func (p *CachePlugin) reportStats(ctx context.Context) { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + stats := map[string]interface{}{ + "hits": p.hits.Load(), + "misses": p.misses.Load(), + "hit_rate": p.calculateHitRate(), + "timestamp": time.Now().Unix(), + } + + _, err := sdk.SendToControlJSON(ctx, stats, "", map[string]string{ + "metric_type": "cache_stats", + "interval": "5m", + }) + if err != nil { + p.logger.Warn("Failed to send stats", "error", err) + } + } + } +} + +func main() { + sdk.Serve(NewCachePlugin()) +} +``` + +## Control Plane Plugin: Receiving Data + +### EdgePayloadReceiver Interface + +AI Studio plugins implement `EdgePayloadReceiver` to receive payloads from edge instances: + +```go +// EdgePayloadReceiver handles payloads sent from edge (Edge Gateway) instances +type EdgePayloadReceiver interface { + Plugin + + // AcceptEdgePayload is called when a payload arrives from an edge instance. + // Returns: + // - handled: true if this plugin processed the payload + // - error: non-nil if processing failed + AcceptEdgePayload(ctx Context, payload *EdgePayload) (handled bool, err error) +} +``` + +### EdgePayload Structure + +```go +type EdgePayload struct { + Payload []byte // Raw payload data from edge plugin + EdgeID string // Edge instance identifier + EdgeNamespace string // Namespace of the edge instance + CorrelationID string // Correlation ID for tracking + Metadata map[string]string // Key-value metadata + EdgeTimestamp int64 // Unix timestamp when generated at edge + ReceivedTimestamp int64 // Unix timestamp when received at control +} +``` + +### Example: Complete Control Plane Plugin + +```go Expandable +package main + +import ( + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" +) + +type CacheStatsAggregator struct { + plugin_sdk.BasePlugin + + mu sync.RWMutex + stats map[string]*EdgeStats // keyed by edge ID +} + +type EdgeStats struct { + EdgeID string + Hits int64 + Misses int64 + LastUpdate time.Time +} + +type IncomingStats struct { + Hits int64 `json:"hits"` + Misses int64 `json:"misses"` + HitRate float64 `json:"hit_rate"` + Timestamp int64 `json:"timestamp"` +} + +func NewCacheStatsAggregator() *CacheStatsAggregator { + return &CacheStatsAggregator{ + BasePlugin: plugin_sdk.NewBasePlugin( + "cache-stats-aggregator", + "1.0.0", + "Aggregates cache statistics from edge instances", + ), + stats: make(map[string]*EdgeStats), + } +} + +func (p *CacheStatsAggregator) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + ctx.Services.Logger().Info("Cache stats aggregator initialized") + return nil +} + +// Implement EdgePayloadReceiver interface +func (p *CacheStatsAggregator) AcceptEdgePayload(ctx plugin_sdk.Context, payload *plugin_sdk.EdgePayload) (bool, error) { + // Check if this payload is for us + metricType, ok := payload.Metadata["metric_type"] + if !ok || metricType != "cache_stats" { + // Not our payload, return handled=false so other plugins can process it + return false, nil + } + + // Parse the payload + var stats IncomingStats + if err := json.Unmarshal(payload.Payload, &stats); err != nil { + ctx.Services.Logger().Error("Failed to parse stats payload", + "edge_id", payload.EdgeID, + "error", err, + ) + return true, fmt.Errorf("invalid payload format: %w", err) + } + + // Store the stats + p.mu.Lock() + p.stats[payload.EdgeID] = &EdgeStats{ + EdgeID: payload.EdgeID, + Hits: stats.Hits, + Misses: stats.Misses, + LastUpdate: time.Unix(payload.EdgeTimestamp, 0), + } + p.mu.Unlock() + + ctx.Services.Logger().Info("Received cache stats from edge", + "edge_id", payload.EdgeID, + "namespace", payload.EdgeNamespace, + "hits", stats.Hits, + "misses", stats.Misses, + "hit_rate", stats.HitRate, + ) + + // Optionally persist to KV storage + key := fmt.Sprintf("edge-stats:%s", payload.EdgeID) + data, _ := json.Marshal(p.stats[payload.EdgeID]) + ctx.Services.KV().Write(ctx, key, data) + + return true, nil +} + +// GetAggregatedStats returns combined stats from all edges +func (p *CacheStatsAggregator) GetAggregatedStats() map[string]interface{} { + p.mu.RLock() + defer p.mu.RUnlock() + + var totalHits, totalMisses int64 + edgeCount := len(p.stats) + + for _, s := range p.stats { + totalHits += s.Hits + totalMisses += s.Misses + } + + hitRate := float64(0) + if totalHits+totalMisses > 0 { + hitRate = float64(totalHits) / float64(totalHits+totalMisses) * 100 + } + + return map[string]interface{}{ + "edge_count": edgeCount, + "total_hits": totalHits, + "total_misses": totalMisses, + "hit_rate": hitRate, + } +} + +func main() { + plugin_sdk.Serve(NewCacheStatsAggregator()) +} +``` + +## Plugin ID Matching + +The control plane routes payloads based on **plugin ID**. When an edge plugin sends a payload, it's associated with that plugin's numeric ID. The control plane then delivers it to the AI Studio plugin with the **same ID**. + +### Configuration + +1. **Create AI Studio plugin** (control plane receiver) +2. **Note the plugin ID** (e.g., `42`) +3. **Deploy Edge Gateway plugin** with the same ID configuration +4. Edge payloads from plugin 42 will be routed to AI Studio plugin 42 + +### Example Setup + +```yaml Expandable +# AI Studio plugin (plugin_id: 42) +plugins: + - id: 42 + name: "Cache Stats Aggregator" + command: "file:///plugins/cache-stats-aggregator" + hook_type: "post_auth" + plugin_type: "studio" + +# Edge Gateway config +plugins_config: + plugins: + - id: 42 + name: "LLM Cache" + command: "file:///plugins/llm-cache" + hook_type: "post_auth" +``` + +## Error Handling + +### Edge Plugin Errors + +```go Expandable +pendingCount, err := sdk.SendToControl(ctx, payload, "", nil) +if err != nil { + switch { + case errors.Is(err, sdk.ErrPayloadTooLarge): + // Payload exceeds 1MB limit + p.logger.Error("Payload too large, dropping") + case errors.Is(err, sdk.ErrQueueFull): + // Queue has too many pending payloads + p.logger.Warn("Queue full, payload dropped") + default: + // Other error (e.g., serialization failed) + p.logger.Error("Failed to queue payload", "error", err) + } +} +``` + +### Control Plane Plugin Errors + +```go Expandable +func (p *MyPlugin) AcceptEdgePayload(ctx plugin_sdk.Context, payload *plugin_sdk.EdgePayload) (bool, error) { + // Return handled=false if this isn't your payload + if !p.isMyPayload(payload) { + return false, nil + } + + // Return error for processing failures + if err := p.processPayload(payload); err != nil { + ctx.Services.Logger().Error("Failed to process payload", + "edge_id", payload.EdgeID, + "correlation_id", payload.CorrelationID, + "error", err, + ) + // Return handled=true with error - payload won't be retried + return true, err + } + + return true, nil +} +``` + +## Best Practices + +### Edge Plugins + +1. **Batch locally first**: Aggregate data before sending to reduce payload count +2. **Use correlation IDs**: For request/response matching or debugging +3. **Include metadata**: Add context like metric type, source, timestamp +4. **Handle errors gracefully**: Queue failures shouldn't crash your plugin +5. **Monitor pending count**: High pending counts may indicate connectivity issues + +### Control Plane Plugins + +1. **Check metadata first**: Return `handled=false` quickly for irrelevant payloads +2. **Validate payloads**: Don't trust edge data - validate before processing +3. **Use KV for persistence**: Store aggregated data for dashboard/API access +4. **Log with context**: Include edge ID and correlation ID in logs +5. **Handle duplicates**: Network issues may cause duplicate deliveries + +### Performance + +1. **Keep payloads small**: Smaller payloads = faster transmission +2. **Aggregate at edges**: Send summaries, not individual events +3. **Use appropriate intervals**: Balance freshness vs. overhead +4. **Monitor queue depth**: Alert on growing queues + +## Troubleshooting + + + + + +1. **Check edge connectivity**: Verify edge can reach control plane +2. **Check plugin IDs**: Edge and control plugins must have matching IDs +3. **Check logs**: Look for errors in both edge and control logs +4. **Verify plugin loaded**: Ensure control plane plugin is active + + + + + +1. **Check batch interval**: Default is 30s heartbeat +2. **Check payload size**: Large payloads take longer to transmit +3. **Check network**: Latency between edge and control +4. **Check queue depth**: High pending count = backlog + + + + + +1. **Check payload size**: Must be under 1MB +2. **Check queue capacity**: Queue may be full +3. **Check retention**: Payloads older than 24h are cleaned up +4. **Check errors**: Processing errors may cause drops + + + + diff --git a/ai-management/ai-studio/plugins/examples.mdx b/ai-management/ai-studio/plugins/examples.mdx new file mode 100644 index 0000000000..7d896c0a0d --- /dev/null +++ b/ai-management/ai-studio/plugins/examples.mdx @@ -0,0 +1,689 @@ +--- +title: "Tyk AI Studio Example Plugins" +description: "A comprehensive reference of working plugin examples in the Tyk AI Studio repository, demonstrating real-world patterns for different plugin capabilities." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Example Plugins" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph TD + A[Examples] --> B[Community Plugins] + A --> C[Enterprise Plugins] +``` + +Comprehensive reference of working plugin examples in the Tyk AI Studio repository. All examples use the **Unified Plugin SDK** (`pkg/plugin_sdk`) and demonstrate real-world patterns for different plugin capabilities. + +## Production-Ready Plugins + +The following plugins in `community/plugins/` and `enterprise/plugins/` are **production-tested** and serve as the best reference implementations: + +### Community Plugins + +#### LLM Cache + +**Path**: [`plugins/llm-cache/`](https://github.com/TykTechnologies/ai-studio-community-plugins/tree/main/plugins/llm-cache) + +**Capabilities**: PostAuth, Response, StreamComplete, UI, RPC, EdgePayloadReceiver, SessionAware, Config + +**Description**: Semantic caching for LLM responses using request hash matching. Reduces costs and latency by returning cached responses for similar prompts. + +**Key Features**: +- Request hashing for cache key generation +- Streaming response caching +- Edge-to-control cache synchronization +- WebComponent UI for cache management +- KV storage for cache state +- Session-aware broker connection warmup + +**Why Use This Example**: +- **Best example of multi-capability plugin** - Shows how to combine 8 different capabilities +- **Edge-to-control pattern** - Demonstrates hub-and-spoke communication +- **SessionAware warmup** - Critical pattern for Service API access + +**Complexity**: Advanced + +--- + +#### LLM Firewall + +**Path**: [`plugins/llm-firewall/`](https://github.com/TykTechnologies/ai-studio-community-plugins/tree/main/plugins/llm-firewall) + +**Capabilities**: PreAuth, PostAuth, Config + +**Description**: Content filtering for LLM prompts using configurable phrase/pattern matching. Blocks requests containing disallowed content. + +**Key Features**: +- Regex and literal phrase matching +- Per-model rule configuration +- Multi-vendor content extraction (OpenAI, Anthropic, Google AI, Vertex) +- Configurable block messages +- Case-sensitive/insensitive matching + +**Why Use This Example**: +- **Best example of content filtering** - Clean implementation of request inspection +- **Multi-vendor support** - Shows how to handle different LLM formats +- **Configuration-driven rules** - JSON schema for rule management + +**Complexity**: Intermediate + +--- + +#### GitHub RAG Ingest + +**Path**: [`plugins/github-rag-ingest/`](https://github.com/TykTechnologies/ai-studio-community-plugins/tree/main/plugins/github-rag-ingest) + +**Capabilities**: UI, RPC, Scheduler, SessionAware + +**Description**: Scheduled ingestion of GitHub repository content for RAG (Retrieval-Augmented Generation). Indexes code, docs, and issues. + +**Key Features**: +- Cron-based scheduled execution +- GitHub API integration +- Custom UI for ingest configuration +- RPC methods for manual triggers +- Session-aware initialization + +**Why Use This Example**: +- **Best example of scheduled data ingestion** - Complete Scheduler implementation +- **UI + backend integration** - Shows RPC pattern for UI communication +- **External API integration** - GitHub API usage patterns + +**Complexity**: Advanced + +--- + +### Enterprise Plugins + +> **Note**: Enterprise plugins require a valid license. + +#### LLM Load Balancer + +**Capabilities**: PostAuth, Response, UI + +**Description**: Intelligent request distribution across multiple LLM backends with health checking and failover. + +**Complexity**: Advanced + +--- + +#### Advanced LLM Cache + +**Capabilities**: PostAuth, Response, UI + +**Description**: Extended caching with semantic similarity matching, cache invalidation policies, and advanced analytics. Extends the community LLM Cache. + +**Complexity**: Advanced + +--- + +## Example Plugins + +The following examples in `examples/plugins/` demonstrate specific patterns and are useful for learning: + +## AI Studio Plugins + +### Echo Agent + +**Path**: [`examples/plugins/studio/echo-agent/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/echo-agent) + +**Capabilities**: Agent + +> **Experimental**: Agent plugins are currently experimental. See [Agent Plugins Guide](/ai-management/ai-studio/plugins/studio-agent) for full documentation. + +**Description**: Simple conversational agent that wraps LLM responses with custom prefix/suffix formatting. Demonstrates basic agent implementation with streaming responses and LLM integration. + +**Architecture**: Agent plugins follow the **Plugin → Agent Object → App Object** pattern: +- **Plugin**: Implements `HandleAgentMessage` for conversations (long-running gRPC) +- **Agent Object**: Binds the plugin to an App with configuration and group access +- **App Object**: Provides LLM access, tools, datasources, and budget control + +**Key Features**: +- Streaming server-side responses via gRPC +- LLM integration via `ai_studio_sdk.CallLLM()` +- Per-agent configuration (prefix, suffix, metadata) +- Fallback echo mode when no LLM available +- JSON schema configuration +- SessionAware warmup for Service API connection + +**Use Cases**: +- Learning agent plugin basics +- Response formatting and wrapping +- Custom agent configuration +- Reference implementation for agent architecture + +**Complexity**: Beginner + +--- + +### LLM Validator + +**Path**: [`examples/plugins/studio/llm-validator/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/llm-validator) + +**Capabilities**: Object Hooks (before_create, before_update) + +**Description**: Validates LLM configurations before they're saved to the database. Enforces HTTPS endpoints, blocks specific vendors, validates privacy scores, and requires descriptions. + +**Key Features**: +- Object hook registration for LLM objects +- `before_create` and `before_update` hooks +- Configurable validation rules +- Block operations with rejection reasons +- Add validation metadata to approved objects +- Priority ordering (runs early in chain) + +**Use Cases**: +- Enforcing security policies (HTTPS-only endpoints) +- Vendor compliance and blocking +- Privacy score validation +- Required field enforcement + +**Complexity**: Intermediate + +--- + +### LLM Rate Limiter (Multi-Phase) + +**Path**: [`examples/plugins/studio/llm-rate-limiter-multiphase/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/llm-rate-limiter-multiphase) + +**Capabilities**: PostAuth, Response, UI Provider + +**Description**: Comprehensive example showing a **multi-capability plugin** that implements rate limiting across the entire request/response lifecycle with a custom UI dashboard. + +**Key Features**: +- **PostAuth**: Check rate limits before proxying to LLM +- **Response**: Update counters after successful response +- **UI Provider**: Custom dashboard showing rate limit status +- KV storage for rate limit state +- Per-app and per-user rate limiting +- WebComponent-based UI +- Custom RPC methods for UI interaction + +**Use Cases**: +- Advanced rate limiting beyond built-in budget controls +- Multi-phase request processing +- Building plugins with custom UIs +- Stateful plugin logic with KV storage + +**Complexity**: Advanced + +--- + +### Hook Test Plugin + +**Path**: [`examples/plugins/studio/hook-test-plugin/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/hook-test-plugin) + +**Capabilities**: Object Hooks (all types) + +**Description**: Comprehensive testing plugin demonstrating all object hook types (before/after create/update/delete) for all supported objects (llm, datasource, tool, user). + +**Key Features**: +- Registers hooks for all 4 object types +- Demonstrates all 6 hook types per object +- Shows blocking vs non-blocking hooks +- Metadata enrichment patterns +- Priority ordering examples +- Extensive logging for debugging + +**Use Cases**: +- Learning object hook patterns +- Testing hook behavior +- Understanding hook execution order +- Reference implementation for all hooks + +**Complexity**: Intermediate + +--- + +### Service API Test + +**Path**: [`examples/plugins/studio/service-api-test/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/service-api-test) + +**Capabilities**: PostAuth (for testing purposes) + +**Description**: Comprehensive test plugin demonstrating all Studio Service API operations including LLMs, Tools, Apps, Datasources, Filters, Tags, and Plugins management. + +**Key Features**: +- Complete Studio Services API coverage +- CRUD operations for all object types +- Broker ID initialization +- Error handling patterns +- Service API authentication + +**Use Cases**: +- Learning Service API usage +- Testing Service API operations +- Reference for API method signatures +- Understanding broker connection setup + +**Complexity**: Advanced + +--- + +### Custom Auth UI + +**Path**: [`examples/plugins/studio/custom-auth-ui/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/custom-auth-ui) + +**Capabilities**: UI Provider, Auth + +**Description**: UI plugin with custom authentication extension. Shows how to add custom pages, sidebars, and authentication flows to the AI Studio dashboard. + +**Key Features**: +- Custom sidebar integration +- Route registration +- WebComponent implementation +- Asset serving (JS/CSS bundles) +- Custom authentication integration + +**Use Cases**: +- Extending dashboard UI +- Custom authentication flows +- Adding new admin pages +- WebComponent integration + +**Complexity**: Advanced + +--- + +### Portal Feedback + +**Path**: [`examples/plugins/studio/portal-feedback/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/portal-feedback) + +**Capabilities**: UI Provider, Portal UI Provider + +**Description**: Example plugin demonstrating portal UI with user feedback form. Shows how to build pages visible to end-users in the AI Portal alongside an admin dashboard for managing submissions. + +**Key Features**: +- Portal sidebar integration with group-based visibility +- Portal RPC with authenticated user context (`HandlePortalRPC`) +- Admin RPC for management operations (`HandleRPC`) +- Separate WebComponents for portal and admin UIs +- `waitForAPIAndLoad()` pattern for API injection timing +- Shared asset serving between admin and portal contexts + +**Use Cases**: +- Learning portal UI plugin development +- User-facing forms and data collection +- Dual admin/portal plugin architecture +- Portal RPC with user context + +**Complexity**: Beginner + +--- + +## Gateway Plugins + +### Custom Echo Endpoint + +**Path**: [`examples/plugins/gateway/custom-echo-endpoint/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/custom-echo-endpoint) + +**Capabilities**: CustomEndpointHandler, UIProvider, ConfigProvider + +**Description**: Demonstrates custom HTTP endpoints on the gateway combined with a Studio admin UI. Registers a catch-all endpoint at `/plugins/custom-echo-endpoint/` that echoes request metadata and user-configured content. + +**Key Features**: +- CustomEndpointHandler with `/*` catch-all registration +- Full request metadata echo (method, path, headers, query, body, path_segments) +- Studio UI (WebComponent) for editing custom content +- Config persistence via `ai_studio_sdk.UpdatePluginConfig()` +- Config sync from Studio → Gateway via gRPC ConfigurationSnapshot +- Manifest with `custom_endpoint` + `studio_ui` hooks + +**Use Cases**: +- Learning custom endpoint basics +- Understanding the config sync flow (Studio UI → DB → Gateway) +- Combining CustomEndpointHandler with UIProvider +- Reference implementation for custom endpoints + +**Complexity**: Beginner + +--- + +### Request Enricher + +**Path**: [`examples/plugins/gateway/request_enricher/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/request_enricher) + +**Capabilities**: PostAuth + +**Description**: Enriches authenticated requests with additional metadata and instructions before proxying to LLM. Most common gateway plugin pattern. + +**Key Features**: +- PostAuth hook implementation +- Header injection +- Request body modification +- Configurable enrichment via plugin config +- Context-aware enrichment (app_id, user_id) + +**Use Cases**: +- Adding custom headers +- Injecting additional instructions +- Request metadata enrichment +- Per-app request modification + +**Complexity**: Beginner + +--- + +### Response Modifier + +**Path**: [`examples/plugins/gateway/response_modifier/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/response_modifier) + +**Capabilities**: Response (OnBeforeWriteHeaders, OnBeforeWrite) + +**Description**: Modifies LLM responses before returning to client. Demonstrates two-phase response processing (headers then body). + +**Key Features**: +- Header modification (OnBeforeWriteHeaders) +- Body transformation (OnBeforeWrite) +- Response filtering +- Content injection +- Streaming response handling + +**Use Cases**: +- Content filtering and moderation +- Response formatting +- Adding custom response headers +- Injecting metadata into responses + +**Complexity**: Intermediate + +--- + +### Message Modifier + +**Path**: [`examples/plugins/gateway/message_modifier/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/message_modifier) + +**Capabilities**: PostAuth + +**Description**: Similar to request enricher but focuses on modifying the message content specifically for chat/completion requests. + +**Key Features**: +- Message content transformation +- Chat-specific request handling +- System message injection +- Context-aware modifications + +**Use Cases**: +- Chat message preprocessing +- System prompt injection +- Message format standardization + +**Complexity**: Beginner + +--- + +### Elasticsearch Collector + +**Path**: [`examples/plugins/gateway/legacy-collectors/elasticsearch_collector/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/legacy-collectors/elasticsearch_collector) + +**Capabilities**: DataCollector + +**Description**: Exports proxy logs, analytics, and budget data to Elasticsearch for external analysis and monitoring. + +**Key Features**: +- HandleProxyLog implementation +- HandleAnalytics implementation +- HandleBudgetUsage implementation +- Elasticsearch bulk API integration +- Async export with error handling +- Configurable index names + +**Use Cases**: +- Exporting logs to data warehouses +- Real-time analytics pipelines +- External monitoring systems +- Custom dashboards (Kibana, Grafana) + +**Complexity**: Advanced + +--- + +### Gateway Service Test + +**Path**: [`examples/plugins/gateway/gateway-service-test/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/gateway-service-test) + +**Capabilities**: PostAuth (for testing purposes) + +**Description**: Demonstrates all Gateway-specific Service API operations including app management, LLM info, budget status, and credential validation. + +**Key Features**: +- GetApp, ListApps +- GetLLM, ListLLMs +- GetBudgetStatus +- GetModelPrice +- ValidateCredential +- Runtime detection (Gateway vs Studio) + +**Use Cases**: +- Learning Gateway Services API +- Budget-aware request handling +- App configuration access +- Testing Gateway-specific features + +**Complexity**: Intermediate + +--- + +### Custom Echo Endpoint + +**Path**: [`examples/plugins/gateway/custom-echo-endpoint/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway/custom-echo-endpoint) + +**Capabilities**: CustomEndpoint, UI, Config + +**Description**: Serves a custom HTTP endpoint on the gateway that echoes back request metadata alongside user-configured custom content. Includes a Studio admin UI for editing the content. + +**Key Features**: +- Custom endpoint registration (catch-all `/*`) +- Request metadata echo (method, path, headers, query, body) +- Configurable custom content via Studio UI +- Config persistence via `UpdatePluginConfig` Service API +- Config sync from Studio to gateway via gRPC +- WebComponent-based admin UI + +**Use Cases**: +- Learning custom endpoint development +- Understanding Studio-to-gateway config flow +- Multi-capability plugin patterns (CustomEndpoint + UI + Config) +- MCP/webhook plugin starting point + +**Complexity**: Beginner + +--- + +### File Data Collectors (Unified SDK) + +**Path**: [`examples/plugins/data-collectors/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/data-collectors) + +**Capabilities**: DataCollector + +**Examples**: +- `file-proxy-collector/` - Exports proxy logs to JSONL files +- `file-analytics-collector/` - Exports analytics to CSV or JSONL files +- `file-budget-collector/` - Exports budget data to CSV or JSONL files with optional aggregation + +**Description**: Simple file-based data collectors for testing and local development. Write telemetry data (proxy logs, analytics, budget usage) to files instead of or in addition to the database. + +**Features**: +- Multiple output formats (CSV, JSONL) +- Daily log rotation +- Optional aggregate summaries (budget collector) +- Configurable output directories +- Environment variable support +- Replace or supplement database storage + +**Use Cases**: +- Local development and debugging +- Understanding DataCollector interface +- Simple log export without external dependencies +- Custom analytics pipelines +- Reduced database load in high-throughput scenarios +- Backup and archival of telemetry data + +**Configuration Example**: +```yaml +data_collection_plugins: + - name: "analytics-files" + path: "./examples/plugins/unified/data-collectors/file-analytics-collector/file_analytics_collector" + enabled: true + priority: 200 + replace_database: false + hook_types: + - "analytics" + config: + output_directory: "./data/collected/analytics" + enabled: "true" + format: "jsonl" # or "csv" +``` + +**Complexity**: Beginner + +**Migration Status**: ✅ Migrated to unified SDK. Old examples remain available at [`examples/plugins/gateway/file_*_collector/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/gateway) for reference. + +--- + +## Example Organization + +### By Capability + +| Capability | Examples | +|------------|----------| +| **Agent** | echo-agent | +| **Object Hooks** | llm-validator, hook-test-plugin | +| **PreAuth** | **llm-firewall** ★ | +| **PostAuth** | **llm-cache** ★, **llm-firewall** ★, request_enricher, message_modifier, service-api-test, gateway-service-test | +| **Response** | **llm-cache** ★, response_modifier, llm-rate-limiter-multiphase | +| **DataCollector** | elasticsearch_collector, file-analytics-collector, file-budget-collector, file-proxy-collector | +| **UI Provider** | **llm-cache** ★, **llm-firewall** ★, llm-rate-limiter-multiphase, custom-auth-ui, portal-feedback | +| **Portal UI** | portal-feedback | +| **Scheduler** | **github-rag-ingest** ★ | +| **EdgePayloadReceiver** | **llm-cache** ★ | +| **Custom Endpoints** | custom-echo-endpoint | +| **Multi-Capability** | **llm-cache** ★ (8 capabilities), llm-rate-limiter-multiphase (PostAuth + Response + UI), custom-echo-endpoint (CustomEndpoint + UI + Config) | + +★ = Production-ready community/enterprise plugins (recommended as reference) + +### By Runtime + +| Runtime | Examples | +|---------|----------| +| **Studio Only** | echo-agent, llm-validator, hook-test-plugin, llm-rate-limiter-multiphase, custom-auth-ui, portal-feedback, service-api-test | +| **Gateway Only** | request_enricher, response_modifier, message_modifier, elasticsearch_collector, gateway-service-test, file-analytics-collector, file-budget-collector, file-proxy-collector | +| **Both** | custom-echo-endpoint (UI in Studio + CustomEndpoint on Gateway) | + +### By Complexity + +| Level | Examples | +|-------|----------| +| **Beginner** | echo-agent, portal-feedback, request_enricher, message_modifier, custom-echo-endpoint, file-analytics-collector, file-budget-collector, file-proxy-collector | +| **Intermediate** | llm-validator, hook-test-plugin, response_modifier, gateway-service-test | +| **Advanced** | llm-rate-limiter-multiphase, service-api-test, elasticsearch_collector, custom-auth-ui | + +## Running Examples + +### Build an Example + +```bash +cd examples/plugins/studio/echo-agent +go build -o echo-agent server/main.go +``` + +### Deploy to AI Studio + +```bash +# Create plugin +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "name": "Echo Agent", + "slug": "echo-agent", + "command": "file:///path/to/echo-agent", + "hook_type": "agent", + "plugin_type": "agent", + "is_active": true + }' +``` + +### Deploy to Gateway + +Configure in gateway config: + +```yaml +plugins: + - name: request-enricher + command: file:///path/to/request-enricher + enabled: true +``` + +## Common Patterns + +### 1. Basic Plugin Structure + +All examples follow this pattern: + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + +type MyPlugin struct { + plugin_sdk.BasePlugin +} + +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin("name", "version", "desc"), + } +} + +func main() { + plugin_sdk.Serve(NewMyPlugin()) +} +``` + +### 2. Configuration Handling + +```go +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Parse plugin-specific config + p.apiKey = config["api_key"] + + // Extract broker ID for Service API access (Studio plugins) + if brokerIDStr, ok := config["_service_broker_id"]; ok { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + } + + return nil +} +``` + +### 3. Universal Services + +```go +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Logging + ctx.Services.Logger().Info("Processing", "app_id", ctx.AppID) + + // KV storage + data, _ := ctx.Services.KV().Read(ctx, "key") + ctx.Services.KV().Write(ctx, "key", []byte("value")) + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### 4. Runtime Detection + +```go +if ctx.Runtime == plugin_sdk.RuntimeStudio { + // Studio-specific code + llms, _ := ctx.Services.Studio().ListLLMs(ctx, 1, 10) +} else if ctx.Runtime == plugin_sdk.RuntimeGateway { + // Gateway-specific code + app, _ := ctx.Services.Gateway().GetApp(ctx, ctx.AppID) +} +``` + diff --git a/ai-management/ai-studio/plugins/manifests.mdx b/ai-management/ai-studio/plugins/manifests.mdx new file mode 100644 index 0000000000..9e5ba007b7 --- /dev/null +++ b/ai-management/ai-studio/plugins/manifests.mdx @@ -0,0 +1,746 @@ +--- +title: "Tyk AI Studio Plugin Manifests" +description: "How to define plugin manifests for Tyk AI Studio plugins?" +keywords: "AI Studio, AI Management, Plugin Manifests, Plugin Development" +sidebarTitle: "Manifests" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph TD + A[Plugin Manifest] --> B[Metadata] + A --> C[Capabilities] + A --> D[Permissions] + A --> E[UI Integration] +``` + + +Plugin manifests define plugin metadata, capabilities, permissions, and UI integration. Understanding manifests is essential for building secure, well-integrated plugins. + +## Manifest Structure + +### Edge Gateway Plugins + +Edge Gateway plugins don't use JSON manifests. Configuration is provided via the API when creating the plugin: + +```json +{ + "name": "Custom Auth Plugin", + "slug": "custom-auth", + "description": "Custom authentication logic", + "command": "file:///path/to/plugin", + "hook_type": "auth", + "plugin_type": "gateway", + "config": { + "valid_token": "secret123" + }, + "is_active": true +} +``` + +Hook types for Edge Gateway plugins: +- `pre_auth` - Before authentication +- `auth` - Custom authentication +- `post_auth` - After authentication +- `on_response` - Response modification +- `data_collection` - Data export + +### AI Studio UI Plugins + +Complete manifest structure for UI plugins: + +```json Expandable +{ + "id": "com.example.plugin", + "name": "My Plugin", + "version": "1.0.0", + "description": "Plugin description", + "plugin_type": "ai_studio", + "permissions": { + "services": [ + "llms.read", + "llms.write", + "llms.proxy", + "tools.read", + "tools.execute", + "tools.write", + "datasources.read", + "datasources.query", + "apps.read", + "apps.write", + "plugins.read", + "analytics.read", + "kv.read", + "kv.readwrite" + ] + }, + "ui": { + "slots": [ + { + "slot": "sidebar.section", + "label": "My Plugin", + "icon": "/assets/icon.svg", + "items": [ + { + "type": "route", + "path": "/admin/my-plugin", + "title": "Dashboard", + "mount": { + "kind": "webc", + "tag": "my-plugin-dashboard", + "entry": "/ui/webc/dashboard.js", + "props": { + "apiBase": "/plugin/com.example.plugin/rpc" + } + } + } + ] + } + ] + }, + "rpc": { + "basePath": "/plugin/com.example.plugin/rpc" + }, + "assets": [ + "/assets/icon.svg", + "/ui/webc/dashboard.js" + ], + "compat": { + "app": ">=2.6 <3.0", + "api": ["ui-v1", "kv-v1", "rpc-v1"] + }, + "security": { + "csp": "script-src 'self'; object-src 'none'" + } +} +``` + +### AI Studio Agent Plugins + +Agent plugin manifests are simpler (no UI): + +```json Expandable +{ + "id": "com.example.agent", + "name": "My Agent", + "version": "1.0.0", + "description": "Custom conversational agent", + "plugin_type": "agent", + "permissions": { + "services": [ + "llms.proxy", + "tools.execute", + "datasources.query", + "kv.readwrite" + ] + }, + "ui": { + "slots": [] + } +} +``` + +### Object Hooks Plugins + +Object hooks plugins intercept CRUD operations on platform objects. These plugins use the unified SDK and register hooks programmatically, so the manifest doesn't need special hook configuration. + +#### Basic Object Hooks Manifest + +```json Expandable +{ + "id": "com.example.validator", + "name": "LLM Validator", + "version": "1.0.0", + "description": "Validates LLM configurations before saving", + "plugin_type": "ai_studio", + "permissions": { + "services": [ + "llms.read", + "kv.readwrite" + ] + }, + "ui": { + "slots": [] + } +} +``` + +**Note**: Object hooks are registered via the `GetObjectHookRegistrations()` method in the plugin code, not in the manifest. The plugin_type is `"ai_studio"` since object hooks are Studio-only. + +#### Hook Registration (Code, Not Manifest) + +Object hooks are registered programmatically: + +```go Expandable +func (p *ValidatorPlugin) GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) { + return []*pb.ObjectHookRegistration{ + { + ObjectType: "llm", // llm, datasource, tool, user + HookTypes: []string{ // before_create, after_create, etc. + "before_create", + "before_update", + }, + Priority: 10, // Lower runs first + }, + { + ObjectType: "datasource", + HookTypes: []string{"before_create"}, + Priority: 5, // Runs before priority 10 + }, + }, nil +} +``` + +**Supported Objects**: +- `llm` - LLM provider configurations +- `datasource` - Data source connections +- `tool` - External tool definitions +- `user` - User accounts + +**Supported Hook Types** (per object): +- `before_create` - Before object creation (can block) +- `after_create` - After object creation (notification only) +- `before_update` - Before object update (can block) +- `after_update` - After object update (notification only) +- `before_delete` - Before object deletion (can block) +- `after_delete` - After object deletion (notification only) + +**Priority**: Lower numbers run first (e.g., priority 5 runs before priority 10). Use priority to control hook execution order when multiple plugins register hooks for the same object/event. + +#### Multi-Capability with Hooks + +Object hooks plugins can combine hooks with UI: + +```json Expandable +{ + "id": "com.example.approval-system", + "name": "Approval System", + "version": "1.0.0", + "description": "Requires approval for datasource creation", + "plugin_type": "ai_studio", + "permissions": { + "services": [ + "datasources.read", + "kv.readwrite" + ] + }, + "ui": { + "slots": [ + { + "slot": "sidebar.section", + "label": "Approvals", + "items": [ + { + "type": "route", + "path": "/admin/approvals", + "title": "Pending Approvals", + "mount": { + "kind": "webc", + "tag": "approval-dashboard", + "entry": "/ui/webc/approvals.js" + } + } + ] + } + ] + }, + "rpc": { + "basePath": "/plugin/com.example.approval-system/rpc" + } +} +``` + +This plugin would: +1. Register `before_create` hooks for datasources (via code) +2. Block creation and add to pending approvals +3. Provide UI dashboard to approve/reject +4. Use RPC methods for approval actions + +See `examples/plugins/studio/llm-validator/` and `examples/plugins/studio/hook-test-plugin/` for complete examples. + +### Resource Provider Plugins + +Resource Provider plugins register custom resource types that integrate into the App creation flow. Declare resource types in the manifest's `resource_types` section: + +```json Expandable +{ + "id": "com.example.mcp-registry", + "name": "MCP Registry", + "version": "1.0.0", + "description": "Manages MCP server resources", + "capabilities": { + "hooks": ["resource_provider", "studio_ui"] + }, + "resource_types": [ + { + "slug": "mcp_servers", + "name": "MCP Servers", + "description": "Model Context Protocol servers", + "icon": "Hub", + "has_privacy_score": true, + "supports_submissions": true, + "form_component": { + "tag": "mcp-server-selector", + "entry_point": "ui/webc/mcp-selector.js" + } + } + ], + "permissions": { + "services": ["apps.read", "kv.readwrite"] + } +} +``` + +Resource types declared in the manifest are automatically registered when the plugin loads. The plugin also implements `ResourceProvider` methods programmatically for runtime behavior. + +| Field | Required | Description | +|-------|:---:|-------------| +| `slug` | Yes | Machine-readable identifier, unique per plugin | +| `name` | Yes | Display name shown in the App form | +| `has_privacy_score` | No | Whether instances carry privacy scores (default: `false`) | +| `supports_submissions` | No | Whether community users can submit instances (default: `false`) | +| `form_component` | No | Custom Web Component for the App form selector | + +See [Resource Provider Plugins](/ai-management/ai-studio/plugins/resource-types) for the full guide. + +## Service Scopes Reference + +### LLM Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `llms.read` | Read LLM configurations | List LLMs, Get LLM details, Get LLM counts | +| `llms.write` | Create/update LLMs | Create LLM, Update LLM, Delete LLM | +| `llms.proxy` | Call LLMs via proxy | CallLLM (streaming/non-streaming) | + +### Tool Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `tools.read` | Read tool configurations | List Tools, Get Tool details | +| `tools.execute` | Execute tools | ExecuteTool with operation and parameters | +| `tools.write` | Create/update tools | Create Tool, Update Tool, Delete Tool | +| `tools.operations` | Manage tool operations | Add/remove operations | + +### Datasource Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `datasources.read` | Read datasource configurations | List Datasources, Get Datasource details | +| `datasources.query` | Query datasources | QueryDatasource with SQL/query DSL | +| `datasources.write` | Create/update datasources | Create, Update, Delete Datasources | + +### App Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `apps.read` | Read app configurations | List Apps, Get App details | +| `apps.write` | Create/update apps | Create App, Update App, Delete App | + +### Plugin Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `plugins.read` | Read plugin configurations | List Plugins, Get Plugin details, Get counts | +| `plugins.write` | Create/update plugins | Create Plugin, Update Plugin | + +### KV Storage Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `kv.read` | Read plugin KV storage | ReadPluginKV, ListPluginKVKeys | +| `kv.readwrite` | Read/write plugin KV storage | Read + WritePluginKV, DeletePluginKV | + +### Analytics Scopes + +| Scope | Description | Operations | +|-------|-------------|------------| +| `analytics.read` | Read analytics data | GetUsageStats, GetCostAnalytics | +| `analytics.write` | Write analytics data | RecordCustomMetric | + +## UI Slot System + +### Available Slots + +#### sidebar.section + +Add a collapsible section to the sidebar with nested items: + +```json Expandable +{ + "slot": "sidebar.section", + "label": "My Plugin", + "icon": "/assets/icon.svg", + "items": [ + { + "type": "route", + "path": "/admin/my-plugin/dashboard", + "title": "Dashboard", + "mount": { } + }, + { + "type": "route", + "path": "/admin/my-plugin/settings", + "title": "Settings", + "mount": { } + } + ] +} +``` + +#### sidebar.link + +Add a single link to the sidebar: + +```json +{ + "slot": "sidebar.link", + "label": "Quick Action", + "icon": "/assets/icon.svg", + "path": "/admin/quick-action" +} +``` + +#### settings.section + +Add a section to the Settings page: + +```json +{ + "slot": "settings.section", + "label": "Plugin Settings", + "path": "/admin/settings/plugin", + "mount": { + "kind": "webc", + "tag": "plugin-settings", + "entry": "/ui/webc/settings.js" + } +} +``` + +#### app.detail.tab + +Add a tab to App detail pages: + +```json +{ + "slot": "app.detail.tab", + "label": "Custom View", + "mount": { + "kind": "webc", + "tag": "app-custom-view", + "entry": "/ui/webc/app-view.js", + "props": { + "appId": "{{app.id}}" + } + } +} +``` + +#### llm.detail.tab + +Add a tab to LLM detail pages: + +```json +{ + "slot": "llm.detail.tab", + "label": "Analytics", + "mount": { + "kind": "webc", + "tag": "llm-analytics", + "entry": "/ui/webc/llm-analytics.js", + "props": { + "llmId": "{{llm.id}}" + } + } +} +``` + +### Mount Configuration + +#### WebComponent Mount + +```json +{ + "kind": "webc", + "tag": "my-component", + "entry": "/ui/webc/component.js", + "props": { + "apiBase": "/plugin/com.example/rpc", + "theme": "dark" + } +} +``` + +- `kind`: Must be `"webc"` for WebComponents +- `tag`: Custom element tag name +- `entry`: Path to JavaScript file (relative to plugin) +- `props`: Properties passed to the component + +## Permission Validation + +Permissions are validated when plugins call the Service API: + +```go +// This call requires "llms.read" scope +llmsResp, err := ai_studio_sdk.ListLLMs(ctx, 1, 10) +if err != nil { + // Error: insufficient permissions + log.Printf("Permission denied: %v", err) +} +``` + +If your plugin doesn't declare the required scope in its manifest, Service API calls will fail with permission errors. + +## Configuration Schema + +Plugins can provide JSON Schema for their configuration: + +```go Expandable +func (p *MyPlugin) GetConfigSchema() ([]byte, error) { + schema := map[string]interface{}{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "My Plugin Configuration", + "properties": map[string]interface{}{ + "api_key": map[string]interface{}{ + "type": "string", + "description": "API key for external service", + "minLength": 1, + }, + "endpoint": map[string]interface{}{ + "type": "string", + "format": "uri", + "description": "Service endpoint URL", + "default": "https://api.example.com", + }, + "rate_limit": map[string]interface{}{ + "type": "integer", + "description": "Requests per minute", + "minimum": 1, + "maximum": 1000, + "default": 100, + }, + "enabled": map[string]interface{}{ + "type": "boolean", + "description": "Enable plugin", + "default": true, + }, + }, + "required": []string{"api_key"}, + } + + return json.Marshal(schema) +} +``` + +The platform uses this schema to: +- Validate configuration on save +- Generate UI forms +- Provide inline documentation +- Set default values + +## Security Best Practices + +### Principle of Least Privilege + +Only request scopes your plugin actually needs: + +```json +{ + "permissions": { + "services": [ + "llms.read", // ✅ Need to list LLMs + "kv.readwrite" // ✅ Need to store settings + // ❌ Don't add "llms.write" if not creating LLMs + // ❌ Don't add "llms.proxy" if not calling LLMs + ] + } +} +``` + +### Content Security Policy + +Define CSP headers for UI plugins: + +```json +{ + "security": { + "csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; object-src 'none'; frame-ancestors 'none'" + } +} +``` + +### Input Validation + +Always validate inputs in RPC methods: + +```go Expandable +func (p *MyPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + // Validate method + allowedMethods := map[string]bool{ + "get_data": true, + "save_config": true, + } + + if !allowedMethods[method] { + return nil, fmt.Errorf("invalid method: %s", method) + } + + // Validate payload + var data map[string]interface{} + if err := json.Unmarshal(payload, &data); err != nil { + return nil, fmt.Errorf("invalid payload: %w", err) + } + + // Additional validation... + return p.processMethod(method, data) +} +``` + +### Secrets Management + +Never hardcode secrets in manifests or code: + +```go +// ❌ Bad: Hardcoded secret +func (p *MyPlugin) OnInitialize(...) error { + p.apiKey = "secret123" // Don't do this! +} + +// ✅ Good: Configuration from secure storage +func (p *MyPlugin) OnInitialize(...) error { + // Read from config (stored securely by platform) + config, _ := ai_studio_sdk.ReadPluginKV(ctx, "config") + var cfg Config + json.Unmarshal(config, &cfg) + p.apiKey = cfg.APIKey +} +``` + +## Versioning and Compatibility + +### Semantic Versioning + +Use semantic versioning for plugin versions: + +```json +{ + "version": "1.2.3" // MAJOR.MINOR.PATCH +} +``` + +- MAJOR: Breaking changes +- MINOR: New features, backward compatible +- PATCH: Bug fixes, backward compatible + +### Compatibility Declaration + +Declare platform compatibility: + +```json +{ + "compat": { + "app": ">=2.6 <3.0", + "api": ["ui-v1", "kv-v1", "rpc-v1"] + } +} +``` + +## Testing Manifests + +### Validation + +Validate your manifest before deployment: + +```bash +# Check JSON syntax +cat plugin.manifest.json | jq . + +# Validate required fields +jq '.id, .name, .version, .plugin_type' plugin.manifest.json +``` + +### Common Errors + +| Error | Cause | Solution | +|-------|-------|----------| +| "Invalid plugin_type" | Wrong plugin type | Use `"ai_studio"` or `"agent"` | +| "Missing required field" | Missing manifest field | Add `id`, `name`, `version` | +| "Invalid scope" | Unknown service scope | Check scope name spelling | +| "Duplicate UI slot" | Slot registered twice | Remove duplicate slot | +| "Asset not found" | Missing embedded file | Verify `//go:embed` directive | + +## Complete Examples + +### Minimal UI Plugin + +```json Expandable +{ + "id": "com.example.minimal", + "name": "Minimal Plugin", + "version": "1.0.0", + "plugin_type": "ai_studio", + "permissions": { + "services": ["kv.readwrite"] + }, + "ui": { + "slots": [ + { + "slot": "sidebar.link", + "label": "Minimal", + "path": "/admin/minimal" + } + ] + } +} +``` + +### Full-Featured UI Plugin + +See [plugins-studio-ui.md](/ai-management/ai-studio/plugins/studio-ui) for complete rate-limiting-ui example. + +### Minimal Agent Plugin + +```json +{ + "id": "com.example.simple-agent", + "name": "Simple Agent", + "version": "1.0.0", + "plugin_type": "agent", + "permissions": { + "services": ["llms.proxy"] + }, + "ui": { + "slots": [] + } +} +``` + +### Advanced Agent Plugin + +```json Expandable +{ + "id": "com.example.rag-agent", + "name": "RAG Agent", + "version": "1.0.0", + "description": "Agent with retrieval-augmented generation", + "plugin_type": "agent", + "permissions": { + "services": [ + "llms.proxy", + "datasources.query", + "tools.execute", + "kv.readwrite", + "analytics.read" + ] + }, + "ui": { + "slots": [] + } +} +``` diff --git a/ai-management/ai-studio/plugins/object-hooks.mdx b/ai-management/ai-studio/plugins/object-hooks.mdx new file mode 100644 index 0000000000..37332b9979 --- /dev/null +++ b/ai-management/ai-studio/plugins/object-hooks.mdx @@ -0,0 +1,625 @@ +--- +title: "Object Hooks Plugin in Tyk AI Studio" +description: "Learn how to use Object Hooks plugins in Tyk AI Studio to intercept and modify requests and responses for specific AI providers and models." +keywords: "AI Studio, AI Management, Plugins, Object Hooks" +sidebarTitle: "Object Hooks" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +## Object Hooks Plugin Guide + + +```mermaid +graph TD + A[CRUD Operation] --> B{Object Hook} + B -->|Pre-Hook| C[Validate/Enrich] + B -->|Post-Hook| D[Audit/Sync] +``` + + +Object Hooks are a powerful plugin capability that allows you to **intercept and control CRUD operations** on AI Studio objects before they are saved to the database. This enables validation, enrichment, policy enforcement, and integration with external systems. + +## Overview + +Object Hooks provide plugins with the ability to: + +- **Validate** objects before creation or modification +- **Reject** operations that don't meet requirements +- **Enrich** objects with additional data +- **Enforce policies** (security, compliance, naming conventions) +- **Integrate** with external systems (ticketing, approval workflows) +- **Audit** changes with custom metadata +- **Transform** data before it reaches the database + +### Supported Objects + +Object Hooks work with four core AI Studio object types: + +| Object Type | Description | Common Use Cases | +|-------------|-------------|------------------| +| **llm** | LLM Providers | Validate endpoints, enforce HTTPS, check privacy scores | +| **datasource** | Data Sources | Validate connections, enforce security policies | +| **tool** | External Tools | Validate OpenAPI specs, check endpoints | +| **user** | Users | Enforce naming conventions, integrate with LDAP/AD | + +### Hook Types + +Each object type supports six hook types that fire at different points in the lifecycle: + +| Hook Type | When It Fires | Can Block? | Can Modify Object? | Common Use Cases | +|-----------|---------------|------------|-------------------|------------------| +| **before_create** | Before object creation | Yes | Yes | Validation, default values, policy enforcement | +| **after_create** | After object creation | No | No | Notifications, external system sync, audit logging | +| **before_update** | Before object update | Yes | Yes | Change validation, approval workflows | +| **after_update** | After object update | No | No | Change notifications, audit trails | +| **before_delete** | Before object deletion | Yes | No | Prevent deletion, cleanup checks | +| **after_delete** | After object deletion | No | No | Cleanup external resources, notifications | + +**Important Notes:** +- `before_*` hooks can block operations by setting `AllowOperation: false` +- `after_*` hooks are informational only and cannot block operations +- Only `before_create` and `before_update` can modify the object itself +- All hooks can add plugin metadata that is stored with the object + +## Implementing Object Hooks + +### Step 1: Implement ObjectHookHandler Interface + +```go +type ObjectHookHandler interface { + GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) + HandleObjectHook(ctx Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) +} +``` + +### Step 2: Register Your Hooks + +Declare which object types and hook types your plugin handles: + +```go Expandable +func (p *MyPlugin) GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) { + return []*pb.ObjectHookRegistration{ + { + ObjectType: "llm", + HookTypes: []string{"before_create", "before_update"}, + Priority: 10, // Lower numbers run first + }, + { + ObjectType: "datasource", + HookTypes: []string{"before_create"}, + Priority: 20, + }, + }, nil +} +``` + +**Priority Ordering:** +- Lower priority numbers execute **first** (e.g., priority 10 runs before priority 50) +- Use priorities to control execution order when multiple plugins handle the same hook +- Common priority ranges: + - `1-10`: Critical validation that should run first + - `11-49`: Standard validation and policy enforcement + - `50-89`: Enrichment and transformation + - `90-99`: Logging and audit (should run last) + +### Step 3: Handle Hook Invocations + +Process the hook request and return a response: + +```go Expandable +func (p *MyPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + // Parse the object + var obj MyObjectType + if err := json.Unmarshal([]byte(req.ObjectJson), &obj); err != nil { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: fmt.Sprintf("Invalid object data: %v", err), + }, nil + } + + // Perform validation/processing + if err := validateObject(&obj); err != nil { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: err.Error(), + Message: fmt.Sprintf("Validation failed: %v", err), + }, nil + } + + // Allow operation with metadata + return &pb.ObjectHookResponse{ + AllowOperation: true, + PluginMetadata: map[string]string{ + "validated_by": "my-plugin", + "validated_at": time.Now().Format(time.RFC3339), + }, + Message: "Validation passed", + }, nil +} +``` + +## Hook Request Structure + +The `ObjectHookRequest` provides context about the operation: + +```go +type ObjectHookRequest struct { + ObjectType string // "llm", "datasource", "tool", or "user" + ObjectID uint32 // Object ID (0 for create operations) + ObjectJson string // JSON representation of the object + HookType string // e.g., "before_create", "after_update" + OperationId string // Unique operation identifier + UserId uint32 // User performing the operation + PreviousJson string // Previous state (for updates only) + Metadata map[string]string // Additional context +} +``` + +**Key Fields:** +- `ObjectJson`: The object being created/updated/deleted (JSON string) +- `PreviousJson`: Previous object state for update operations (compare before/after) +- `ObjectID`: 0 for create operations, actual ID for update/delete +- `OperationId`: Unique ID for correlating logs and debugging + +## Hook Response Structure + +Your plugin returns an `ObjectHookResponse`: + +```go +type ObjectHookResponse struct { + AllowOperation bool // true = allow, false = block (before_* only) + RejectionReason string // Error message when blocking + Modified bool // true if ModifiedObjectJson is provided + ModifiedObjectJson string // Modified object (before_create/before_update only) + PluginMetadata map[string]string // Metadata to store with object + Message string // User-friendly message +} +``` + +**Response Patterns:** + +### 1. Allow Without Changes +```go +return &pb.ObjectHookResponse{ + AllowOperation: true, + Modified: false, +}, nil +``` + +### 2. Allow With Metadata +```go +return &pb.ObjectHookResponse{ + AllowOperation: true, + PluginMetadata: map[string]string{ + "validated": "true", + "validator_version": "1.0", + }, + Message: "Validation passed", +}, nil +``` + +### 3. Block Operation +```go +return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "API endpoint must use HTTPS", + Message: "Security policy violation", +}, nil +``` + +### 4. Modify Object (before_create/before_update only) +```go Expandable +// Modify the object +obj.Name = strings.ToUpper(obj.Name) +obj.Metadata["enriched"] = "true" + +// Marshal back to JSON +modifiedJSON, _ := json.Marshal(obj) + +return &pb.ObjectHookResponse{ + AllowOperation: true, + Modified: true, + ModifiedObjectJson: string(modifiedJSON), + PluginMetadata: map[string]string{ + "enriched_by": "my-plugin", + }, +}, nil +``` + +## Complete Example: LLM Validator + +This example validates LLM objects to enforce security and quality policies: + +```go Expandable +package main + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +type LLMValidatorPlugin struct { + plugin_sdk.BasePlugin + config *Config +} + +type Config struct { + RequireHTTPS bool `json:"require_https"` + BlockedVendors []string `json:"blocked_vendors"` + MinPrivacyScore int `json:"min_privacy_score"` + RequireDescription bool `json:"require_description"` +} + +type LLM struct { + ID uint `json:"id"` + Name string `json:"name"` + APIEndpoint string `json:"api_endpoint"` + Vendor string `json:"vendor"` + PrivacyScore int `json:"privacy_score"` + ShortDescription string `json:"short_description"` +} + +func NewLLMValidatorPlugin() *LLMValidatorPlugin { + return &LLMValidatorPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "llm-validator", + "1.0.0", + "Validates LLM configurations", + ), + config: &Config{ + RequireHTTPS: true, + BlockedVendors: []string{}, + MinPrivacyScore: 0, + RequireDescription: true, + }, + } +} + +func (p *LLMValidatorPlugin) Init(ctx plugin_sdk.Context, config map[string]string) error { + if configJSON, ok := config["config"]; ok { + if err := json.Unmarshal([]byte(configJSON), p.config); err != nil { + return fmt.Errorf("failed to parse config: %w", err) + } + } + return nil +} + +// Register hooks for LLM objects +func (p *LLMValidatorPlugin) GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) { + return []*pb.ObjectHookRegistration{ + { + ObjectType: "llm", + HookTypes: []string{"before_create", "before_update"}, + Priority: 10, // Run early + }, + }, nil +} + +// Handle hook invocations +func (p *LLMValidatorPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + // Only handle LLM objects + if req.ObjectType != "llm" { + return &pb.ObjectHookResponse{AllowOperation: true}, nil + } + + // Parse LLM object + var llm LLM + if err := json.Unmarshal([]byte(req.ObjectJson), &llm); err != nil { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: fmt.Sprintf("Invalid LLM data: %v", err), + }, nil + } + + // Run validations + if err := p.validateLLM(&llm); err != nil { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: err.Error(), + Message: fmt.Sprintf("LLM validation failed: %v", err), + }, nil + } + + // Add validation metadata + return &pb.ObjectHookResponse{ + AllowOperation: true, + PluginMetadata: map[string]string{ + "validated_by": "llm-validator", + "validation_rules": fmt.Sprintf("https=%v,privacy>=%d", + p.config.RequireHTTPS, p.config.MinPrivacyScore), + }, + Message: fmt.Sprintf("LLM '%s' validated successfully", llm.Name), + }, nil +} + +func (p *LLMValidatorPlugin) validateLLM(llm *LLM) error { + // Check HTTPS requirement + if p.config.RequireHTTPS && llm.APIEndpoint != "" { + if !strings.HasPrefix(strings.ToLower(llm.APIEndpoint), "https://") { + return fmt.Errorf("API endpoint must use HTTPS (got: %s)", llm.APIEndpoint) + } + } + + // Check blocked vendors + for _, blocked := range p.config.BlockedVendors { + if strings.EqualFold(llm.Vendor, blocked) { + return fmt.Errorf("vendor '%s' is blocked by policy", llm.Vendor) + } + } + + // Check minimum privacy score + if llm.PrivacyScore < p.config.MinPrivacyScore { + return fmt.Errorf("privacy score %d is below minimum %d", + llm.PrivacyScore, p.config.MinPrivacyScore) + } + + // Check description requirement + if p.config.RequireDescription && strings.TrimSpace(llm.ShortDescription) == "" { + return fmt.Errorf("short description is required") + } + + return nil +} + +func main() { + plugin_sdk.Serve(NewLLMValidatorPlugin()) +} +``` + +## Use Case Examples + +### 1. Enforce HTTPS for LLM Endpoints + +```go Expandable +func (p *SecurityPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + if req.ObjectType == "llm" && strings.HasPrefix(req.HookType, "before_") { + var llm LLM + json.Unmarshal([]byte(req.ObjectJson), &llm) + + if llm.APIEndpoint != "" && !strings.HasPrefix(llm.APIEndpoint, "https://") { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "API endpoint must use HTTPS for security", + }, nil + } + } + return &pb.ObjectHookResponse{AllowOperation: true}, nil +} +``` + +### 2. Auto-Enrich Objects with Defaults + +```go Expandable +func (p *EnrichmentPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + if req.ObjectType == "user" && req.HookType == "before_create" { + var user User + json.Unmarshal([]byte(req.ObjectJson), &user) + + // Add default role if not specified + if user.Role == "" { + user.Role = "viewer" + } + + // Add organization metadata + if user.Metadata == nil { + user.Metadata = make(map[string]interface{}) + } + user.Metadata["created_by"] = "auto-provisioning" + + modifiedJSON, _ := json.Marshal(user) + return &pb.ObjectHookResponse{ + AllowOperation: true, + Modified: true, + ModifiedObjectJson: string(modifiedJSON), + }, nil + } + return &pb.ObjectHookResponse{AllowOperation: true}, nil +} +``` + +### 3. Integration with External Approval System + +```go Expandable +func (p *ApprovalPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + // Require approval for datasource creation + if req.ObjectType == "datasource" && req.HookType == "before_create" { + // Check if operation has approval metadata + approvalID, hasApproval := req.Metadata["approval_id"] + + if !hasApproval { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "Datasource creation requires manager approval. Please submit a request.", + }, nil + } + + // Verify approval with external system + if !p.verifyApproval(ctx, approvalID) { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "Invalid or expired approval", + }, nil + } + } + return &pb.ObjectHookResponse{AllowOperation: true}, nil +} +``` + +### 4. Audit Trail with Change Tracking + +```go Expandable +func (p *AuditPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + if req.HookType == "before_update" { + // Compare old and new + changes := p.detectChanges(req.PreviousJson, req.ObjectJson) + + // Log to external system + p.logAudit(ctx, AuditEntry{ + ObjectType: req.ObjectType, + ObjectID: req.ObjectID, + UserID: req.UserId, + Changes: changes, + Timestamp: time.Now(), + }) + } + + return &pb.ObjectHookResponse{ + AllowOperation: true, + PluginMetadata: map[string]string{ + "audit_logged": "true", + "audit_id": generateAuditID(), + }, + }, nil +} +``` + +### 5. Prevent Deletion of Active Resources + +```go Expandable +func (p *ProtectionPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + if req.HookType == "before_delete" { + // Check if LLM is actively being used + if req.ObjectType == "llm" { + activeApps, err := p.getActiveApps(ctx, req.ObjectID) + if err != nil { + ctx.Services.Logger().Error("Failed to check active apps", "error", err) + } + + if len(activeApps) > 0 { + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: fmt.Sprintf("Cannot delete LLM: in use by %d application(s)", len(activeApps)), + }, nil + } + } + } + return &pb.ObjectHookResponse{AllowOperation: true}, nil +} +``` + +## Best Practices + +### Validation +- **Fail fast**: Perform quick checks first to avoid expensive operations +- **Clear messages**: Provide specific, actionable error messages +- **Log verbosely**: Use `ctx.Services.Logger()` for debugging +- **Handle errors**: Always check JSON unmarshaling errors + +### Performance +- **Keep hooks lightweight**: Hooks run synchronously in the request path +- **Cache lookups**: Use KV storage for repeated validations +- **Timeout external calls**: Use context timeouts for external APIs +- **Async for after_ hooks**: Use goroutines for non-critical after_ hook work + +### Security +- **Validate all inputs**: Never trust object data +- **Check permissions**: Use `req.UserId` to enforce RBAC +- **Sanitize output**: Don't leak sensitive data in error messages +- **Audit changes**: Log all modifications for compliance + +### Metadata +- **Use consistent keys**: Establish naming conventions (e.g., `plugin_name:key`) +- **Version your metadata**: Include plugin version for debugging +- **Don't overload**: Keep metadata concise, use external storage for large data +- **Document schema**: Document what metadata your plugin adds + +### Error Handling +- **Block vs. log**: Use `AllowOperation: false` sparingly +- **Helpful messages**: User sees `Message` field, make it clear +- **Differentiate errors**: Use `RejectionReason` for why it failed +- **Return nil error**: Return `(response, nil)` unless plugin crashes + +## Debugging Object Hooks + +### Enable Verbose Logging + +```go +func (p *MyPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + ctx.Services.Logger().Info("Hook invoked", + "object_type", req.ObjectType, + "hook_type", req.HookType, + "object_id", req.ObjectID, + "operation_id", req.OperationId, + ) + + // Your logic here +} +``` + +### Test with hook-test-plugin + +The `hook-test-plugin` example provides a comprehensive testing UI: + +```bash +cd examples/plugins/studio/hook-test-plugin +go build +# Install and configure via AI Studio UI +``` + +Features: +- Test all 24 hook combinations (4 objects × 6 hook types) +- Configure behavior per hook (allow/reject/modify/metadata) +- View real-time hook invocations +- Automated test runner + +### Common Issues + +**Issue: Hook not firing** +- Check `GetObjectHookRegistrations()` returns correct object types +- Verify plugin is installed and enabled in AI Studio +- Check plugin logs for initialization errors + +**Issue: Changes not persisting** +- Only `before_create` and `before_update` can modify objects +- Must set `Modified: true` and provide `ModifiedObjectJson` +- Verify JSON marshaling succeeds + +**Issue: Operation blocked unexpectedly** +- Check all registered plugins for the same hook +- Lower priority plugins run first +- Review plugin logs for rejection reasons + +## Manifest Configuration + +Object hooks must be declared in your plugin manifest: + +```json +{ + "name": "llm-validator", + "version": "1.0.0", + "description": "Validates LLM configurations", + "capabilities": ["object_hooks"], + "object_hooks": { + "llm": ["before_create", "before_update"], + "datasource": ["before_create"] + } +} +``` + +See [Plugin Manifests Guide](/ai-management/ai-studio/plugins/manifests) for complete manifest documentation. + +## Complete Working Examples + +### LLM Validator +[`examples/plugins/studio/llm-validator/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/llm-validator) +- Validates LLM endpoints (HTTPS enforcement) +- Blocks based on vendor or privacy score +- Requires description field +- Adds validation metadata + +### Hook Test Plugin +[`examples/plugins/studio/hook-test-plugin/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/hook-test-plugin) +- Comprehensive testing of all 24 hook combinations +- Configurable behavior per hook type +- Web UI for testing and configuration +- Automated test runner + diff --git a/ai-management/ai-studio/plugins/overview.mdx b/ai-management/ai-studio/plugins/overview.mdx new file mode 100644 index 0000000000..3bcb3f3b19 --- /dev/null +++ b/ai-management/ai-studio/plugins/overview.mdx @@ -0,0 +1,225 @@ +--- +title: "Tyk AI Studio Plugin Overview" +description: "An overview of Tyk AI Studio's plugin system, featuring the Unified Plugin SDK, plugin capabilities, architecture, and deployment options." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Overview" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio's plugin system enables powerful extensibility across the entire platform through a **Unified Plugin SDK**. Built on [HashiCorp's go-plugin](https://github.com/hashicorp/go-plugin) framework, plugins run as isolated processes with gRPC communication, providing security and fault tolerance. + +## Unified Plugin SDK + +All plugins now use a single SDK (`pkg/plugin_sdk`) that works seamlessly in both AI Studio and Edge Gateway contexts. The SDK automatically detects the runtime environment and provides appropriate capabilities. + +### Key Features + +- **Single Import**: One SDK works everywhere +- **10 Plugin Capabilities**: Mix and match to build exactly what you need +- **Runtime Detection**: Automatic AI Studio vs Edge Gateway detection +- **Service API Access**: Built-in KV storage, logging, and management APIs +- **Type-Safe**: Clean Go interfaces, no manual proto handling + +## Plugin Capabilities + +Plugins implement one or more of these 12 capabilities: + +| Capability | Where It Works | Purpose | Common Use Cases | +|------------|----------------|---------|------------------| +| **Pre-Auth** | Studio + Gateway | Process before authentication | IP filtering, request validation | +| **Auth** | Studio + Gateway | Custom authentication | OAuth, API keys, JWT validation | +| **Post-Auth** | Studio + Gateway | Process after authentication | Request enrichment, policy enforcement | +| **Response** | Studio + Gateway | Modify responses | Content filtering, header injection | +| **Data Collection** | Studio + Gateway | Collect telemetry | Export to Elasticsearch, ClickHouse | +| **Custom Endpoints** | Gateway | Serve custom HTTP endpoints | MCP proxy, OAuth provider, webhooks | +| **Object Hooks** | Studio only | Intercept CRUD operations | Validation, approval workflows | +| **Agent** | Studio only | Conversational AI | Chat-based agents, LLM wrapping | +| **UI Provider** | Studio only | Dashboard extensions | Custom dashboards, admin tools | +| **Portal UI** | Studio only | Portal extensions | User-facing forms, pages, dashboards | +| **Config Provider** | Studio + Gateway | Provide JSON Schema config | Dynamic configuration | +| **Manifest Provider** | Gateway only | Plugin manifest | Gateway-only plugins | + +### Multi-Capability Plugins + +A single plugin can implement multiple capabilities. For example, a rate limiter might: +- Implement **Post-Auth** to check limits before request +- Implement **Response** to update counters after response +- Implement **UI Provider** to show rate limit dashboard + +## Plugin Types Overview + +While all plugins use the unified SDK, they generally fall into three categories based on their primary use case: + +### 1. Edge Gateway Plugins + +Edge Gateway plugins provide middleware hooks in the LLM proxy request/response pipeline using the unified SDK. + + +[Learn more →](/ai-management/ai-studio/plugins/edge-gateway) + +### 2. AI Studio UI Plugins + +AI Studio UI plugins extend the dashboard with custom WebComponents, adding new pages, sidebars, and interactive features to the admin interface. These also use the unified SDK and can combine UI capabilities with middleware hooks. + +[Learn more →](/ai-management/ai-studio/plugins/studio-ui) + +### 3. AI Studio Agent Plugins + +> **Experimental Feature**: Agent plugins are currently experimental. The API and behavior may change in future releases. + +Agent plugins enable conversational AI experiences in the Chat Interface using the unified SDK. These plugins can wrap LLMs, add custom logic, integrate external services, and create sophisticated multi-turn conversations. + +[Learn more →](/ai-management/ai-studio/plugins/studio-agent) + +### 4. Object Hooks Plugins + +**Object Hooks** are a powerful AI Studio-only capability that allows plugins to intercept and control CRUD operations on key objects before they reach the database. This is particularly useful for validation, approval workflows, and policy enforcement. + +[Learn more →](/ai-management/ai-studio/plugins/object-hooks) + +## Plugin Architecture + +### Monolithic Plugin Architecture + +A single plugin binary can run in **both** AI Studio and the Edge Gateway, so long as the requisite interfaces are implemented and the requirements are clear in the manifest file. This means one plugin can provide UI extensions in Studio, middleware hooks in the gateway, and even use the event bus to communicate between its Studio and gateway components in near-real-time. + +### Scheduled Tasks + +Studio plugins can register **scheduled tasks** — periodic calls from the host to the plugin on a configurable interval. This enables long-running background jobs such as analytics analysis, compliance checks, log scanning, or data synchronization. + +### Process Isolation + +Plugins run as separate processes, communicating with the main platform via gRPC. This provides: + +- **Security**: Plugin crashes don't affect the main platform +- **Language Flexibility**: While the gRPC protocol theoretically supports any language, **only Go plugins have been tested** in production. If you plan to write plugins in another language, expect to do additional integration work. +- **Resource Management**: Plugins can be restarted independently +- **Version Independence**: Update plugins without platform restarts + +Plugins can also run as standalone gRPC services in a sidecar or elsewhere on the network, rather than as local sub-processes. See [Plugin Deployment](/ai-management/ai-studio/plugins/deployment) for details. + +### Communication Flow + +```mermaid +graph TD + Host["AI Studio Host
(Main Process)"] + + Host -- "go-plugin
gRPC" --> Edge["Edge Gateway
Plugin Process
- Pre/Post Auth
- Data Collection
- Request Filter"] + Host -- "go-plugin
gRPC" --> UI["UI Plugin
Process
- WebComponents
- Service API
- RPC Methods"] + Host -- "go-plugin
gRPC" --> Agent["Agent Plugin
Process
- HandleMessage
- LLM Calls
- Tool Execute"] +``` + +### Service API (AI Studio Plugins Only) + +AI Studio UI and Agent plugins can access the Service API via a reverse gRPC broker connection: + +```mermaid +graph LR + Plugin["Plugin Process
Plugin Service
(Host→Plugin)
- HandleMessage
- GetAsset
- Call (RPC)"] + Host["AI Studio Host
Service API
(Plugin→Host)
- CallLLM
- ExecuteTool
- QueryDatasource"] + + Plugin <-->|"Broker
Pattern"| Host +``` + +The Service API provides 100+ gRPC operations for managing LLMs, apps, tools, datasources, analytics, and more. Access is controlled via permission scopes declared in the plugin manifest. + +[Learn more about Service API →](/ai-management/ai-studio/plugins/service-api) + +## Deployment Options + +Plugins support three deployment methods: + +### file:// + +Local filesystem plugins for development and testing: + +``` +file:///path/to/plugin-binary +``` + +### grpc:// + +Remote gRPC plugins running as network services: + +``` +grpc://plugin-host:50051 +``` + +### oci:// + +Container registry plugins (OCI artifacts): + +``` +oci://registry.example.com/plugins/my-plugin:v1.0.0 +``` + +[Learn more about deployment →](/ai-management/ai-studio/plugins/deployment) + +## Permissions and Scopes + +AI Studio plugins declare required permissions in their manifest: + +```json +{ + "permissions": { + "services": [ + "llms.proxy", // Call LLMs via proxy + "llms.read", // List and read LLM configs + "tools.execute", // Execute tools + "datasources.query", // Query datasources + "kv.readwrite", // Key-value storage + "analytics.read" // Read analytics data + ] + } +} +``` + +Permissions are validated when plugins call the Service API. The platform enforces least-privilege access based on declared scopes. + +[Learn more about manifests →](/ai-management/ai-studio/plugins/manifests) + +## Getting Started + +### Choose Your Plugin Type + +1. **Need to intercept/modify LLM requests?** → Edge Gateway Plugin +2. **Building dashboard UI features?** → AI Studio UI Plugin +3. **Creating conversational AI experiences?** → AI Studio Agent Plugin + +### Development Workflow + +1. Choose your plugin type +2. Read the specific plugin guide +3. Review example plugins in `examples/plugins/` and `community/plugins/` +4. Use the SDK to implement required interfaces +5. Build and test with `file://` deployment (see [Development Workflow Guide](/ai-management/ai-studio/plugins/development-workflow) for fast iteration) +6. Deploy with `grpc://` or `oci://` for production + +**Pro tip**: Use the reload API (`POST /api/v1/plugins/{id}/reload`) to test changes instantly without reinstalling. See the [Development Workflow Guide](/ai-management/ai-studio/plugins/development-workflow) for the fastest iteration loop. + +### SDK Installation + +All plugins use the unified SDK: + +```bash +go get github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk +``` + +```go +import "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + +type MyPlugin struct { + plugin_sdk.BasePlugin +} + +func main() { + plugin_sdk.Serve(NewMyPlugin()) +} +``` + +**Note**: If you have existing plugins using the old SDKs (`microgateway/plugins/sdk` or `pkg/ai_studio_sdk`), see the Migration Guide for upgrade instructions. + diff --git a/ai-management/ai-studio/plugins/portal-ui.mdx b/ai-management/ai-studio/plugins/portal-ui.mdx new file mode 100644 index 0000000000..e87e887bf4 --- /dev/null +++ b/ai-management/ai-studio/plugins/portal-ui.mdx @@ -0,0 +1,557 @@ +--- +title: "AI Portal UI Plugins" +description: "Learn how to extend the AI Portal with custom user-facing pages, forms, and dashboards using Portal UI plugins." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "AI Portal UI" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Portal UI plugins extend the **AI Portal** (end-user facing) with custom pages, forms, and interactive features. Unlike [Admin UI plugins](/ai-management/ai-studio/plugins/studio-ui) which are only visible to administrators, Portal UI plugins are accessible to all authenticated portal users and support group-based visibility filtering. + +## Overview + +Portal UI plugins enable you to: + +- **Add Portal Pages**: Register new routes in the portal navigation +- **Extend Portal Sidebar**: Add sections and links to the portal drawer +- **Serve WebComponents**: Use any frontend framework (same asset serving as admin UI) +- **Handle Portal RPC**: Define backend endpoints that receive authenticated user context +- **Control Visibility**: Restrict portal pages to specific user groups +- **Combine with Admin UI**: A single plugin can have both admin and portal interfaces + +### Use Cases + +- Support ticket systems for end users +- Custom resource browsers or dashboards +- User feedback and survey forms +- Forum or community features +- Self-service configuration pages +- Data submission and approval workflows + +## Architecture + +Portal UI plugins use a **separate security scope** from admin UI plugins. This is enforced at every level: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Plugin Binary │ +│ │ +│ UIProvider (admin) PortalUIProvider (portal) │ +│ ├── GetAsset() ◄── shared ├── HandlePortalRPC() │ +│ ├── GetManifest() ◄── shared │ receives PortalUserContext │ +│ └── HandleRPC() │ (user_id, email, groups) │ +│ ▲ │ │ +│ │ ▼ │ +│ Admin RPC Portal RPC │ +│ POST /api/v1/plugins/ POST /common/plugins/ │ +│ {id}/rpc/{method} {id}/portal-rpc/{method} │ +│ (admin only) (any authenticated user) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Key security boundaries:** + +| Aspect | Admin UI | Portal UI | +|--------|----------|-----------| +| **API prefix** | `/api/v1/plugins/` | `/common/plugins/` | +| **Auth required** | Admin only | Any authenticated user | +| **RPC handler** | `HandleRPC()` | `HandlePortalRPC()` | +| **User context** | None (admin implied) | `PortalUserContext` with user ID, email, groups | +| **gRPC method** | `Call` | `PortalCall` | +| **Hook type** | `studio_ui` | `portal_ui` | +| **Visibility** | All admins | Filterable by user group | + + +## Quick Start + +### 1. Project Structure + +``` +my-portal-plugin/ +├── main.go # Plugin entry point +├── manifest.json # Plugin manifest (embedded) +├── ui/ +│ ├── portal-page.js # Portal-facing WebComponent +│ └── admin-page.js # Admin-facing WebComponent (optional) +└── go.mod +``` + +### 2. Create Manifest + +The manifest declares both admin (`ui`) and portal (`portal`) UI sections. The `portal` section uses `PortalUISlot` which includes a `groups` field for visibility filtering. + +```json Expandable +{ + "id": "com.example.my-portal-plugin", + "version": "1.0.0", + "name": "My Portal Plugin", + "capabilities": { + "hooks": ["studio_ui", "portal_ui"] + }, + "permissions": { + "ui": ["sidebar.register", "route.register"], + "portal_ui": ["sidebar.register", "route.register"], + "kv": ["read", "write"], + "rpc": ["call"] + }, + "ui": { + "slots": [ + { + "slot": "sidebar.section", + "label": "Plugin Admin", + "icon": "settings", + "items": [ + { + "type": "route", + "path": "/admin/my-plugin", + "title": "Manage", + "mount": { + "kind": "webc", + "tag": "my-plugin-admin", + "entry": "/ui/admin-page.js" + } + } + ] + } + ] + }, + "portal": { + "slots": [ + { + "slot": "portal_sidebar.section", + "label": "My Feature", + "icon": "star", + "groups": [], + "items": [ + { + "type": "route", + "path": "/portal/plugins/my-feature", + "title": "My Feature", + "mount": { + "kind": "webc", + "tag": "my-plugin-portal", + "entry": "/ui/portal-page.js" + } + } + ] + } + ] + } +} +``` + +### Group-Based Visibility + +The `groups` field on portal slots controls which users can see the page: + +| `groups` value | Visibility | +|---|---| +| `[]` (empty array) | All portal users | +| `["engineering", "support"]` | Users in at least one of these groups | +| `["admin-team"]` | Only users in the "admin-team" group | + +The filtering happens server-side — the portal UI registry and sidebar menu endpoints only return entries the authenticated user is allowed to see. + +### 3. Implement the Plugin + +A portal UI plugin must implement **both** `UIProvider` (for assets and manifest) and `PortalUIProvider` (for portal RPC): + +```go Expandable +package main + +import ( + "embed" + "encoding/json" + "fmt" + "strings" + + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +//go:embed manifest.json +var manifestFile []byte + +//go:embed ui/* +var uiAssets embed.FS + +type MyPortalPlugin struct { + plugin_sdk.BasePlugin +} + +func NewMyPortalPlugin() *MyPortalPlugin { + return &MyPortalPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-portal-plugin", + "1.0.0", + "Plugin with portal UI", + ), + } +} + +func (p *MyPortalPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + ctx.Services.Logger().Info("Portal plugin initialized") + return nil +} + +// --- UIProvider (required for asset serving and manifest) --- + +func (p *MyPortalPlugin) GetAsset(assetPath string) ([]byte, string, error) { + path := strings.TrimPrefix(assetPath, "/") + content, err := uiAssets.ReadFile(path) + if err != nil { + return nil, "", fmt.Errorf("asset not found: %s", path) + } + + mimeType := "application/octet-stream" + if strings.HasSuffix(path, ".js") { + mimeType = "application/javascript" + } else if strings.HasSuffix(path, ".css") { + mimeType = "text/css" + } + + return content, mimeType, nil +} + +func (p *MyPortalPlugin) ListAssets(pathPrefix string) ([]*pb.AssetInfo, error) { + return nil, nil +} + +func (p *MyPortalPlugin) GetManifest() ([]byte, error) { + return manifestFile, nil +} + +// HandleRPC processes admin RPC calls (admin-only) +func (p *MyPortalPlugin) HandleRPC(method string, payload []byte) ([]byte, error) { + switch method { + case "admin_get_data": + // Admin-only operations + return json.Marshal(map[string]interface{}{"status": "ok"}) + default: + return nil, fmt.Errorf("unknown admin method: %s", method) + } +} + +// --- PortalUIProvider (required for portal RPC) --- + +// HandlePortalRPC processes portal RPC calls (any authenticated user) +func (p *MyPortalPlugin) HandlePortalRPC( + method string, + payload []byte, + userCtx *plugin_sdk.PortalUserContext, +) ([]byte, error) { + switch method { + case "get_user_data": + // userCtx provides authenticated user info + return json.Marshal(map[string]interface{}{ + "user_id": userCtx.UserID, + "email": userCtx.Email, + "groups": userCtx.Groups, + }) + case "submit_form": + // Process user form submission + return p.handleFormSubmission(payload, userCtx) + default: + return nil, fmt.Errorf("unknown portal method: %s", method) + } +} + +func main() { + plugin_sdk.Serve(NewMyPortalPlugin()) +} +``` + +### 4. Create Portal WebComponent + +Portal WebComponents receive a `portalPluginAPI` object (injected by the portal plugin loader) for making RPC calls: + +```javascript Expandable +class MyPortalPage extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(); + this.waitForAPIAndLoad(); + } + + // Wait for portalPluginAPI injection by the React wrapper + waitForAPIAndLoad(attempts = 0) { + if (this.portalPluginAPI) { + this.loadData(); + return; + } + if (attempts < 20) { + setTimeout(() => this.waitForAPIAndLoad(attempts + 1), 100); + } + } + + render() { + this.shadowRoot.innerHTML = ` + +

My Portal Page

+
Loading...
+ + `; + + this.shadowRoot.getElementById('submit') + .addEventListener('click', () => this.handleSubmit()); + } + + async loadData() { + try { + const result = await this.portalPluginAPI.call('get_user_data', {}); + this.shadowRoot.getElementById('content').textContent = + `Hello ${result.email}!`; + } catch (err) { + console.error('Failed to load data:', err); + } + } + + async handleSubmit() { + try { + const result = await this.portalPluginAPI.call('submit_form', { + title: 'My submission', + data: { key: 'value' } + }); + console.log('Submitted:', result); + } catch (err) { + console.error('Submit failed:', err); + } + } +} + +customElements.define('my-plugin-portal', MyPortalPage); +``` + +**Key difference from admin WebComponents:** +- Admin components receive `this.pluginAPI` (routes to `HandleRPC`) +- Portal components receive `this.portalPluginAPI` (routes to `HandlePortalRPC`) + +### 5. Create Admin WebComponent (Optional) + +If your plugin also has an admin interface, create a separate WebComponent that uses `this.pluginAPI`: + +```javascript Expandable +class MyPluginAdmin extends HTMLElement { + connectedCallback() { + this.render(); + this.waitForAPIAndLoad(); + } + + waitForAPIAndLoad(attempts = 0) { + if (this.pluginAPI) { + this.loadData(); + return; + } + if (attempts < 20) { + setTimeout(() => this.waitForAPIAndLoad(attempts + 1), 100); + } + } + + async loadData() { + // Uses admin RPC (HandleRPC) - only accessible to admins + const result = await this.pluginAPI.call('admin_get_data', {}); + } +} + +customElements.define('my-plugin-admin', MyPluginAdmin); +``` + +## PortalUserContext + +Every portal RPC call includes a `PortalUserContext` with the authenticated user's information: + +```go +type PortalUserContext struct { + UserID uint32 // Database user ID + Email string // User email address + Name string // Display name + IsAdmin bool // Whether user has admin role + Groups []string // Group names the user belongs to + Metadata map[string]string // Additional user metadata +} +``` + +Use this context for: +- **Authorization**: Check if the user has permission for the requested operation +- **Data scoping**: Return only data belonging to the calling user +- **Audit logging**: Record who performed each action +- **Group-based features**: Enable features based on group membership + +```go Expandable +func (p *MyPlugin) HandlePortalRPC( + method string, + payload []byte, + userCtx *plugin_sdk.PortalUserContext, +) ([]byte, error) { + // Only allow members of "premium" group + isPremium := false + for _, g := range userCtx.Groups { + if g == "premium" { + isPremium = true + break + } + } + if !isPremium { + return json.Marshal(map[string]interface{}{ + "error": "This feature requires premium access", + }) + } + + // ... handle request +} +``` + +## Manifest Reference + +### Portal Slots + +The `portal` section in the manifest is parallel to the `ui` section: + +```json Expandable +{ + "portal": { + "slots": [ + { + "slot": "portal_sidebar.section", + "label": "Section Label", + "icon": "icon-name", + "groups": ["group1", "group2"], + "items": [ + { + "type": "route", + "path": "/portal/plugins/my-feature/page1", + "title": "Page Title", + "mount": { + "kind": "webc", + "tag": "my-component-tag", + "entry": "/ui/my-component.js" + } + } + ] + } + ] + } +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `slot` | string | Yes | Slot identifier. Currently supports `portal_sidebar.section` | +| `label` | string | Yes | Display label in the sidebar | +| `icon` | string | No | Icon name for the sidebar entry | +| `groups` | string[] | No | Allowed user groups. Empty = all portal users | +| `items` | array | Yes | Routes/components to register | +| `items[].type` | string | Yes | Must be `"route"` | +| `items[].path` | string | Yes | Route path (should start with `/portal/plugins/`) | +| `items[].title` | string | Yes | Page title | +| `items[].mount.kind` | string | Yes | Mount type: `"webc"` or `"iframe"` | +| `items[].mount.tag` | string | Yes | Custom element tag name | +| `items[].mount.entry` | string | Yes | JavaScript entry point path | + +### Capabilities and Permissions + +Portal UI plugins must declare both hook types and permissions: + +```json +{ + "capabilities": { + "hooks": ["studio_ui", "portal_ui"] + }, + "permissions": { + "ui": ["sidebar.register", "route.register"], + "portal_ui": ["sidebar.register", "route.register"] + } +} +``` + +- `studio_ui` in hooks enables admin UI + asset serving +- `portal_ui` in hooks enables portal RPC handling +- Both are required for a plugin with portal UI (assets are served via `UIProvider`) + +### Route Path Convention + +Portal plugin routes should follow the pattern `/portal/plugins/{plugin-name}/{page}` to avoid conflicts with built-in portal routes: + +``` +/portal/plugins/feedback # Single-page plugin +/portal/plugins/support/tickets # Multi-page plugin +/portal/plugins/support/new-ticket # Multi-page plugin +``` + +## API Endpoints + +Portal plugin API endpoints are under `/common/` (authenticated, no admin required): + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/common/plugins/portal-ui-registry` | GET | Get portal UI components (filtered by user groups) | +| `/common/plugins/portal-sidebar-menu` | GET | Get portal sidebar items (filtered by user groups) | +| `/common/plugins/:id/portal-rpc/:method` | POST | Call portal RPC method on plugin | +| `/common/plugins/assets/:id/*filepath` | GET | Serve plugin static assets | + +### Portal RPC Call Flow + +``` +Portal UI (WebComponent) + │ this.portalPluginAPI.call('method', payload) + ▼ +pubClient.post('/common/plugins/{id}/portal-rpc/{method}', payload) + │ (AuthMiddleware - any authenticated user) + ▼ +callPortalPluginRPC handler + │ 1. Validate plugin active + loaded + supports portal_ui + │ 2. Build PortalUserContext from authenticated user + ▼ +AIStudioPluginManager.CallPluginPortalRPC() + │ gRPC PortalCall with user context + ▼ +Plugin's HandlePortalRPC(method, payload, userCtx) + │ Plugin processes request with user info + ▼ +JSON response → WebComponent +``` + +## Example: Portal Feedback Plugin + +A complete working example is available at [`examples/plugins/studio/portal-feedback/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/portal-feedback). + +This plugin demonstrates: +- Portal form for users to submit feedback (`HandlePortalRPC`) +- Admin dashboard to view all submissions (`HandleRPC`) +- Shared asset serving between admin and portal UIs +- Group-based visibility (set to `[]` for all users) +- `waitForAPIAndLoad()` pattern for WebComponent API injection timing + +## Best Practices + +### Security + +1. **Never trust portal input** - Always validate and sanitize data in `HandlePortalRPC` +2. **Scope data to users** - Use `userCtx.UserID` to ensure users only see their own data +3. **Keep admin operations in HandleRPC** - Destructive operations (delete, admin overrides) should stay in the admin-only `HandleRPC` method +4. **Check groups in RPC handlers** - Even though sidebar visibility is filtered, users could call the RPC endpoint directly. Validate group membership in `HandlePortalRPC` for sensitive operations + +### Performance + +1. **Use KV storage for persistence** - In-memory data is lost on plugin restart +2. **Cache frequently accessed data** - Use `sync.Map` or similar for hot data +3. **Keep portal pages lightweight** - Portal users expect fast page loads + +### WebComponent Patterns + +1. **Wait for API injection** - Use the `waitForAPIAndLoad()` pattern instead of calling the API in `connectedCallback()` directly +2. **Handle errors gracefully** - Show user-friendly messages, not stack traces +3. **Use Shadow DOM** - Prevents style conflicts with the host application +4. **Escape user content** - Always escape HTML in dynamic content to prevent XSS + diff --git a/ai-management/ai-studio/plugins/resource-types.mdx b/ai-management/ai-studio/plugins/resource-types.mdx new file mode 100644 index 0000000000..fb9e89daf5 --- /dev/null +++ b/ai-management/ai-studio/plugins/resource-types.mdx @@ -0,0 +1,461 @@ +--- +title: "Tyk AI Studio Resource Provider Plugins" +description: "Learn how to register custom resource types that integrate into the App creation flow, participate in privacy scoring, and work with group-based access control." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Resource Provider" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph LR + A[Plugin] -->|Registers| B[Custom Resource Type] + B --> C[App Creation Flow] + B --> D[Access Control] +``` + +Resource Provider plugins allow you to **register custom resource types** that integrate into the App creation flow, participate in privacy scoring, and work with the group-based access control model. This enables plugins to extend the platform's governance model with new kinds of resources beyond the built-in LLMs, Datasources, and Tools. + +## Overview + +By default, Apps in AI Studio bundle three built-in resource types: LLMs, Datasources, and Tools. The Resource Provider capability lets plugins register additional resource types that: + +- **Appear in the Create App form** as selectable resources (via a plugin-provided Web Component or a platform-rendered multi-select) +- **Participate in privacy scoring** with the generalized rule: no resource privacy score may exceed the maximum LLM privacy score in the app +- **Integrate with group-based access control** so admins can assign resource instances to groups, and users only see resources available to their groups +- **Support the community submission workflow** so end-users can submit new resource instances for admin review +- **Propagate to gateways** via the config snapshot, so gateway plugins can access resource associations at runtime + +### Use Cases + +- **MCP Server Registry**: Register MCP servers as a resource type, let users bundle them into Apps +- **Vector Store Catalog**: Expose vector databases with privacy scores for RAG pipelines +- **API Connectors**: Custom API integrations that need governance and access control +- **Knowledge Bases**: Document collections with sensitivity classifications + +### How It Works + +``` +Plugin declares resource types via manifest or GetResourceTypeRegistrations() + | +Platform registers types in DB, shows them in the App Form + | +Admin assigns instances to Groups (direct mapping, no catalogues) + | +User creates App -> selects resources from their accessible instances + | +Platform validates privacy scores + calls plugin's ValidateResourceSelection() + | +Associations stored in app_plugin_resources join table + | +Config snapshot includes associations for gateway access +``` + +## Implementing a Resource Provider + +### 1. Implement the ResourceProvider Interface + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + +type MyPlugin struct { + plugin_sdk.BasePlugin +} + +// GetResourceTypeRegistrations declares the resource types this plugin provides. +// Called once after Initialize() and again on plugin reload. +func (p *MyPlugin) GetResourceTypeRegistrations() ([]*plugin_sdk.ResourceTypeRegistration, error) { + return []*plugin_sdk.ResourceTypeRegistration{ + { + Slug: "mcp_servers", + Name: "MCP Servers", + Description: "Model Context Protocol servers for tool access", + Icon: "Hub", + HasPrivacyScore: true, + SupportsSubmissions: true, + // FormComponent: nil means the platform renders a standard multi-select + }, + }, nil +} + +// ListResourceInstances returns all instances for the App form selector. +func (p *MyPlugin) ListResourceInstances(ctx plugin_sdk.Context, slug string) ([]*plugin_sdk.ResourceInstance, error) { + // Load from plugin KV, external API, or any storage + servers, err := p.loadServers(ctx) + if err != nil { + return nil, err + } + + var instances []*plugin_sdk.ResourceInstance + for _, s := range servers { + instances = append(instances, &plugin_sdk.ResourceInstance{ + ID: s.ID, + Name: s.Name, + Description: s.Description, + PrivacyScore: s.PrivacyScore, + IsActive: s.IsActive, + }) + } + return instances, nil +} + +// GetResourceInstance retrieves a single instance by ID. +func (p *MyPlugin) GetResourceInstance(ctx plugin_sdk.Context, slug, instanceID string) (*plugin_sdk.ResourceInstance, error) { + server, err := p.loadServer(ctx, instanceID) + if err != nil { + return nil, err + } + return &plugin_sdk.ResourceInstance{ + ID: server.ID, + Name: server.Name, + PrivacyScore: server.PrivacyScore, + IsActive: server.IsActive, + }, nil +} + +// ValidateResourceSelection is called during app create/update. +// Use this for cross-instance validation (e.g., "max 3 servers per app"). +func (p *MyPlugin) ValidateResourceSelection(ctx plugin_sdk.Context, slug string, instanceIDs []string, appID uint32) error { + if len(instanceIDs) > 5 { + return fmt.Errorf("maximum 5 MCP servers per app") + } + return nil +} + +// CreateResourceInstance is called when a community submission is approved. +func (p *MyPlugin) CreateResourceInstance(ctx plugin_sdk.Context, slug string, payload []byte) (*plugin_sdk.ResourceInstance, error) { + var req CreateServerRequest + if err := json.Unmarshal(payload, &req); err != nil { + return nil, err + } + + server := p.createServer(ctx, req) + return &plugin_sdk.ResourceInstance{ + ID: server.ID, + Name: server.Name, + }, nil +} +``` + +### 2. Declare in the Manifest + +Add the `resource_types` section to your plugin manifest: + +```json Expandable +{ + "id": "com.example.mcp-registry", + "name": "MCP Registry", + "version": "1.0.0", + "capabilities": { + "hooks": ["resource_provider", "studio_ui"] + }, + "resource_types": [ + { + "slug": "mcp_servers", + "name": "MCP Servers", + "description": "Model Context Protocol servers for tool access", + "icon": "Hub", + "has_privacy_score": true, + "supports_submissions": true + } + ] +} +``` + +Resource types declared in the manifest are automatically registered when the plugin loads. The `GetResourceTypeRegistrations()` method provides a runtime fallback and can return additional types not in the manifest. + +### 3. Serve the Plugin + +```go +func main() { + plugin_sdk.Serve(NewMyPlugin()) +} +``` + +## ResourceProvider Interface Reference + +| Method | When Called | Purpose | +|--------|-----------|---------| +| `GetResourceTypeRegistrations()` | Plugin load/reload | Declare resource types | +| `ListResourceInstances(ctx, slug)` | App form load, Group form load | List available instances | +| `GetResourceInstance(ctx, slug, id)` | Config snapshot build | Get instance details for gateway | +| `ValidateResourceSelection(ctx, slug, ids, appID)` | App create/update | Custom validation logic | +| `CreateResourceInstance(ctx, slug, payload)` | Submission approval | Create instance from approved submission | + +## SDK Types + +### ResourceTypeRegistration + +```go +type ResourceTypeRegistration struct { + Slug string // Machine-readable ID (unique per plugin) + Name string // Display name in the UI + Description string // Help text + Icon string // Material icon name or asset path + HasPrivacyScore bool // Whether instances carry privacy scores + SupportsSubmissions bool // Whether community submissions are supported + FormComponent *ResourceFormComponent // Custom Web Component (nil = standard multi-select) +} +``` + +### ResourceInstance + +```go +type ResourceInstance struct { + ID string // Plugin-assigned unique identifier + Name string // Display name + Description string // Optional description + PrivacyScore int // 0-100 (only meaningful if type has HasPrivacyScore) + Metadata []byte // Opaque JSON included in config snapshots + IsActive bool // Whether instance is currently usable +} +``` + +> **Security**: Do not store secrets, credentials, or PII in the `Metadata` field. Metadata is propagated to all gateways via config snapshots, cached in database join tables, and may appear in logs or be accessible to other plugins with access to the app configuration. + +### ResourceFormComponent + +```go +type ResourceFormComponent struct { + Tag string // Web Component custom element tag (e.g., "mcp-server-selector") + EntryPoint string // JS asset path relative to plugin root (e.g., "ui/webc/selector.js") +} +``` + +## Privacy Scoring + +When `HasPrivacyScore` is `true`, each resource instance carries a privacy score (0-100). The platform enforces a generalized rule during app creation and updates: + +> **No resource privacy score may exceed the maximum LLM privacy score in the app.** + +This applies to both built-in datasources and plugin resources. For example: + +| Resource | Privacy Score | Result | +|----------|:---:|--------| +| LLM "GPT-4 Enterprise" | 80 | Max LLM score = 80 | +| Datasource "Internal DB" | 60 | OK `(60 <= 80)` | +| Plugin resource "MCP Server A" | 70 | OK `(70 <= 80)` | +| Plugin resource "MCP Server B" | 90 | **Rejected** (90 > 80) | + +The plugin sets privacy scores on instances via the `PrivacyScore` field in `ResourceInstance`. Admins review and approve these scores through the submission workflow. + +## Access Control + +Plugin resource instances use **direct group mapping** instead of the catalogue pattern used by built-in types. This is simpler and sufficient since plugins organize their own resources. + +### Access Chain + +``` +User -> Group -> Plugin Resource Instance (direct) +``` + +Compare with built-in types: +``` +User -> Group -> Catalogue -> LLM/Datasource/Tool +``` + +### Admin Workflow + +1. Admin navigates to **Teams** (Groups) in the admin UI +2. Opens a group and scrolls to the **Plugin Resources** section +3. For each registered resource type, selects which instances this group can access +4. Saves the group + +### User Experience + +When a user creates an App, they only see resource instances accessible via their group memberships. Admins bypass this filter and see all instances. + +## Custom Form Components + +For richer selection UX, plugins can provide a **Web Component** instead of the platform's standard multi-select. Declare it in the `FormComponent` field: + +```go +FormComponent: &plugin_sdk.ResourceFormComponent{ + Tag: "mcp-server-selector", + EntryPoint: "ui/webc/mcp-selector.js", +}, +``` + +The platform loads your Web Component JS from plugin assets and renders it in the App form. The contract: + +### Injected Properties + +| Property | Type | Description | +|----------|------|-------------| +| `data-selected-ids` | JSON `string[]` | Currently selected instance IDs | +| `data-app-id` | `string` | App ID (empty on create) | +| `data-mode` | `"create"` or `"edit"` | Form mode | +| `pluginAPI.call(method, payload)` | function | Make RPC calls to your plugin backend | +| `pluginAPI.listInstances()` | function | List available instances | + +### Events to Dispatch + +| Event | Detail | Description | +|-------|--------|-------------| +| `selection-change` | `{ selectedIds: string[] }` | Dispatch when user changes selection | + +### Example Web Component + +```javascript Expandable +class MCPServerSelector extends HTMLElement { + connectedCallback() { + this.render(); + + // Listen for attribute changes from the platform + const observer = new MutationObserver(() => this.render()); + observer.observe(this, { attributes: true }); + } + + async render() { + const selectedIds = JSON.parse(this.getAttribute('data-selected-ids') || '[]'); + const instances = await this.pluginAPI.listInstances(); + + this.innerHTML = ` +
+ ${instances.data.map(inst => ` + + `).join('')} +
+ `; + + this.querySelectorAll('input').forEach(input => { + input.addEventListener('change', () => { + const selected = [...this.querySelectorAll('input:checked')] + .map(el => el.value); + this.dispatchEvent(new CustomEvent('selection-change', { + detail: { selectedIds: selected } + })); + }); + }); + } +} + +customElements.define('mcp-server-selector', MCPServerSelector); +``` + +## Gateway Integration + +Plugin resource associations are included in the config snapshot sent to gateways. Each `AppConfig` includes a `plugin_resources` field: + +```protobuf +message PluginResourceAssociation { + uint32 plugin_id = 1; + string resource_type_slug = 2; + repeated string instance_ids = 3; + repeated ResourceInstanceSnapshot instances = 4; +} +``` + +Gateway plugins can access these associations from the app's config to make routing or authorization decisions. For example, an MCP proxy plugin could check if the requesting app has access to a specific MCP server by examining the `plugin_resources` field. + +## Community Submissions + +When `SupportsSubmissions` is `true`, community users can submit new resource instances through the existing submission workflow: + +1. User fills out a submission form with `resource_type: "plugin"` and a `plugin_resource_type_id` +2. The `resource_payload` contains plugin-defined JSON describing the new instance +3. Admin reviews the submission (including a suggested privacy score) +4. On approval, the platform calls the plugin's `CreateResourceInstance()` with the payload +5. The plugin creates the instance and returns its ID +6. The instance becomes available for selection in the App form + +## Manifest Reference + +```json Expandable +{ + "resource_types": [ + { + "slug": "string (required)", + "name": "string (required)", + "description": "string", + "icon": "string", + "has_privacy_score": false, + "supports_submissions": false, + "form_component": { + "tag": "string (custom element tag)", + "entry_point": "string (JS asset path)" + } + } + ] +} +``` + +| Field | Required | Default | Description | +|-------|:---:|:---:|-------------| +| `slug` | Yes | - | Machine-readable identifier, unique per plugin | +| `name` | Yes | - | Human-readable display name | +| `description` | No | `""` | Description shown in the App form | +| `icon` | No | `""` | Material icon name or plugin asset path | +| `has_privacy_score` | No | `false` | Whether instances carry privacy scores | +| `supports_submissions` | No | `false` | Whether community submissions are enabled | +| `form_component` | No | `null` | Custom Web Component for the App form (null = standard multi-select) | + +## API Endpoints + +These endpoints are available for frontend integration: + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/api/v1/plugin-resource-types` | List all active registered resource types | +| `GET` | `/api/v1/apps/:id/plugin-resources` | Get plugin resources for an app | +| `GET` | `/api/v1/groups/:id/plugin-resources` | Get plugin resource access for a group | +| `PUT` | `/api/v1/groups/:id/plugin-resources` | Set plugin resource access for a group (admin) | + +### Set Group Plugin Resources + +```bash +curl -X PUT /api/v1/groups/5/plugin-resources \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "resources": [ + { + "plugin_id": 1, + "resource_type_slug": "mcp_servers", + "instance_ids": ["server-1", "server-2"] + } + ] + }' +``` + +## Combining with Other Capabilities + +Resource Provider plugins often combine with other capabilities for a complete solution: + +| Combination | Purpose | +|------------|---------| +| ResourceProvider + UIProvider | Admin UI for managing resource instances | +| ResourceProvider + PortalUIProvider | Portal self-service for resource browsing | +| ResourceProvider + CustomEndpointHandler | Gateway proxy for resource access (e.g., MCP proxy) | +| ResourceProvider + EdgePayloadReceiver | Aggregate analytics from gateway resource usage | +| ResourceProvider + ConfigProvider | Admin-configurable plugin settings | + +### Example: Complete MCP Registry Plugin + +```go Expandable +type MCPRegistryPlugin struct { + plugin_sdk.BasePlugin +} + +// ResourceProvider - register MCP servers as a resource type +func (p *MCPRegistryPlugin) GetResourceTypeRegistrations() ([]*plugin_sdk.ResourceTypeRegistration, error) { ... } +func (p *MCPRegistryPlugin) ListResourceInstances(ctx plugin_sdk.Context, slug string) ([]*plugin_sdk.ResourceInstance, error) { ... } +func (p *MCPRegistryPlugin) GetResourceInstance(ctx plugin_sdk.Context, slug, id string) (*plugin_sdk.ResourceInstance, error) { ... } +func (p *MCPRegistryPlugin) ValidateResourceSelection(ctx plugin_sdk.Context, slug string, ids []string, appID uint32) error { ... } +func (p *MCPRegistryPlugin) CreateResourceInstance(ctx plugin_sdk.Context, slug string, payload []byte) (*plugin_sdk.ResourceInstance, error) { ... } + +// UIProvider - admin UI for server management +func (p *MCPRegistryPlugin) GetAsset(path string) ([]byte, string, error) { ... } +func (p *MCPRegistryPlugin) GetManifest() ([]byte, error) { ... } +func (p *MCPRegistryPlugin) HandleRPC(method string, payload []byte) ([]byte, error) { ... } + +// CustomEndpointHandler - gateway proxy for MCP requests +func (p *MCPRegistryPlugin) GetEndpointRegistrations() ([]*pb.EndpointRegistration, error) { ... } +func (p *MCPRegistryPlugin) HandleEndpointRequest(ctx plugin_sdk.Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error) { ... } +``` diff --git a/ai-management/ai-studio/plugins/sdk.mdx b/ai-management/ai-studio/plugins/sdk.mdx new file mode 100644 index 0000000000..4217f6d003 --- /dev/null +++ b/ai-management/ai-studio/plugins/sdk.mdx @@ -0,0 +1,1044 @@ +--- +title: "Tyk AI Studio Plugin SDK" +description: "Comprehensive guide to the Tyk AI Studio Plugin SDK, including capabilities, interfaces, and development patterns for building plugins that run in both AI Studio and Edge Gateway contexts." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "SDK Reference" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph TD + A[Unified Plugin SDK] --> B[BasePlugin] + A --> C[Capabilities Interfaces] + A --> D[Service API Client] +``` + +Tyk AI Studio provides a **Unified Plugin SDK** that works seamlessly in both AI Studio and Edge Gateway contexts with a single API. This guide covers the core SDK concepts, capabilities, and patterns. + +## Unified SDK Overview + +The Unified SDK (`pkg/plugin_sdk`) is the modern, recommended approach for all plugin development. It provides: + +- **Single Import**: One SDK works in both AI Studio and Edge Gateway +- **Automatic Runtime Detection**: SDK detects the execution environment +- **Capability-Based Design**: Implement only what you need +- **Type-Safe**: Clean Go types, no manual proto handling +- **Service Access**: Built-in KV storage, logging, events, and management APIs +- **Context-Rich**: Access to app, user, LLM metadata in every call + +### Installation + +```bash +go get github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk +``` + +### Basic Plugin Structure + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + +type MyPlugin struct { + plugin_sdk.BasePlugin + // Plugin-specific fields +} + +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-plugin", + "1.0.0", + "My plugin description", + ), + } +} + +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Initialize plugin + return nil +} + +func main() { + plugin_sdk.Serve(NewMyPlugin()) +} +``` + +## Plugin Capabilities + +Plugins implement one or more capability interfaces. The SDK supports 15 distinct capabilities: + +| Capability | Interface | Where It Works | Purpose | +|------------|-----------|----------------|---------| +| **Pre-Auth** | `PreAuthHandler` | Studio + Gateway | Process requests before authentication | +| **Auth** | `AuthHandler` | Studio + Gateway | Custom authentication with credential lookup | +| **Post-Auth** | `PostAuthHandler` | Studio + Gateway | Process requests after authentication (most common) | +| **Response** | `ResponseHandler` | Studio + Gateway | Modify response headers and body | +| **Data Collection** | `DataCollector` | Studio + Gateway | Collect telemetry (analytics, budgets, proxy logs) | +| **[Custom Endpoints](/ai-management/ai-studio/plugins/custom-endpoints)** | `CustomEndpointHandler` | Gateway | Serve custom HTTP endpoints under `/plugins/{slug}/` | +| **UI Provider** | `UIProvider` | Studio only | Serve admin web UI assets | +| **[Portal UI](/ai-management/ai-studio/plugins/portal-ui)** | `PortalUIProvider` | Studio only | Portal-facing pages and forms with user context | +| **Config Provider** | `ConfigProvider` | Studio + Gateway | Provide JSON Schema configuration | +| **Manifest Provider** | `ManifestProvider` | Gateway only | Provide plugin manifest (gateway-only plugins) | +| **Agent** | `AgentPlugin` | Studio only | Conversational AI agent with streaming | +| **Object Hooks** | `ObjectHookHandler` | Studio only | Intercept CRUD operations on objects | +| **Scheduler** | `SchedulerPlugin` | Studio only | Execute tasks on cron-based schedules | +| **Edge Payload** | `EdgePayloadReceiver` | Studio only | Receive data from edge (gateway) plugins | +| **[Resource Provider](/ai-management/ai-studio/plugins/resource-types)** | `ResourceProvider` | Studio only | Register custom resource types for Apps with privacy scoring | + +### Multi-Capability Plugins + +A single plugin can implement multiple capabilities. For example, a rate limiter might implement: +- `PostAuthHandler` - Check limits before request +- `ResponseHandler` - Update counters after response +- `UIProvider` - Provide management UI + +```go Expandable +type RateLimiter struct { + plugin_sdk.BasePlugin +} + +// Implement PostAuthHandler +func (p *RateLimiter) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Check rate limits +} + +// Implement ResponseHandler +func (p *RateLimiter) OnBeforeWriteHeaders(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + // Update counters +} + +// Implement UIProvider +func (p *RateLimiter) GetAsset(path string) ([]byte, string, error) { + // Serve UI assets +} +``` + +## Core Interfaces + +### 1. PreAuthHandler + +Process requests **before** authentication. Useful for IP filtering, request validation, etc. + +```go +type PreAuthHandler interface { + HandlePreAuth(ctx Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) +} +``` + +**Example:** +```go +func (p *MyPlugin) HandlePreAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Block requests from specific IPs + if isBlockedIP(req.ClientIp) { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "IP blocked", + }, nil + } + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### 2. AuthHandler + +Custom authentication with credential lookup. This interface requires **three methods** to fully integrate with the access control system. + +```go +type AuthHandler interface { + Plugin + HandleAuth(ctx Context, req *pb.AuthRequest) (*pb.AuthResponse, error) + GetAppByCredential(ctx Context, credential string) (*pb.App, error) + GetUserByCredential(ctx Context, credential string) (*pb.User, error) +} +``` + +#### Why App Linking Is Critical + +**A valid credential alone is not enough.** The system requires an associated App object because Apps provide the access control context: + +- **Policy enforcement** - Rate limits, usage quotas, and restrictions +- **Tool/Datasource permissions** - Which tools and datasources the credential can access +- **LLM restrictions** - Which LLMs the credential is allowed to use +- **Budget controls** - Cost tracking and spending limits + +Without a valid App association, authenticated requests will fail even if the credential itself is valid. + +#### Authentication Flow + +1. **`HandleAuth()`** - Validates the credential and returns an App ID and User ID +2. **`GetAppByCredential()`** - System calls this to fetch the full App object for access control +3. **`GetUserByCredential()`** - System calls this to fetch the User object for identity context + +#### Example Implementation + +```go Expandable +type MyAuthPlugin struct { + plugin_sdk.BasePlugin + tokenStore map[string]*TokenConfig // Maps tokens to app/user IDs +} + +// HandleAuth validates the credential and returns App/User IDs +func (p *MyAuthPlugin) HandleAuth(ctx plugin_sdk.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) { + // Extract token from request + token := req.Credential + if token == "" { + return &pb.AuthResponse{ + Authenticated: false, + ErrorMessage: "No credential provided", + }, nil + } + + // Validate token and look up associated IDs + tokenConfig, valid := p.tokenStore[token] + if !valid { + return &pb.AuthResponse{ + Authenticated: false, + ErrorMessage: "Invalid token", + }, nil + } + + // CRITICAL: Return the App ID - this links the credential to access control + return &pb.AuthResponse{ + Authenticated: true, + AppId: tokenConfig.AppID, // Must be a valid App in the database + UserId: tokenConfig.UserID, + }, nil +} + +// GetAppByCredential fetches the App object for access control enforcement +func (p *MyAuthPlugin) GetAppByCredential(ctx plugin_sdk.Context, credential string) (*pb.App, error) { + tokenConfig, ok := p.tokenStore[credential] + if !ok { + return nil, fmt.Errorf("unknown credential") + } + + // Fetch the App from the Service API + if ctx.Runtime == plugin_sdk.RuntimeGateway { + return ctx.Services.Gateway().GetApp(ctx, tokenConfig.AppID) + } + return ctx.Services.Studio().GetApp(ctx, tokenConfig.AppID) +} + +// GetUserByCredential fetches the User object for identity context +func (p *MyAuthPlugin) GetUserByCredential(ctx plugin_sdk.Context, credential string) (*pb.User, error) { + tokenConfig, ok := p.tokenStore[credential] + if !ok { + return nil, fmt.Errorf("unknown credential") + } + + // Fetch the User from the Service API + if ctx.Runtime == plugin_sdk.RuntimeGateway { + return ctx.Services.Gateway().GetUser(ctx, tokenConfig.UserID) + } + return ctx.Services.Studio().GetUser(ctx, tokenConfig.UserID) +} +``` + +#### Common Pitfalls + +| Pitfall | Symptom | Solution | +|---------|---------|----------| +| Not returning App ID | Requests fail with "no app context" | Always return a valid `AppId` in `AuthResponse` | +| App doesn't exist | 500 errors after auth success | Verify App exists in database before returning its ID | +| Not implementing `GetAppByCredential` | Compile error or runtime panic | Implement all three interface methods | +| Using wrong App ID | Permission denied for tools/LLMs | Ensure the App has the required permissions configured | + +See the working example at `examples/plugins/studio/custom-auth-ui/` for a complete implementation. + +### 3. PostAuthHandler + +Process requests **after** authentication. Most common capability for request enrichment, policy enforcement, etc. + +```go +type PostAuthHandler interface { + HandlePostAuth(ctx Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) +} +``` + +**Example:** +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + ctx.Services.Logger().Info("Processing request", + "app_id", ctx.AppID, + "user_id", ctx.UserID, + ) + + // Add custom header + req.Headers["X-Custom-Header"] = "value" + + return &pb.PluginResponse{ + Modified: true, + Request: req, + }, nil +} +``` + +### 4. ResponseHandler + +Modify response headers and body. Two methods allow phased processing: + +```go +type ResponseHandler interface { + OnBeforeWriteHeaders(ctx Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) + OnBeforeWrite(ctx Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) +} +``` + +**Example:** +```go Expandable +func (p *MyPlugin) OnBeforeWriteHeaders(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + // Add tracking header + if req.Headers == nil { + req.Headers = make(map[string]string) + } + req.Headers["X-Request-Id"] = generateRequestID() + + return &pb.ResponseWriteResponse{ + Modified: true, + Headers: req.Headers, + }, nil +} + +func (p *MyPlugin) OnBeforeWrite(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + // Modify response body + modifiedBody := transformResponse(req.Body) + + return &pb.ResponseWriteResponse{ + Modified: true, + Body: modifiedBody, + }, nil +} +``` + +### 5. DataCollector + +Collect telemetry data (analytics, budgets, proxy logs). + +```go +type DataCollector interface { + HandleProxyLog(ctx Context, log *pb.ProxyLogData) error + HandleAnalytics(ctx Context, analytics *pb.AnalyticsData) error + HandleBudgetUsage(ctx Context, usage *pb.BudgetUsageData) error +} +``` + +**Example:** +```go Expandable +func (p *MyPlugin) HandleAnalytics(ctx plugin_sdk.Context, analytics *pb.AnalyticsData) error { + // Export analytics to external system + return exportToElasticsearch(analytics) +} + +func (p *MyPlugin) HandleBudgetUsage(ctx plugin_sdk.Context, usage *pb.BudgetUsageData) error { + // Track budget usage + return trackBudget(usage) +} + +func (p *MyPlugin) HandleProxyLog(ctx plugin_sdk.Context, log *pb.ProxyLogData) error { + // Log proxy requests + return logToFile(log) +} +``` + +### 6. AgentPlugin + +Conversational AI agent with streaming support. See [Agent Plugins Guide](/ai-management/ai-studio/plugins/studio-agent) for details. + +```go +type AgentPlugin interface { + HandleAgentMessage(req *pb.AgentMessageRequest, stream pb.PluginService_HandleAgentMessageServer) error +} +``` + +### 7. ObjectHookHandler + +Intercept CRUD operations on AI Studio objects (LLMs, Datasources, Tools, Users). See [Object Hooks Guide](/ai-management/ai-studio/plugins/object-hooks) for complete details. + +```go +type ObjectHookHandler interface { + GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) + HandleObjectHook(ctx Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) +} +``` + +### 8. UIProvider + +Serve web UI assets for AI Studio plugins. See [UI Plugins Guide](/ai-management/ai-studio/plugins/studio-ui) for details. + +```go +type UIProvider interface { + GetAsset(path string) ([]byte, string, error) + ListAssets() ([]string, error) + GetManifest() ([]byte, error) + HandleRPC(method string, payload []byte) ([]byte, error) +} +``` + +### 9. ConfigProvider + +Provide JSON Schema for plugin configuration. + +```go +type ConfigProvider interface { + GetConfigSchema() ([]byte, error) +} +``` + +**Example:** +```go Expandable +func (p *MyPlugin) GetConfigSchema() ([]byte, error) { + schema := map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "api_key": map[string]interface{}{ + "type": "string", + "description": "API key for external service", + }, + "rate_limit": map[string]interface{}{ + "type": "integer", + "description": "Requests per minute", + "default": 100, + }, + }, + "required": []string{"api_key"}, + } + return json.Marshal(schema) +} +``` + +### 10. ManifestProvider + +Provide plugin manifest for gateway-only plugins (no UI). + +```go +type ManifestProvider interface { + GetManifest() ([]byte, error) +} +``` + +### 11. SchedulerPlugin + +Execute tasks on cron-based schedules. + +```go +type SchedulerPlugin interface { + ExecuteScheduledTask(ctx Context, schedule *Schedule) error +} + +type Schedule struct { + ID string // Unique identifier from manifest + Name string // Human-readable name + Cron string // Cron expression (e.g., "0 * * * *") + Timezone string // Timezone for cron evaluation + Enabled bool // Whether schedule is currently enabled + TimeoutSeconds int // Maximum execution time + Config map[string]interface{} // Schedule-specific configuration +} +``` + +**Example:** +```go +func (p *MyPlugin) ExecuteScheduledTask(ctx plugin_sdk.Context, schedule *plugin_sdk.Schedule) error { + ctx.Services.Logger().Info("Running scheduled task", + "schedule_id", schedule.ID, + "schedule_name", schedule.Name, + ) + + // Perform scheduled work + return p.runCleanup(ctx) +} +``` + +### 12. EdgePayloadReceiver + +Receive data from edge (Edge Gateway) plugins. This enables the hub-and-spoke communication pattern where edge plugins can send data back to the control plane. See [Edge-to-Control Communication](/ai-management/ai-studio/plugins/edge-to-control) for complete details. + +```go +type EdgePayloadReceiver interface { + AcceptEdgePayload(ctx Context, payload *EdgePayload) (handled bool, err error) +} + +type EdgePayload struct { + Payload []byte // Raw payload data from edge plugin + EdgeID string // Edge instance identifier + EdgeNamespace string // Namespace of the edge instance + CorrelationID string // Correlation ID for tracking + Metadata map[string]string // Key-value metadata + EdgeTimestamp int64 // Unix timestamp when generated at edge + ReceivedTimestamp int64 // Unix timestamp when received at control +} +``` + +**Example:** +```go Expandable +func (p *MyPlugin) AcceptEdgePayload(ctx plugin_sdk.Context, payload *plugin_sdk.EdgePayload) (bool, error) { + // Check if this payload is for us + if payload.Metadata["type"] != "my-plugin-data" { + return false, nil // Not our payload + } + + ctx.Services.Logger().Info("Received edge payload", + "edge_id", payload.EdgeID, + "correlation_id", payload.CorrelationID, + ) + + // Process the payload + if err := p.processEdgeData(payload.Payload); err != nil { + return true, err + } + + return true, nil +} +``` + +## Context and Services + +Every handler receives a `Context` that provides access to runtime information and services. + +### Context Fields + +```go +type Context struct { + Runtime Runtime // RuntimeStudio or RuntimeGateway + AppID uint32 // Current application ID + UserID uint32 // Current user ID (if authenticated) + SessionID string // Chat session ID (if applicable) + LLM *pb.LLM // LLM configuration (if applicable) + Services Services // Service broker +} +``` + +### Runtime Detection + +Plugins can adapt behavior based on runtime: + +```go +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + if ctx.Runtime == plugin_sdk.RuntimeStudio { + // Studio-specific logic + ctx.Services.Logger().Info("Running in AI Studio") + } else { + // Gateway-specific logic + ctx.Services.Logger().Info("Running in Microgateway") + } + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### Service Broker + +The context provides access to services through `ctx.Services`: + +#### Universal Services (Both Runtimes) + +**KV Storage:** +```go +// Write data +err := ctx.Services.KV().Write(ctx, "key", []byte("value")) + +// Read data +data, err := ctx.Services.KV().Read(ctx, "key") + +// Delete data +err := ctx.Services.KV().Delete(ctx, "key") + +// List keys +keys, err := ctx.Services.KV().List(ctx, "prefix") +``` + +**Note on KV Storage:** +- **Studio**: PostgreSQL-backed, shared across hosts, durable +- **Gateway**: Local database, per-instance, ephemeral + +**Logging:** +```go +ctx.Services.Logger().Info("Message", "key", "value") +ctx.Services.Logger().Warn("Warning", "error", err) +ctx.Services.Logger().Error("Error", "details", details) +ctx.Services.Logger().Debug("Debug info", "data", data) +``` + +**Events:** +```go Expandable +// Publish an event (flows up from edge to control) +err := ctx.Services.Events().Publish(ctx, "cache.invalidate", payload, plugin_sdk.DirUp) + +// Subscribe to events on a specific topic +subscriptionID, err := ctx.Services.Events().Subscribe("cache.invalidate", func(ev plugin_sdk.Event) { + // Handle event +}) + +// Subscribe to all events +subscriptionID, err := ctx.Services.Events().SubscribeAll(func(ev plugin_sdk.Event) { + // Handle any event +}) + +// Unsubscribe when done +err := ctx.Services.Events().Unsubscribe(subscriptionID) +``` + +**Note on Events:** +- Events enable real-time communication between plugins and across the hub-spoke architecture +- Direction controls routing: `DirLocal` (stays local), `DirUp` (edge→control), `DirDown` (control→edge) +- See [Service API Reference](/ai-management/ai-studio/plugins/service-api#event-service) for complete documentation + +#### Runtime-Specific Services + +**Gateway Services** (`ctx.Services.Gateway()`): +```go Expandable +if ctx.Runtime == plugin_sdk.RuntimeGateway { + // Get app + app, err := ctx.Services.Gateway().GetApp(ctx, appID) + + // List apps + apps, err := ctx.Services.Gateway().ListApps(ctx) + + // Get LLM + llm, err := ctx.Services.Gateway().GetLLM(ctx, llmID) + + // Get budget status + status, err := ctx.Services.Gateway().GetBudgetStatus(ctx, appID) + + // Validate credential + valid, err := ctx.Services.Gateway().ValidateCredential(ctx, token) +} +``` + +**Studio Services** (`ctx.Services.Studio()`): +```go Expandable +if ctx.Runtime == plugin_sdk.RuntimeStudio { + // Get app + app, err := ctx.Services.Studio().GetApp(ctx, appID) + + // List apps (basic) + apps, err := ctx.Services.Studio().ListApps(ctx, page, limit) + + // List apps with filtering (by owner, namespace, active status) + apps, err := ctx.Services.Studio().ListAppsWithFilters(ctx, page, limit, &plugin_sdk.ListAppsOptions{ + UserID: &ownerID, + Namespace: "production", + }) + + // Update app with metadata (full replacement) + err := ctx.Services.Studio().UpdateAppWithMetadata(ctx, appID, metadata) + + // Patch a single metadata key (atomic, safe for concurrent use) + metadataJSON, err := ctx.Services.Studio().PatchAppMetadata(ctx, appID, "tier", `"premium"`, false) + + // List LLMs + llms, err := ctx.Services.Studio().ListLLMs(ctx, page, limit) + + // List tools + tools, err := ctx.Services.Studio().ListTools(ctx, page, limit) + + // Call LLM + stream, err := ctx.Services.Studio().CallLLM(ctx, llmID, model, messages, temp, maxTokens, tools, stream) +} +``` + +## Initialization Pattern + +Plugins should extract the service broker ID during initialization for Service API access: + +```go Expandable +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Extract broker ID for Service API access + brokerIDStr := "" + if id, ok := config["_service_broker_id"]; ok { + brokerIDStr = id + } else if id, ok := config["service_broker_id"]; ok { + brokerIDStr = id + } + + if brokerIDStr != "" { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + } + + // Parse plugin-specific config + p.apiKey = config["api_key"] + + return nil +} +``` + +## BasePlugin Convenience Struct + +The SDK provides `BasePlugin` to reduce boilerplate: + +```go Expandable +type MyPlugin struct { + plugin_sdk.BasePlugin + apiKey string +} + +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-plugin", + "1.0.0", + "My plugin description", + ), + } +} +``` + +`BasePlugin` provides default implementations for common methods, which you can override as needed. + +## Error Handling + +### Blocking Requests + +Return a response with `Block: true`: + +```go +return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Request blocked: invalid input", +}, nil +``` + +### Non-Blocking Errors + +Log the error and continue: + +```go +if err != nil { + ctx.Services.Logger().Error("Failed to process", "error", err) + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### Agent Errors + +Send ERROR chunks for streaming agents: + +```go +return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: "Failed to process request", + IsFinal: true, +}) +``` + +## Complete Example: Multi-Capability Plugin + +```go Expandable +package main + +import ( + "encoding/json" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk/pb" +) + +type RequestLogger struct { + plugin_sdk.BasePlugin +} + +func NewRequestLogger() *RequestLogger { + return &RequestLogger{ + BasePlugin: plugin_sdk.NewBasePlugin( + "request-logger", + "1.0.0", + "Logs requests and responses", + ), + } +} + +// PostAuthHandler: Log incoming requests +func (p *RequestLogger) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + ctx.Services.Logger().Info("Incoming request", + "app_id", ctx.AppID, + "user_id", ctx.UserID, + "path", req.Path, + "method", req.Method, + ) + + // Store request metadata in KV + metadata := map[string]interface{}{ + "timestamp": time.Now().Unix(), + "path": req.Path, + "method": req.Method, + } + data, _ := json.Marshal(metadata) + ctx.Services.KV().Write(ctx, fmt.Sprintf("req:%s", req.RequestId), data) + + return &pb.PluginResponse{Modified: false}, nil +} + +// ResponseHandler: Log responses +func (p *RequestLogger) OnBeforeWriteHeaders(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + ctx.Services.Logger().Info("Outgoing response", + "app_id", ctx.AppID, + "status_code", req.StatusCode, + "request_id", req.RequestId, + ) + + return &pb.ResponseWriteResponse{Modified: false}, nil +} + +// ConfigProvider: Provide config schema +func (p *RequestLogger) GetConfigSchema() ([]byte, error) { + schema := map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "log_level": map[string]interface{}{ + "type": "string", + "enum": []string{"debug", "info", "warn", "error"}, + "default": "info", + }, + }, + } + return json.Marshal(schema) +} + +func main() { + plugin_sdk.Serve(NewRequestLogger()) +} +``` + +## Testing Plugins + +### Unit Testing + +```go Expandable +func TestPluginLogic(t *testing.T) { + plugin := NewRequestLogger() + + ctx := plugin_sdk.Context{ + Runtime: plugin_sdk.RuntimeStudio, + AppID: 1, + } + + req := &pb.EnrichedRequest{ + Path: "/api/v1/chat", + Method: "POST", + } + + resp, err := plugin.HandlePostAuth(ctx, req) + if err != nil { + t.Fatalf("HandlePostAuth failed: %v", err) + } + + if resp.Block { + t.Error("Expected request to not be blocked") + } +} +``` + +### Integration Testing + +See working examples in [`examples/plugins/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins) for integration test patterns. + +## Best Practices + +### Configuration +- Validate configuration in `Initialize()` +- Extract broker ID for Service API access +- Set sensible defaults +- Return errors for invalid config + +### Service API Usage +- Always check runtime before calling runtime-specific services +- Use context timeouts for external calls +- Cache frequently accessed data in KV storage +- Handle service errors gracefully +- Use Events for cross-plugin and edge-to-control communication +- Unsubscribe from events in `Shutdown()` to prevent leaks + +### Performance +- Minimize Service API calls in request path +- Use KV storage for caching +- Avoid blocking operations in handlers +- Use goroutines for async work (clean up in Shutdown) + +### Resource Management +- Clean up resources in `Shutdown()` method +- Close connections and file handles +- Cancel background goroutines +- Clear caches + +### Security +- Validate all inputs +- Sanitize log output (no secrets) +- Use secure defaults +- Follow least privilege principle + +## Session-Based Broker Pattern + +Plugins running in AI Studio use a **session-based broker pattern** for Service API access. Understanding this pattern is critical for plugins that need to call host APIs (like `ai_studio_sdk.CreateLLM()`, `ai_studio_sdk.ListApps()`, etc.). + +### How It Works + +1. **Plugin loads**: The host creates a long-lived gRPC broker connection +2. **Session opens**: The host calls `OpenSession` on the plugin, providing the broker ID +3. **OnSessionReady callback**: For plugins implementing `SessionAware`, this signals the broker is ready +4. **Service API available**: The plugin can now dial the broker and call host APIs + +### The SessionAware Interface + +Plugins that need early access to Service APIs should implement `SessionAware`: + +```go +type SessionAware interface { + OnSessionReady(ctx Context) // Called when broker connection is established + OnSessionClosing(ctx Context) // Called before session closes +} +``` + +### Connection Warmup Pattern (Critical!) + +**Important**: The go-plugin broker only accepts **ONE connection per broker ID**. If your plugin uses both the Event Service and the Management Service API, whichever dials first will succeed, and the connection must be shared. + +The SDK handles this automatically, but you should **warm up the connection early** in `OnSessionReady` to ensure it's established before any RPC calls come in: + +```go Expandable +type MyPlugin struct { + plugin_sdk.BasePlugin + services plugin_sdk.ServiceBroker +} + +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + p.services = ctx.Services + return nil +} + +// OnSessionReady implements plugin_sdk.SessionAware +// This is called when the session-based broker connection is established. +func (p *MyPlugin) OnSessionReady(ctx plugin_sdk.Context) { + log.Printf("Session ready - warming up service API connection...") + + // Eagerly establish the broker connection by making a simple API call. + // This "warms up" the connection so subsequent RPC calls don't need to dial. + if ai_studio_sdk.IsInitialized() { + // Make a lightweight API call to establish the connection + _, err := ai_studio_sdk.GetPluginsCount(context.Background()) + if err != nil { + log.Printf("Service API warmup failed: %v", err) + } else { + log.Printf("Service API connection established successfully") + } + } +} + +// OnSessionClosing implements plugin_sdk.SessionAware +func (p *MyPlugin) OnSessionClosing(ctx plugin_sdk.Context) { + log.Printf("Session closing - cleaning up resources") +} +``` + +### Why Warmup Is Important + +Without warmup, you may encounter "timeout waiting for connection info" errors when your plugin tries to use the Service API during an RPC call. This happens because: + +1. The broker connection is time-sensitive +2. Dialing late (during RPC) may fail if the broker has timed out +3. Event subscriptions and Service API calls share the same connection + +### Complete Example: Plugin with Service API and Events + +```go Expandable +package main + +import ( + "context" + "log" + + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" +) + +type MyServicePlugin struct { + plugin_sdk.BasePlugin + services plugin_sdk.ServiceBroker + eventSubID string +} + +func NewMyServicePlugin() *MyServicePlugin { + return &MyServicePlugin{ + BasePlugin: plugin_sdk.NewBasePlugin("my-service-plugin", "1.0.0", "Plugin using Service API"), + } +} + +func (p *MyServicePlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + p.services = ctx.Services + log.Printf("Initialized in %s runtime", ctx.Runtime) + return nil +} + +// OnSessionReady - warm up connections and set up subscriptions +func (p *MyServicePlugin) OnSessionReady(ctx plugin_sdk.Context) { + log.Printf("Session ready") + + // 1. Warm up Service API connection + if ai_studio_sdk.IsInitialized() { + _, err := ai_studio_sdk.GetPluginsCount(context.Background()) + if err != nil { + log.Printf("Service API warmup failed: %v", err) + } else { + log.Printf("Service API connection ready") + } + } + + // 2. Set up event subscriptions (uses same connection) + if p.services != nil { + events := p.services.Events() + if events != nil { + subID, err := events.Subscribe("config.updated", p.handleConfigUpdate) + if err != nil { + log.Printf("Failed to subscribe to events: %v", err) + } else { + p.eventSubID = subID + log.Printf("Subscribed to config.updated events") + } + } + } +} + +func (p *MyServicePlugin) handleConfigUpdate(ev plugin_sdk.Event) { + log.Printf("Received config update: %s", ev.Topic) + // Handle the event... +} + +func (p *MyServicePlugin) OnSessionClosing(ctx plugin_sdk.Context) { + // Clean up event subscription + if p.eventSubID != "" && p.services != nil { + p.services.Events().Unsubscribe(p.eventSubID) + } +} + +// HandleRPC - called from UI, Service API is already warmed up +func (p *MyServicePlugin) HandleRPC(method string, payload []byte) ([]byte, error) { + // Service API calls will work because connection was warmed up in OnSessionReady + llms, err := ai_studio_sdk.ListLLMs(context.Background(), 1, 10) + if err != nil { + return nil, err + } + // Process llms... + return []byte(`{"success": true}`), nil +} + +func main() { + plugin_sdk.Serve(NewMyServicePlugin()) +} +``` + +### Troubleshooting Connection Issues + +**Error: "timeout waiting for connection info"** +- Plugin is trying to dial the broker too late +- Solution: Implement `SessionAware` and warm up the connection in `OnSessionReady` + +**Error: "service broker ID not set"** +- The broker ID wasn't extracted from config +- Solution: The SDK handles this automatically via `OpenSession`, but verify your plugin isn't overriding the broker setup + +**Error: "SDK not initialized"** +- `ai_studio_sdk.Initialize()` wasn't called or failed +- Solution: Check logs for initialization errors during plugin startup diff --git a/ai-management/ai-studio/plugins/service-api.mdx b/ai-management/ai-studio/plugins/service-api.mdx new file mode 100644 index 0000000000..3fae689c7c --- /dev/null +++ b/ai-management/ai-studio/plugins/service-api.mdx @@ -0,0 +1,2103 @@ +--- +title: "Tyk AI Studio Service API Reference" +description: "A comprehensive reference for the Tyk AI Studio Service API, providing management capabilities for plugins to interact with the platform, including KV storage, logging, and management APIs." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "Service API Reference" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph TD + A[Plugin] -->|gRPC| B[Service API] + B --> C[KV Storage] + B --> D[Logging] + B --> E[Management APIs] +``` + +The Service API provides rich management capabilities for plugins to interact with the platform. Access is available through the **Unified Plugin SDK** via the `Context.Services` interface. + +## Overview + +Service API access is available to all plugins using the unified SDK (`pkg/plugin_sdk`), with different capabilities depending on the runtime: + +### Universal Services (Both Runtimes) +- **KV Storage**: Key-value storage (PostgreSQL in Studio, local DB in Gateway) +- **Logger**: Structured logging + +### Runtime-Specific Services +- **Gateway Services**: App management, LLM info, budget status, credential validation +- **Studio Services**: Full management API (LLMs, tools, apps, filters, tags, CallLLM) + +## Access Pattern + +All services are accessed through the `Context.Services` interface provided to your plugin handlers: + +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Universal services + ctx.Services.Logger().Info("Processing request", "app_id", ctx.AppID) + data, err := ctx.Services.KV().Read(ctx, "my-key") + + // Runtime-specific services + if ctx.Runtime == plugin_sdk.RuntimeStudio { + llms, err := ctx.Services.Studio().ListLLMs(ctx, 1, 10) + } else if ctx.Runtime == plugin_sdk.RuntimeGateway { + app, err := ctx.Services.Gateway().GetApp(ctx, ctx.AppID) + } + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +## Initialization and Connection Warmup + +For Service API access in AI Studio, plugins use a **session-based broker pattern**. The SDK handles most of the setup automatically, but there's a critical pattern you must follow for reliable Service API access. + +### The Connection Warmup Pattern + +**Critical**: The go-plugin broker only accepts **ONE connection per broker ID**. If your plugin uses both the Event Service and the Management Service API, whichever service dials first will succeed, and the connection is shared between them. + +To ensure reliable Service API access, implement `SessionAware` and **warm up the connection in `OnSessionReady`**: + +```go Expandable +import ( + "context" + "log" + + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" +) + +type MyPlugin struct { + plugin_sdk.BasePlugin + services plugin_sdk.ServiceBroker +} + +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + p.services = ctx.Services + return nil +} + +// OnSessionReady implements plugin_sdk.SessionAware +// CRITICAL: Warm up the Service API connection here! +func (p *MyPlugin) OnSessionReady(ctx plugin_sdk.Context) { + log.Printf("Session ready - warming up service API connection...") + + // Eagerly establish the broker connection by making a lightweight API call. + // This ensures the connection is ready before any RPC calls come in. + if ai_studio_sdk.IsInitialized() { + _, err := ai_studio_sdk.GetPluginsCount(context.Background()) + if err != nil { + log.Printf("Service API warmup failed: %v", err) + } else { + log.Printf("Service API connection established successfully") + } + } +} + +func (p *MyPlugin) OnSessionClosing(ctx plugin_sdk.Context) { + log.Printf("Session closing") +} +``` + +### Why Warmup Is Required + +Without the warmup pattern, you may encounter **"timeout waiting for connection info"** errors when your plugin tries to use the Service API during an RPC call. This happens because: + +1. The broker connection is time-sensitive and must be established early +2. Dialing late (during an RPC call from the UI) may fail if timing is off +3. Event subscriptions and Service API calls share the same underlying connection + +### Legacy Pattern (Still Supported) + +The older pattern of extracting the broker ID manually during `Initialize` still works but is not recommended: + +```go Expandable +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Extract broker ID for Service API access (automatic with SessionAware) + brokerIDStr := "" + if id, ok := config["_service_broker_id"]; ok { + brokerIDStr = id + } else if id, ok := config["service_broker_id"]; ok { + brokerIDStr = id + } + + if brokerIDStr != "" { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + } + + return nil +} +``` + +**Note**: The SDK now handles broker ID extraction automatically via `OpenSession`. You only need to implement `SessionAware` and warm up the connection. + +## Universal Services + +These services are available in both Studio and Gateway runtimes. + +### KV Storage + +Key-value storage for plugin data: +- **Studio**: PostgreSQL-backed, shared across hosts, durable +- **Gateway**: Local database, per-instance, ephemeral + +#### Write Data + +```go +err := ctx.Services.KV().Write(ctx, "my-key", []byte("value")) +``` + +Returns error if write fails. + +Example: +```go +settings := map[string]interface{}{ + "enabled": true, + "rate_limit": 100, +} + +data, _ := json.Marshal(settings) +err := ctx.Services.KV().Write(ctx, "settings", data) +if err != nil { + ctx.Services.Logger().Error("Failed to write settings", "error", err) +} +``` + +#### Read Data + +```go +data, err := ctx.Services.KV().Read(ctx, "my-key") +``` + +Returns error if key doesn't exist. + +Example: +```go +data, err := ctx.Services.KV().Read(ctx, "settings") +if err != nil { + ctx.Services.Logger().Warn("Settings not found", "error", err) + // Use defaults +} + +var settings map[string]interface{} +json.Unmarshal(data, &settings) +``` + +#### Delete Data + +```go +err := ctx.Services.KV().Delete(ctx, "my-key") +``` + +Example: +```go +err := ctx.Services.KV().Delete(ctx, "cache:user:123") +if err != nil { + ctx.Services.Logger().Error("Failed to delete cache", "error", err) +} +``` + +#### List Keys + +```go +keys, err := ctx.Services.KV().List(ctx, "prefix") +``` + +Example: +```go +keys, err := ctx.Services.KV().List(ctx, "cache:") +if err != nil { + return err +} + +for _, key := range keys { + ctx.Services.Logger().Debug("Found key", "key", key) +} +``` + +### Logger + +Structured logging with key-value pairs: + +```go +ctx.Services.Logger().Info("Message", "key", "value") +ctx.Services.Logger().Warn("Warning", "error", err) +ctx.Services.Logger().Error("Error", "details", details) +ctx.Services.Logger().Debug("Debug info", "data", data) +``` + +Example: +```go Expandable +func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + ctx.Services.Logger().Info("Request received", + "app_id", ctx.AppID, + "user_id", ctx.UserID, + "path", req.Path, + "method", req.Method, + ) + + // Process request... + + ctx.Services.Logger().Info("Request processed", + "app_id", ctx.AppID, + "duration_ms", time.Since(startTime).Milliseconds(), + ) + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### Event Service + +The Event Service enables plugins to publish and subscribe to events using the Event Bridge system. This allows plugins to communicate across the distributed architecture (edge ↔ control) using a pub/sub pattern. + +**Key Features:** +- Publish events to the local event bus +- Subscribe to events by topic or all events +- Events can flow across the hub-spoke architecture based on direction +- Automatic cleanup of subscriptions when plugin disconnects + +#### Event Directions + +Events have a direction that controls routing: + +| Direction | Constant | Description | +|-----------|----------|-------------| +| Local | `plugin_sdk.DirLocal` | Stays on local bus only, never forwarded | +| Up | `plugin_sdk.DirUp` | Flows from edge (Edge Gateway) to control (AI Studio) | +| Down | `plugin_sdk.DirDown` | Flows from control (AI Studio) to edge(s) | + +#### Publish Event + +Publish an event with a JSON-serializable payload: + +```go +err := ctx.Services.Events().Publish(ctx, "my.topic", payload, plugin_sdk.DirUp) +``` + +Example: +```go Expandable +// Publish a cache hit event to the local bus only +cacheEvent := map[string]interface{}{ + "key": "user:123", + "hit": true, + "timestamp": time.Now().Unix(), +} +err := ctx.Services.Events().Publish(ctx, "cache.hit", cacheEvent, plugin_sdk.DirLocal) +if err != nil { + ctx.Services.Logger().Error("Failed to publish cache event", "error", err) +} + +// Publish metrics from edge to control +metrics := map[string]interface{}{ + "requests_per_second": 1500, + "avg_latency_ms": 25, + "error_rate": 0.02, +} +err = ctx.Services.Events().Publish(ctx, "metrics.report", metrics, plugin_sdk.DirUp) +``` + +#### Publish Raw Event + +Publish an event with pre-serialized JSON payload (avoids double-serialization): + +```go +err := ctx.Services.Events().PublishRaw(ctx, "my.topic", jsonBytes, plugin_sdk.DirUp) +``` + +Example: +```go +// When you already have JSON bytes +jsonPayload := []byte(`{"status": "ready", "version": "1.0.0"}`) +err := ctx.Services.Events().PublishRaw(ctx, "plugin.status", jsonPayload, plugin_sdk.DirLocal) +``` + +#### Subscribe to Events + +Subscribe to events on a specific topic: + +```go +subscriptionID, err := ctx.Services.Events().Subscribe("my.topic", func(ev plugin_sdk.Event) { + // Handle event + fmt.Printf("Received: %s from %s\n", ev.Topic, ev.Origin) +}) +``` + +Example: +```go Expandable +// Subscribe to configuration updates +subID, err := ctx.Services.Events().Subscribe("config.updated", func(ev plugin_sdk.Event) { + ctx.Services.Logger().Info("Configuration updated", + "event_id", ev.ID, + "origin", ev.Origin, + ) + + // Parse payload + var config map[string]interface{} + if err := json.Unmarshal(ev.Payload, &config); err != nil { + ctx.Services.Logger().Error("Failed to parse config", "error", err) + return + } + + // Apply new configuration + p.applyConfig(config) +}) +if err != nil { + ctx.Services.Logger().Error("Failed to subscribe", "error", err) +} + +// Store subscription ID to unsubscribe later +p.configSubID = subID +``` + +#### Subscribe to All Events + +Subscribe to all events regardless of topic: + +```go +subscriptionID, err := ctx.Services.Events().SubscribeAll(func(ev plugin_sdk.Event) { + // Handle all events +}) +``` + +Example: +```go +// Monitor all events for debugging +subID, err := ctx.Services.Events().SubscribeAll(func(ev plugin_sdk.Event) { + ctx.Services.Logger().Debug("Event observed", + "id", ev.ID, + "topic", ev.Topic, + "origin", ev.Origin, + "direction", ev.Dir, + ) +}) +``` + +#### Unsubscribe + +Remove a subscription: + +```go +err := ctx.Services.Events().Unsubscribe(subscriptionID) +``` + +Example: +```go +// Clean up subscription in Shutdown +func (p *MyPlugin) Shutdown(ctx plugin_sdk.Context) error { + if p.configSubID != "" { + if err := ctx.Services.Events().Unsubscribe(p.configSubID); err != nil { + ctx.Services.Logger().Warn("Failed to unsubscribe", "error", err) + } + } + return nil +} +``` + +**Note:** Subscriptions are automatically cleaned up when the plugin process terminates, but it's good practice to explicitly unsubscribe in `Shutdown()`. + +#### Event Structure + +Events have the following fields: + +```go +type Event struct { + ID string // UUID for deduplication and tracing + Topic string // Logical topic name (e.g., "config.update") + Origin string // Node/plugin that created the event + Dir Direction // Routing direction + Payload json.RawMessage // JSON payload +} +``` + +#### Complete Example: Event-Driven Cache Invalidation + +```go Expandable +package main + +import ( + "encoding/json" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" +) + +type CachePlugin struct { + plugin_sdk.BasePlugin + cache map[string][]byte + invalidationSub string +} + +func (p *CachePlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + p.cache = make(map[string][]byte) + + // Subscribe to cache invalidation events from control + subID, err := ctx.Services.Events().Subscribe("cache.invalidate", func(ev plugin_sdk.Event) { + var req struct { + Keys []string `json:"keys"` + } + if err := json.Unmarshal(ev.Payload, &req); err != nil { + return + } + + for _, key := range req.Keys { + delete(p.cache, key) + } + + ctx.Services.Logger().Info("Cache invalidated", + "keys", len(req.Keys), + "origin", ev.Origin, + ) + }) + if err != nil { + return err + } + p.invalidationSub = subID + + return nil +} + +func (p *CachePlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + cacheKey := req.Path + + // Check cache + if data, ok := p.cache[cacheKey]; ok { + // Publish cache hit event (local only for metrics) + ctx.Services.Events().Publish(ctx, "cache.hit", map[string]interface{}{ + "key": cacheKey, + }, plugin_sdk.DirLocal) + + // Return cached response... + } + + // Cache miss - continue to upstream + return &pb.PluginResponse{Modified: false}, nil +} + +func (p *CachePlugin) Shutdown(ctx plugin_sdk.Context) error { + if p.invalidationSub != "" { + ctx.Services.Events().Unsubscribe(p.invalidationSub) + } + return nil +} +``` + +#### Event Service Best Practices + +1. **Use Appropriate Directions**: + - `DirLocal` for metrics and debugging within a single node + - `DirUp` for edge plugins sending data to control + - `DirDown` for control pushing updates to edges + +2. **Handle Payload Parsing Errors**: Always validate and handle JSON unmarshaling errors in event handlers. + +3. **Avoid Blocking in Handlers**: Event handlers should be fast. For heavy processing, spawn a goroutine or queue work. + +4. **Clean Up Subscriptions**: Unsubscribe in `Shutdown()` for explicit cleanup. + +5. **Use Meaningful Topics**: Use dot-separated topic names for clarity (e.g., `cache.invalidate`, `config.updated`, `metrics.report`). + +6. **Include Context in Payloads**: Add timestamps, correlation IDs, or source info to payloads for debugging. + +### System CRUD Events + +AI Studio emits built-in system events when core objects are created, updated, or deleted. These events are published to the local event bus (control-plane only) and can be subscribed to by any plugin. + +#### Available System Events + +| Topic | Object Type | Action | Description | +|-------|-------------|--------|-------------| +| `system.llm.created` | LLM | created | Emitted when an LLM is created | +| `system.llm.updated` | LLM | updated | Emitted when an LLM is updated | +| `system.llm.deleted` | LLM | deleted | Emitted when an LLM is deleted | +| `system.app.created` | App | created | Emitted when an App is created | +| `system.app.updated` | App | updated | Emitted when an App is updated | +| `system.app.deleted` | App | deleted | Emitted when an App is deleted | +| `system.app.approved` | App | approved | Emitted when an App's credential is activated | +| `system.datasource.created` | Datasource | created | Emitted when a Datasource is created | +| `system.datasource.updated` | Datasource | updated | Emitted when a Datasource is updated | +| `system.datasource.deleted` | Datasource | deleted | Emitted when a Datasource is deleted | +| `system.user.created` | User | created | Emitted when a User is created | +| `system.user.updated` | User | updated | Emitted when a User is updated | +| `system.user.deleted` | User | deleted | Emitted when a User is deleted | +| `system.group.created` | Group | created | Emitted when a Group is created | +| `system.group.updated` | Group | updated | Emitted when a Group is updated | +| `system.group.deleted` | Group | deleted | Emitted when a Group is deleted | +| `system.tool.created` | Tool | created | Emitted when a Tool is created | +| `system.tool.updated` | Tool | updated | Emitted when a Tool is updated | +| `system.tool.deleted` | Tool | deleted | Emitted when a Tool is deleted | + +#### Event Payload Structure + +All system CRUD events use a consistent payload structure: + +```go +type ObjectEventPayload struct { + ObjectType string `json:"object_type"` // "llm", "app", "datasource", "user", "group", "tool" + Action string `json:"action"` // "created", "updated", "deleted", "approved" + ObjectID uint `json:"object_id"` // ID of the affected object + UserID uint `json:"user_id"` // User who performed the action (0 if system/unknown) + Timestamp time.Time `json:"timestamp"` // When the event occurred + Object interface{} `json:"object"` // The full object (for create/update, nil for delete) +} +``` + +#### Subscribing to System Events + +```go Expandable +// Subscribe to all App events using wildcard +subID, err := ctx.Services.Events().Subscribe("system.app.*", func(ev plugin_sdk.Event) { + var payload struct { + ObjectType string `json:"object_type"` + Action string `json:"action"` + ObjectID uint `json:"object_id"` + UserID uint `json:"user_id"` + Timestamp time.Time `json:"timestamp"` + Object interface{} `json:"object"` + } + + if err := json.Unmarshal(ev.Payload, &payload); err != nil { + ctx.Services.Logger().Error("Failed to parse event payload", "error", err) + return + } + + ctx.Services.Logger().Info("App event received", + "action", payload.Action, + "object_id", payload.ObjectID, + "user_id", payload.UserID, + ) +}) + +// Or subscribe to a specific event +subID, err := ctx.Services.Events().Subscribe("system.user.created", func(ev plugin_sdk.Event) { + // Handle new user creation +}) +``` + +#### Example: Audit Log Plugin + +```go Expandable +package main + +import ( + "encoding/json" + "time" + + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" +) + +type AuditLogPlugin struct { + plugin_sdk.BasePlugin + subscriptions []string +} + +func (p *AuditLogPlugin) OnSessionReady(ctx plugin_sdk.Context) { + // Subscribe to all system events + topics := []string{ + "system.llm.created", "system.llm.updated", "system.llm.deleted", + "system.app.created", "system.app.updated", "system.app.deleted", "system.app.approved", + "system.datasource.created", "system.datasource.updated", "system.datasource.deleted", + "system.user.created", "system.user.updated", "system.user.deleted", + "system.group.created", "system.group.updated", "system.group.deleted", + "system.tool.created", "system.tool.updated", "system.tool.deleted", + } + + for _, topic := range topics { + subID, err := ctx.Services.Events().Subscribe(topic, func(ev plugin_sdk.Event) { + p.logAuditEvent(ctx, ev) + }) + if err != nil { + ctx.Services.Logger().Error("Failed to subscribe to event", "topic", topic, "error", err) + continue + } + p.subscriptions = append(p.subscriptions, subID) + } +} + +func (p *AuditLogPlugin) logAuditEvent(ctx plugin_sdk.Context, ev plugin_sdk.Event) { + var payload struct { + ObjectType string `json:"object_type"` + Action string `json:"action"` + ObjectID uint `json:"object_id"` + UserID uint `json:"user_id"` + Timestamp time.Time `json:"timestamp"` + } + + if err := json.Unmarshal(ev.Payload, &payload); err != nil { + return + } + + // Log to external audit system, KV storage, etc. + ctx.Services.Logger().Info("AUDIT", + "object_type", payload.ObjectType, + "action", payload.Action, + "object_id", payload.ObjectID, + "user_id", payload.UserID, + "timestamp", payload.Timestamp, + ) +} + +func (p *AuditLogPlugin) OnSessionClosing(ctx plugin_sdk.Context) { + for _, subID := range p.subscriptions { + ctx.Services.Events().Unsubscribe(subID) + } +} +``` + +**Note**: System events are published with `DirLocal` direction, meaning they stay on the control plane and are not forwarded to edge instances. + +## Studio Services + +Available when `ctx.Runtime == plugin_sdk.RuntimeStudio`. + +### LLM Operations + +Requires: `llms.read`, `llms.write`, or `llms.proxy` scope + +#### List LLMs + +```go +llms, err := ctx.Services.Studio().ListLLMs(ctx, page, limit) +``` + +**Alternative** (direct SDK call): +```go +llmsResp, err := ai_studio_sdk.ListLLMs(ctx, 1, 10) +``` + +Example: +```go Expandable +if ctx.Runtime == plugin_sdk.RuntimeStudio { + llms, err := ctx.Services.Studio().ListLLMs(ctx, 1, 10) + if err != nil { + return err + } + + // Type assert the response + llmsResp := llms.(*studiomgmt.ListLLMsResponse) + for _, llm := range llmsResp.Llms { + ctx.Services.Logger().Info("LLM found", + "name", llm.Name, + "vendor", llm.Vendor, + "model", llm.DefaultModel, + ) + } +} +``` + +#### Get LLM + +```go +llm, err := ctx.Services.Studio().GetLLM(ctx, llmID) +``` + +**Alternative** (direct SDK call): +```go +llm, err := ai_studio_sdk.GetLLM(ctx, 1) +``` + +### Call LLM (Streaming) + +Requires: `llms.proxy` scope + +```go +func CallLLM( + ctx context.Context, + llmID uint32, + model string, + messages []*mgmtpb.LLMMessage, + temperature float64, + maxTokens int32, + tools []*mgmtpb.LLMTool, + stream bool, +) (mgmtpb.AIStudioManagementService_CallLLMClient, error) +``` + +Example: +```go Expandable +messages := []*mgmtpb.LLMMessage{ + {Role: "user", Content: "What is the capital of France?"}, +} + +llmStream, err := ai_studio_sdk.CallLLM(ctx, 1, "gpt-4", messages, 0.7, 1000, nil, false) +if err != nil { + return err +} + +var response string +for { + resp, err := llmStream.Recv() + if err == io.EOF { + break + } + if err != nil { + return err + } + + response += resp.Content + + if resp.Done { + break + } +} + +log.Printf("LLM response: %s", response) +``` + +### Call LLM (Simple) + +Convenience method for simple calls: + +```go +func CallLLMSimple(ctx context.Context, llmID uint32, model string, userMessage string) (string, error) +``` + +Example: +```go +response, err := ai_studio_sdk.CallLLMSimple(ctx, 1, "gpt-4", "Hello, world!") +if err != nil { + return err +} + +log.Printf("Response: %s", response) +``` + +### Get LLMs Count + +```go +func GetLLMsCount(ctx context.Context) (int64, error) +``` + +Example: +```go +count, err := ai_studio_sdk.GetLLMsCount(ctx) +if err != nil { + return err +} + +log.Printf("Total LLMs: %d", count) +``` + +**Note**: For complete Studio Services documentation including Tools, Apps, Plugins, Datasources, and Filters, see the examples in the working plugins at `examples/plugins/studio/service-api-test/`. + +## Gateway Services + +Available when `ctx.Runtime == plugin_sdk.RuntimeGateway`. + +Gateway Services provide read-only access to essential gateway information. + +### Get App + +```go +app, err := ctx.Services.Gateway().GetApp(ctx, appID) +``` + +Returns app configuration. Type assert to `*gwmgmt.GetAppResponse`. + +Example: +```go +if ctx.Runtime == plugin_sdk.RuntimeGateway { + app, err := ctx.Services.Gateway().GetApp(ctx, ctx.AppID) + if err != nil { + ctx.Services.Logger().Error("Failed to get app", "error", err) + return &pb.PluginResponse{Modified: false}, nil + } + + appResp := app.(*gwmgmt.GetAppResponse) + ctx.Services.Logger().Info("Processing request for app", + "app_name", appResp.Name, + "llm_count", len(appResp.Llms), + ) +} +``` + +### List Apps + +```go +apps, err := ctx.Services.Gateway().ListApps(ctx) +``` + +Returns all apps accessible to the gateway. Type assert to `*gwmgmt.ListAppsResponse`. + +### Get LLM + +```go +llm, err := ctx.Services.Gateway().GetLLM(ctx, llmID) +``` + +Returns LLM configuration. Type assert to `*gwmgmt.GetLLMResponse`. + +### List LLMs + +```go +llms, err := ctx.Services.Gateway().ListLLMs(ctx) +``` + +Returns all LLMs configured for the gateway. Type assert to `*gwmgmt.ListLLMsResponse`. + +### Get Budget Status + +```go +status, err := ctx.Services.Gateway().GetBudgetStatus(ctx, appID) +``` + +Returns current budget status for an app. Type assert to `*gwmgmt.GetBudgetStatusResponse`. + +Example: +```go Expandable +if ctx.Runtime == plugin_sdk.RuntimeGateway { + status, err := ctx.Services.Gateway().GetBudgetStatus(ctx, ctx.AppID) + if err != nil { + ctx.Services.Logger().Error("Failed to get budget", "error", err) + return &pb.PluginResponse{Modified: false}, nil + } + + budgetResp := status.(*gwmgmt.GetBudgetStatusResponse) + if budgetResp.RemainingBudget <= 0 { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Budget exceeded", + }, nil + } +} +``` + +### Get Model Price + +```go +price, err := ctx.Services.Gateway().GetModelPrice(ctx, vendor, model) +``` + +Returns pricing information for a model. Type assert to `*gwmgmt.GetModelPriceResponse`. + +### Validate Credential + +```go +valid, err := ctx.Services.Gateway().ValidateCredential(ctx, token) +``` + +Validates a credential token. Type assert to `*gwmgmt.ValidateCredentialResponse`. + +Example: +```go +if ctx.Runtime == plugin_sdk.RuntimeGateway { + valid, err := ctx.Services.Gateway().ValidateCredential(ctx, req.Headers["Authorization"]) + if err != nil || !valid.(*gwmgmt.ValidateCredentialResponse).Valid { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Invalid credentials", + }, nil + } +} +``` + +## Tool Operations (Studio Only) + +Requires: `tools.read`, `tools.write`, or `tools.execute` scope + +### List Tools + +```go +func ListTools(ctx context.Context, page, limit int32) (*mgmtpb.ListToolsResponse, error) +``` + +Example: +```go +toolsResp, err := ai_studio_sdk.ListTools(ctx, 1, 50) +if err != nil { + return err +} + +for _, tool := range toolsResp.Tools { + log.Printf("Tool: %s (%s) - %s", tool.Name, tool.Slug, tool.Description) + for _, op := range tool.Operations { + log.Printf(" Operation: %s", op) + } +} +``` + +### Get Tool by ID + +```go +func GetTool(ctx context.Context, toolID uint32) (*mgmtpb.Tool, error) +``` + +Example: +```go +tool, err := ai_studio_sdk.GetTool(ctx, 1) +if err != nil { + return err +} + +log.Printf("Tool: %s - Type: %s", tool.Name, tool.ToolType) +``` + +### Execute Tool + +Requires: `tools.execute` scope + +```go +func ExecuteTool( + ctx context.Context, + toolID uint32, + operationID string, + parameters map[string]interface{}, +) (*mgmtpb.ExecuteToolResponse, error) +``` + +Example: +```go +params := map[string]interface{}{ + "url": "https://api.example.com/users", + "method": "GET", +} + +result, err := ai_studio_sdk.ExecuteTool(ctx, 1, "http_request", params) +if err != nil { + return err +} + +log.Printf("Tool result: %s", result.Data) +``` + +## Plugin Operations + +Requires: `plugins.read` or `plugins.write` scope + +### List Plugins + +```go +func ListPlugins(ctx context.Context, page, limit int32) (*mgmtpb.ListPluginsResponse, error) +``` + +Example: +```go +pluginsResp, err := ai_studio_sdk.ListPlugins(ctx, 1, 10) +if err != nil { + return err +} + +for _, plugin := range pluginsResp.Plugins { + log.Printf("Plugin: %s - Type: %s, Active: %t", + plugin.Name, plugin.PluginType, plugin.IsActive) +} +``` + +### Get Plugin by ID + +```go +func GetPlugin(ctx context.Context, pluginID uint32) (*mgmtpb.Plugin, error) +``` + +Example: +```go +plugin, err := ai_studio_sdk.GetPlugin(ctx, 1) +if err != nil { + return err +} + +log.Printf("Plugin: %s - Hook: %s", plugin.Name, plugin.HookType) +``` + +### Get Plugins Count + +```go +func GetPluginsCount(ctx context.Context) (int64, error) +``` + +Example: +```go +count, err := ai_studio_sdk.GetPluginsCount(ctx) +if err != nil { + return err +} + +log.Printf("Total plugins: %d", count) +``` + +## App Operations + +Requires: `apps.read` or `apps.write` scope + +### List Apps + +```go +func ListApps(ctx context.Context, page, limit int32) (*mgmtpb.ListAppsResponse, error) +``` + +Example: +```go +appsResp, err := ai_studio_sdk.ListApps(ctx, 1, 10) +if err != nil { + return err +} + +for _, app := range appsResp.Apps { + log.Printf("App: %s - LLMs: %d, Tools: %d", + app.Name, len(app.Llms), len(app.Tools)) +} +``` + +### List Apps with Filters + +```go +func ListAppsWithFilters(ctx context.Context, page, limit int32, opts *ListAppsOptions) (*mgmtpb.ListAppsResponse, error) +``` + +`ListAppsOptions` supports filtering by: +- `IsActive *bool` — Filter by active/inactive status +- `Namespace string` — Filter by namespace (empty = all namespaces) +- `UserID *uint32` — Filter by owner user ID + +Example — list only the current user's apps: +```go +userID := uint32(ctx.UserID) +appsResp, err := ai_studio_sdk.ListAppsWithFilters(ctx, 1, 10, &ai_studio_sdk.ListAppsOptions{ + UserID: &userID, +}) +if err != nil { + return err +} + +for _, app := range appsResp.Apps { + log.Printf("My app: %s (namespace: %s)", app.Name, app.Namespace) +} +``` + +Example — list active apps in a namespace: +```go +active := true +appsResp, err := ai_studio_sdk.ListAppsWithFilters(ctx, 1, 20, &ai_studio_sdk.ListAppsOptions{ + IsActive: &active, + Namespace: "production", +}) +``` + +The original `ListApps(ctx, page, limit)` function is still available for backward compatibility and returns all apps without filtering. + +### Get App by ID + +```go +func GetApp(ctx context.Context, appID uint32) (*mgmtpb.App, error) +``` + +Example: +```go +app, err := ai_studio_sdk.GetApp(ctx, 1) +if err != nil { + return err +} + +log.Printf("App: %s - Description: %s", app.Name, app.Description) +``` + +### Patch App Metadata + +```go +func PatchAppMetadata(ctx context.Context, appID uint32, key, value string, deleteKey bool) (*mgmtpb.PatchAppMetadataResponse, error) +``` + +Atomically updates a single metadata key on an app without requiring a full app update. This is safer than `UpdateAppWithMetadata` for concurrent modifications since it uses database-level transactions with row locking. + +**Set a metadata key** (value must be JSON-encoded): +```go +resp, err := ai_studio_sdk.PatchAppMetadata(ctx, appID, "cache_enabled", `true`, false) +if err != nil { + return err +} +log.Printf("Updated metadata: %s", resp.Metadata) // Full metadata JSON +``` + +**Set complex values:** +```go +// String value +ai_studio_sdk.PatchAppMetadata(ctx, appID, "tier", `"premium"`, false) + +// Numeric value +ai_studio_sdk.PatchAppMetadata(ctx, appID, "max_requests", `1000`, false) + +// Object value +ai_studio_sdk.PatchAppMetadata(ctx, appID, "settings", `{"timeout": 30, "retries": 3}`, false) +``` + +**Delete a metadata key:** +```go +resp, err := ai_studio_sdk.PatchAppMetadata(ctx, appID, "deprecated_field", "", true) +``` + +**Via the plugin_sdk interface:** +```go +// Returns the full metadata JSON string after the operation +metadataJSON, err := ctx.Services.Studio().PatchAppMetadata(ctx, appID, "tier", `"premium"`, false) +``` + +Requires: `apps.write` scope. + +## KV Storage Operations + +Requires: `kv.read` or `kv.readwrite` scope + +### Write Data + +```go +func WritePluginKV(ctx context.Context, key string, value []byte, expireAt *time.Time) (bool, error) +``` + +Returns `true` if created, `false` if updated. Pass `nil` for `expireAt` for no expiration. + +Example: +```go Expandable +settings := map[string]interface{}{ + "enabled": true, + "rate_limit": 100, +} + +data, _ := json.Marshal(settings) +created, err := ai_studio_sdk.WritePluginKV(ctx, "settings", data, nil) +if err != nil { + return err +} + +if created { + log.Println("Settings created") +} else { + log.Println("Settings updated") +} +``` + +### Write Data with TTL + +```go +func WritePluginKVWithTTL(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error) +``` + +Convenience function that writes data with a relative time-to-live. The entry is automatically expired and cleaned up after the TTL elapses. + +Example: +```go +// Cache a response for 1 hour +data, _ := json.Marshal(cachedResponse) +created, err := ai_studio_sdk.WritePluginKVWithTTL(ctx, "cache:response:123", data, 1*time.Hour) +if err != nil { + return err +} +``` + +Expired keys are automatically cleaned up by the platform. Reads on expired keys return a not-found error. + +### Read Data + +```go +func ReadPluginKV(ctx context.Context, key string) ([]byte, error) +``` + +Example: +```go +data, err := ai_studio_sdk.ReadPluginKV(ctx, "settings") +if err != nil { + return err +} + +var settings map[string]interface{} +json.Unmarshal(data, &settings) + +log.Printf("Settings: %+v", settings) +``` + +### Delete Data + +```go +func DeletePluginKV(ctx context.Context, key string) error +``` + +Example: +```go +err := ai_studio_sdk.DeletePluginKV(ctx, "settings") +if err != nil { + log.Printf("Failed to delete: %v", err) +} +``` + +### List Keys + +```go +func ListPluginKVKeys(ctx context.Context, prefix string) ([]string, error) +``` + +Example: +```go +keys, err := ai_studio_sdk.ListPluginKVKeys(ctx, "config:") +if err != nil { + return err +} + +for _, key := range keys { + log.Printf("Key: %s", key) +} +``` + +## Data Types + +### LLMMessage + +```go +type LLMMessage struct { + Role string // "user", "assistant", "system" + Content string // Message content +} +``` + +### LLMTool (for tool calling) + +```go +type LLMTool struct { + Type string // "function" + Function *LLMToolFunction +} + +type LLMToolFunction struct { + Name string + Description string + Parameters map[string]interface{} // JSON Schema +} +``` + +### Tool + +```go +type Tool struct { + Id uint32 + Name string + Slug string + Description string + ToolType string // "rest", "graphql", "grpc", etc. + Operations []string + IsActive bool + PrivacyScore int32 +} +``` + +### Plugin + +```go +type Plugin struct { + Id uint32 + Name string + Slug string + PluginType string // "gateway", "ai_studio", "agent" + HookType string // Hook type + IsActive bool + Command string // file://, grpc://, oci:// +} +``` + +### App + +```go +type App struct { + Id uint32 + Name string + Description string + Llms []*LLM + Tools []*Tool + Datasources []*Datasource +} +``` + +## Error Handling + +Service API calls return standard Go errors: + +```go +llmsResp, err := ai_studio_sdk.ListLLMs(ctx, 1, 10) +if err != nil { + log.Printf("Failed to list LLMs: %v", err) + return err +} +``` + +Common error types: +- Permission denied: Missing required scope +- Not found: Resource doesn't exist +- Invalid argument: Bad request parameters +- Unavailable: Service not ready + +## Rate Limiting + +Service API calls are subject to rate limiting: + +- Default: 1000 requests/minute per plugin +- Configurable via platform settings +- Implement exponential backoff for retries + +Example retry logic: + +```go Expandable +func callWithRetry(ctx context.Context, fn func() error) error { + maxRetries := 3 + backoff := time.Second + + for i := 0; i < maxRetries; i++ { + err := fn() + if err == nil { + return nil + } + + if i < maxRetries-1 { + time.Sleep(backoff) + backoff *= 2 + } + } + + return fmt.Errorf("max retries exceeded") +} +``` + +## Context and Timeouts + +Always use contexts with timeouts: + +```go +ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) +defer cancel() + +llmsResp, err := ai_studio_sdk.ListLLMs(ctx, 1, 10) +``` + +## Best Practices + +1. **Check SDK Initialization**: + ```go + if !ai_studio_sdk.IsInitialized() { + return fmt.Errorf("SDK not initialized") + } + ``` + +2. **Handle Pagination**: + ```go Expandable + page := int32(1) + limit := int32(100) + + for { + resp, err := ai_studio_sdk.ListTools(ctx, page, limit) + if err != nil { + return err + } + + // Process tools... + + if len(resp.Tools) < int(limit) { + break // Last page + } + + page++ + } + ``` + +3. **Cache Results**: + ```go + // Cache LLM list for 5 minutes + var cachedLLMs []*mgmtpb.LLM + var cacheTime time.Time + + if time.Since(cacheTime) > 5*time.Minute { + resp, _ := ai_studio_sdk.ListLLMs(ctx, 1, 100) + cachedLLMs = resp.Llms + cacheTime = time.Now() + } + ``` + +4. **Error Logging**: + ```go + llmsResp, err := ai_studio_sdk.ListLLMs(ctx, 1, 10) + if err != nil { + log.Printf("[Plugin %d] Failed to list LLMs: %v", p.pluginID, err) + return err + } + ``` + +## Scope Requirements Summary + +| Operation | Required Scope | +|-----------|----------------| +| ListLLMs, GetLLM | `llms.read` | +| CallLLM | `llms.proxy` | +| CreateLLM, UpdateLLM | `llms.write` | +| ListTools, GetTool | `tools.read` | +| ExecuteTool | `tools.execute` | +| CreateTool, UpdateTool | `tools.write` | +| ListApps, ListAppsWithFilters, GetApp | `apps.read` | +| CreateApp, UpdateApp, PatchAppMetadata | `apps.write` | +| ListPlugins, GetPlugin | `plugins.read` | +| ReadPluginKV, ListPluginKVKeys | `kv.read` | +| WritePluginKV, DeletePluginKV | `kv.readwrite` | +| ListDatasources, GetDatasource | `datasources.read` | +| CreateDatasource, UpdateDatasource, DeleteDatasource | `datasources.write` | +| GenerateEmbedding, StoreDocuments, ProcessAndStoreDocuments | `datasources.embeddings` | +| QueryDatasource, QueryDatasourceByVector | `datasources.query` | +| CreateSchedule, GetSchedule, ListSchedules, UpdateSchedule, DeleteSchedule | `scheduler.manage` | + +## RAG & Embedding Services + +AI Studio provides comprehensive RAG (Retrieval-Augmented Generation) capabilities through the Service API, enabling plugins to build custom document ingestion and semantic search workflows. + +### Overview + +The RAG Service APIs allow plugins to: +- Generate embeddings using configured embedders (OpenAI, Ollama, Vertex, etc.) +- Store pre-computed embeddings with custom chunking strategies +- Query vector stores with semantic search +- Build complex ingestion plugins (GitHub, Confluence, custom document processors) + +**Key Benefit**: Plugins have **full control** over chunking, embedding generation, and storage - no forced workflows. + +### Core RAG APIs + +#### GenerateEmbedding + +Generate embeddings for text chunks without storing them. + +```go Expandable +resp, err := ai_studio_sdk.GenerateEmbedding(ctx, datasourceID, []string{ + "First chunk of text", + "Second chunk of text", + "Third chunk of text", +}) + +if err != nil || !resp.Success { + return err +} + +// resp.Vectors contains the embedding vectors +for i, vector := range resp.Vectors { + fmt.Printf("Chunk %d embedding dimensions: %d\n", i, len(vector.Values)) +} +``` + +**Required Scope**: `datasources.embeddings` + +**Use Case**: Custom chunking workflows where you generate embeddings first, then decide what to store. + +#### StoreDocuments + +Store pre-computed embeddings in the vector store without regenerating them. + +```go Expandable +documents := make([]*mgmtpb.DocumentWithEmbedding, len(chunks)) +for i, chunk := range chunks { + documents[i] = &mgmtpb.DocumentWithEmbedding{ + Content: chunk, + Embedding: preComputedEmbeddings[i], + Metadata: map[string]string{ + "source": "github", + "repo": "my-repo", + "file": "README.md", + "chunk_index": fmt.Sprintf("%d", i), + }, + } +} + +resp, err := ai_studio_sdk.StoreDocuments(ctx, datasourceID, documents) +if err != nil || !resp.Success { + return err +} + +fmt.Printf("Stored %d documents\n", resp.StoredCount) +``` + +**Required Scope**: `datasources.embeddings` + +**Use Case**: Complete control over embeddings - use custom models, external services, or cached embeddings. + +**Supported Vector Stores**: +- ✅ Pinecone +- ✅ PGVector +- ✅ Chroma (v0.2.5+) +- ✅ Weaviate +- ⚠️ Qdrant (requires SDK installation) +- ⚠️ Redis (requires RediSearch configuration) + +#### ProcessAndStoreDocuments + +Convenience method that generates embeddings and stores in one step. + +```go Expandable +chunks := make([]*mgmtpb.DocumentChunk, len(texts)) +for i, text := range texts { + chunks[i] = &mgmtpb.DocumentChunk{ + Content: text, + Metadata: map[string]string{ + "source": "api", + "index": fmt.Sprintf("%d", i), + }, + } +} + +resp, err := ai_studio_sdk.ProcessAndStoreDocuments(ctx, datasourceID, chunks) +if err != nil || !resp.Success { + return err +} + +fmt.Printf("Processed %d documents\n", resp.ProcessedCount) +``` + +**Required Scope**: `datasources.embeddings` + +**Use Case**: Simplified workflow when you don't need to inspect or cache embeddings. + +#### QueryDatasource + +Semantic search using a text query (embedding generated automatically). + +```go Expandable +resp, err := ai_studio_sdk.QueryDatasource(ctx, datasourceID, + "How do I configure RAG in AI Studio?", + 10, // maxResults + 0.75, // similarityThreshold +) + +if err != nil || !resp.Success { + return err +} + +for _, result := range resp.Results { + fmt.Printf("Score: %.2f | Content: %s\n", + result.SimilarityScore, + result.Content) + // Access metadata + for k, v := range result.Metadata { + fmt.Printf(" %s: %s\n", k, v) + } +} +``` + +**Required Scope**: `datasources.query` + +**Use Case**: Standard semantic search - plugin provides text, system handles embedding. + +#### QueryDatasourceByVector + +Semantic search using a pre-computed embedding vector. + +```go Expandable +// Generate query embedding +queryResp, _ := ai_studio_sdk.GenerateEmbedding(ctx, datasourceID, []string{"search query"}) +queryVector := queryResp.Vectors[0].Values + +// Search with the pre-computed vector +resp, err := ai_studio_sdk.QueryDatasourceByVector(ctx, datasourceID, + queryVector, + 10, // maxResults + 0.75, // similarityThreshold +) + +for _, result := range resp.Results { + fmt.Printf("Match: %s (score: %.2f)\n", result.Content, result.SimilarityScore) +} +``` + +**Required Scope**: `datasources.query` + +**Use Case**: Advanced workflows with custom query embeddings or hybrid search strategies. + +**Supported Vector Stores**: +- ✅ Pinecone +- ✅ PGVector +- ✅ Chroma +- ✅ Weaviate +- ⚠️ Qdrant (requires SDK) +- ⚠️ Redis (requires RediSearch) + +### Complete Custom Ingestion Example + +Building a GitHub repository documentation ingestion plugin: + +```go Expandable +func (p *GitHubDocsPlugin) IngestRepository(ctx plugin_sdk.Context, repo string, datasourceID uint32) error { + // Step 1: Fetch markdown files from GitHub + files, err := p.fetchMarkdownFiles(repo) + if err != nil { + return err + } + + // Step 2: Custom chunking strategy (semantic chunking by headers) + var allChunks []string + var allMetadata []map[string]string + + for _, file := range files { + chunks := p.semanticChunker(file.Content) // Your custom logic + for i, chunk := range chunks { + allChunks = append(allChunks, chunk) + allMetadata = append(allMetadata, map[string]string{ + "source": "github", + "repo": repo, + "file": file.Path, + "chunk_index": fmt.Sprintf("%d", i), + "updated_at": file.UpdatedAt, + }) + } + } + + // Step 3: Generate embeddings for all chunks + embResp, err := ai_studio_sdk.GenerateEmbedding(ctx, datasourceID, allChunks) + if err != nil || !embResp.Success { + return fmt.Errorf("embedding generation failed: %v", err) + } + + // Step 4: Store with pre-computed embeddings + documents := make([]*mgmtpb.DocumentWithEmbedding, len(allChunks)) + for i := range allChunks { + documents[i] = &mgmtpb.DocumentWithEmbedding{ + Content: allChunks[i], + Embedding: embResp.Vectors[i].Values, + Metadata: allMetadata[i], + } + } + + storeResp, err := ai_studio_sdk.StoreDocuments(ctx, datasourceID, documents) + if err != nil || !storeResp.Success { + return fmt.Errorf("storage failed: %v", err) + } + + ctx.Services.Logger().Info("Successfully ingested repository", + "repo", repo, + "chunks", storeResp.StoredCount) + + return nil +} +``` + +### Datasource Configuration + +For RAG APIs to work, datasources must be configured with: + +**Embedder Configuration**: +- `EmbedVendor`: Embedder provider (`"openai"`, `"ollama"`, `"vertex"`, `"googleai"`) +- `EmbedModel`: Model name (e.g., `"text-embedding-3-small"` for OpenAI, `"nomic-embed-text"` for Ollama) +- `EmbedAPIKey`: API key if required by embedder +- `EmbedUrl`: Embedder endpoint URL + +**Vector Store Configuration**: +- `DBSourceType`: Vector store type (`"pinecone"`, `"chroma"`, `"pgvector"`, `"qdrant"`, `"redis"`, `"weaviate"`) +- `DBConnString`: Connection URL for vector store +- `DBConnAPIKey`: API key if required +- `DBName`: Collection/namespace/table name + +**Important**: `EmbedModel` must be the actual model name (e.g., `"text-embedding-3-small"`), NOT the vendor name! + +### RAG Workflow Patterns + +#### Pattern 1: Separate Generate & Store (Full Control) + +```go +// Generate embeddings +embeddings, _ := ai_studio_sdk.GenerateEmbedding(ctx, dsID, customChunks) + +// Store with pre-computed embeddings (no re-embedding!) +ai_studio_sdk.StoreDocuments(ctx, dsID, documentsWithEmbeddings) +``` + +**Best for**: Custom chunking algorithms, caching embeddings, using external embedding services. + +#### Pattern 2: Process & Store (Convenience) + +```go +// Generate and store in one step +ai_studio_sdk.ProcessAndStoreDocuments(ctx, dsID, chunks) +``` + +**Best for**: Simple ingestion when you don't need to inspect or cache embeddings. + +#### Pattern 3: Hybrid Search + +```go +// Generate embeddings for multiple query variants +variants := []string{"original query", "rephrased query", "expanded query"} +embeddings, _ := ai_studio_sdk.GenerateEmbedding(ctx, dsID, variants) + +// Search with each variant and merge results +allResults := []Result{} +for _, emb := range embeddings.Vectors { + results, _ := ai_studio_sdk.QueryDatasourceByVector(ctx, dsID, emb.Values, 5, 0.7) + allResults = append(allResults, results.Results...) +} + +// Deduplicate and rank +finalResults := deduplicateAndRank(allResults) +``` + +**Best for**: Advanced search strategies, query expansion, multi-vector search. + +### Datasource Management APIs + +For managing datasources programmatically: + +```go Expandable +// List all datasources +datasources, err := ai_studio_sdk.ListDatasources(ctx, 1, 100, nil, "") + +// Get specific datasource +ds, err := ai_studio_sdk.GetDatasource(ctx, datasourceID) + +// Create datasource with full configuration +ds, err := ai_studio_sdk.CreateDatasourceWithEmbedder(ctx, + "My RAG Datasource", + "Short description", + "Long description", + "", // URL + "http://localhost:8000", // Chroma connection + "chroma", // Vector store type + "", // DB API key + "my-collection", // Collection name + "openai", // Embedder vendor + "https://api.openai.com/v1/embeddings", // Embedder URL + "sk-...", // Embed API key + "text-embedding-3-small", // Embed model + 5, 1, true, +) + +// Update datasource +ds, err := ai_studio_sdk.UpdateDatasource(ctx, datasourceID, name, ...) + +// Delete datasource +err := ai_studio_sdk.DeleteDatasource(ctx, datasourceID) + +// Search datasources +results, err := ai_studio_sdk.SearchDatasources(ctx, "query") +``` + +**Required Scopes**: `datasources.read` (list/get/search), `datasources.write` (create/update/delete) + +### Error Handling + +```go Expandable +resp, err := ai_studio_sdk.GenerateEmbedding(ctx, dsID, chunks) +if err != nil { + // gRPC communication error + return fmt.Errorf("gRPC error: %w", err) +} + +if !resp.Success { + // Server-side validation or processing error + ctx.Services.Logger().Error("Embedding generation failed", + "error", resp.ErrorMessage, + "datasource_id", dsID) + return fmt.Errorf("embedding failed: %s", resp.ErrorMessage) +} + +// Success - use resp.Vectors +``` + +**Common Errors**: +- `"datasource does not have embedder configured"` - Set EmbedVendor/EmbedModel/EmbedAPIKey +- `"datasource does not have vector store configured"` - Set DBSourceType/DBConnString/DBName +- `"failed to generate embeddings with openai/openai"` - EmbedModel should be model name, not vendor! +- `"vector store connection failed"` - Ensure vector store is running and accessible + +### Advanced Datasource Operations + +These operations provide fine-grained control over vector store data through metadata filtering and namespace management. + +#### Delete Documents by Metadata + +Delete specific documents from vector stores using metadata filters: + +```go Expandable +// Delete all chunks for a specific file +count, err := ai_studio_sdk.DeleteDocumentsByMetadata( + ctx, + datasourceID, + map[string]string{"file_path": "old-file.md"}, + "AND", // filter mode: "AND" or "OR" + false, // dry_run: set true to preview without deleting +) + +// Example with OR mode (delete documents matching any condition) +count, err := ai_studio_sdk.DeleteDocumentsByMetadata( + ctx, + datasourceID, + map[string]string{ + "status": "archived", + "expired": "true", + }, + "OR", // Matches documents with status=archived OR expired=true + false, +) +``` + +**Parameters:** +- `metadataFilter`: map of metadata key-value pairs to match +- `filterMode`: `"AND"` (all conditions must match) or `"OR"` (any condition matches) +- `dryRun`: if `true`, returns count without deleting + +**Returns:** Number of documents deleted (or would be deleted if dry-run) + +**Scope Required**: `datasources.write` + +#### Query by Metadata Only + +Query documents using only metadata filters (no vector similarity): + +```go Expandable +results, totalCount, err := ai_studio_sdk.QueryByMetadataOnly( + ctx, + datasourceID, + map[string]string{"source": "internal-docs"}, + "AND", + 10, // limit + 0, // offset +) + +// Process results +for _, result := range results { + fmt.Printf("Content: %s\nMetadata: %v\n", result.Content, result.Metadata) +} +fmt.Printf("Total matching documents: %d\n", totalCount) +``` + +**Parameters:** +- `metadataFilter`: metadata key-value pairs to match +- `filterMode`: `"AND"` or `"OR"` +- `limit`: max results per page (1-100, default: 10) +- `offset`: pagination offset + +**Returns:** Array of results and total count (for pagination) + +**Scope Required**: `datasources.query` + +#### List Namespaces + +List all namespaces/collections in a vector store: + +```go +namespaces, err := ai_studio_sdk.ListNamespaces(ctx, datasourceID) +for _, ns := range namespaces { + fmt.Printf("Namespace: %s, Documents: %d\n", ns.Name, ns.DocumentCount) +} +``` + +**Returns:** Array of namespace info with document counts + +**Scope Required**: `datasources.read` + +**Note:** Document count may be `-1` if not supported by the vector store. + +#### Delete Namespace + +Delete an entire namespace/collection (bulk operation): + +```go +// Requires confirm=true for safety +err := ai_studio_sdk.DeleteNamespace(ctx, datasourceID, "old-namespace", true) +``` + +**Parameters:** +- `namespace`: namespace/collection name to delete +- `confirm`: must be `true` to proceed (safety check) + +**Scope Required**: `datasources.write` + +**Warning:** This is a destructive operation that deletes all documents in the namespace. Use with caution. + +**Supported Vector Stores:** +- ✅ Full support: Chroma, PGVector, Pinecone, Weaviate +- ⚠️ Limited: Redis (delete/query by metadata not fully supported) +- ⚠️ Partial: Qdrant (namespace management only) + +## Schedule Management + +**Scope Required**: `scheduler.manage` +**Available in**: AI Studio only + +Plugins can programmatically manage their scheduled tasks using the Schedule Management API. This complements manifest-based schedule declarations. + +### Overview + +Schedules can be created in two ways: +1. **Manifest Schedules**: Declared in `plugin.manifest.json`, auto-registered when plugin loads +2. **API Schedules**: Created programmatically via SDK during `Initialize()` or at runtime + +Both types execute via the `ExecuteScheduledTask()` capability method. + +### CreateSchedule + +Create a new schedule for your plugin: + +```go +schedule, err := ai_studio_sdk.CreateSchedule( + ctx, + "hourly-sync", // Schedule ID (unique per plugin) + "Hourly Data Sync", // Human-readable name + "0 * * * *", // Cron expression (5-field format) + "UTC", // Timezone + 120, // Timeout in seconds + map[string]interface{}{ // Config passed to ExecuteScheduledTask + "batch_size": 100, + }, + true, // Enabled +) +``` + +**Returns**: `*mgmtpb.ScheduleInfo` with schedule details +**Errors**: `AlreadyExists` if schedule_id already exists for this plugin + +### GetSchedule + +Retrieve schedule details by manifest schedule ID: + +```go +schedule, err := ai_studio_sdk.GetSchedule(ctx, "hourly-sync") +``` + +**Returns**: `*mgmtpb.ScheduleInfo` +**Errors**: `NotFound` if schedule doesn't exist + +### ListSchedules + +Get all schedules for your plugin: + +```go +schedules, err := ai_studio_sdk.ListSchedules(ctx) +``` + +**Returns**: `[]*mgmtpb.ScheduleInfo` array + +### UpdateSchedule + +Update schedule fields (all fields optional): + +```go +enabled := false +timeout := int32(180) + +schedule, err := ai_studio_sdk.UpdateSchedule(ctx, "hourly-sync", ai_studio_sdk.UpdateScheduleOptions{ + Name: stringPtr("Updated Sync Task"), + CronExpr: stringPtr("30 * * * *"), // Every hour at :30 + Timezone: stringPtr("America/New_York"), + TimeoutSeconds: &timeout, + Enabled: &enabled, + Config: map[string]interface{}{ + "batch_size": 200, + }, +}) +``` + +**Returns**: `*mgmtpb.ScheduleInfo` with updated schedule +**Errors**: `NotFound` if schedule doesn't exist + +### DeleteSchedule + +Remove a schedule: + +```go +err := ai_studio_sdk.DeleteSchedule(ctx, "hourly-sync") +``` + +**Errors**: `NotFound` if schedule doesn't exist + +### Complete Example + +```go Expandable +package main + +import ( + "context" + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" +) + +type MyPlugin struct { + plugin_sdk.BasePlugin +} + +// Initialize creates API-managed schedules +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + if ctx.Runtime != plugin_sdk.RuntimeStudio { + return nil + } + + apiCtx := context.Background() + + // Check if schedule exists (idempotent) + if _, err := ai_studio_sdk.GetSchedule(apiCtx, "data-refresh"); err != nil { + // Create new schedule + _, err := ai_studio_sdk.CreateSchedule( + apiCtx, + "data-refresh", + "Refresh External Data", + "*/15 * * * *", // Every 15 minutes + "UTC", + 300, // 5 minute timeout + map[string]interface{}{ + "api_endpoint": "https://api.example.com/data", + }, + true, + ) + if err != nil { + return fmt.Errorf("failed to create schedule: %w", err) + } + } + + return nil +} + +// ExecuteScheduledTask handles all scheduled executions +func (p *MyPlugin) ExecuteScheduledTask(ctx plugin_sdk.Context, schedule *plugin_sdk.Schedule) error { + switch schedule.ID { + case "data-refresh": + return p.refreshData(ctx, schedule) + default: + return fmt.Errorf("unknown schedule: %s", schedule.ID) + } +} + +func (p *MyPlugin) refreshData(ctx plugin_sdk.Context, schedule *plugin_sdk.Schedule) error { + // Access config from schedule + endpoint := schedule.Config["api_endpoint"].(string) + + // Perform sync logic... + ctx.Services.Logger().Info("Refreshing data", "endpoint", endpoint) + + return nil +} +``` + +### Manifest vs API Schedules + +**Use Manifest When**: +- Schedule is core to plugin functionality +- Configuration is static +- Want schedules registered automatically + +**Use API When**: +- Schedules are dynamic (based on external data) +- Need runtime modification +- Want conditional schedule creation +- Building schedule management UI + +**Example**: Plugin manifest declares one immutable daily report, creates hourly syncs via API based on configured data sources. + +## Best Practices Summary + +1. **Connection Warmup**: Implement `SessionAware` and warm up Service API in `OnSessionReady` - this is critical for reliable API access +2. **Runtime Detection**: Always check `ctx.Runtime` before calling runtime-specific services +3. **Type Assertions**: Gateway and Studio services return `interface{}`, type assert to correct response types +4. **Error Handling**: Always check errors from Service API calls +5. **Logging**: Use `ctx.Services.Logger()` for consistent structured logging +6. **KV Storage**: Understand storage differences between Studio (durable) and Gateway (ephemeral) +7. **Shared Connections**: Event Service and Management Service API share the same broker connection - one warmup establishes both +8. **Context Timeouts**: Use context timeouts for external calls +9. **Caching**: Cache frequently accessed data in KV storage to reduce API calls + +## Complete Examples + +For complete working examples of Service API usage: +- **Studio**: `examples/plugins/studio/service-api-test/` - Comprehensive Studio Services testing +- **Gateway**: `examples/plugins/gateway/gateway-service-test/` - Gateway Services examples +- **Rate Limiter**: `examples/plugins/studio/llm-rate-limiter-multiphase/` - Multi-capability plugin with KV storage +- **Scheduler**: `examples/plugins/studio/scheduler-demo/` - Scheduled tasks with manifest and API patterns diff --git a/ai-management/ai-studio/plugins/studio-agent.mdx b/ai-management/ai-studio/plugins/studio-agent.mdx new file mode 100644 index 0000000000..1b81a6d40b --- /dev/null +++ b/ai-management/ai-studio/plugins/studio-agent.mdx @@ -0,0 +1,1206 @@ +--- +title: "AI Studio Agent Plugins" +description: "Learn how to build conversational AI experiences in the Chat Interface using Agent plugins, which can wrap LLMs, add custom logic, and integrate external services." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "AI Studio Agent" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +> **Experimental Feature**: Agent plugins are currently experimental. The API and behavior may change in future releases. + +```mermaid +graph TD + A[Chat Interface] --> B[Agent Plugin] + B --> C[LLM] + B --> D[Custom Logic/Tools] +``` + +AI Studio Agent plugins enable conversational AI experiences in the Chat Interface using the **Unified Plugin SDK**. Build custom agents that wrap LLMs, add specialized logic, integrate external services, and create sophisticated multi-turn conversations with streaming responses. + +Agent plugins use the same `pkg/plugin_sdk` as other plugin types, automatically detecting the Studio runtime and providing access to Studio Services (LLM calls, tool execution, datasource queries). + +## Architecture Overview + +Agent plugins follow a **three-tier binding model**: + +```mermaid +flowchart LR + subgraph "AGENT ARCHITECTURE" + direction LR + Plugin["Plugin
(gRPC)

Implements
HandleAgentMessage
for conversations"] + Agent["Agent Object
(AgentConfig)

Binds plugin
to an App with:
- Name & slug
- Config
- Group access
- Active state"] + App["App Object

Provides
- LLM access
- Tools
- Datasources
- Credentials
- Budget control"] + + Plugin --> Agent + Agent --> App + end +``` + +### Key Concepts + +| Component | Purpose | +|-----------|---------| +| **Plugin** | Long-running gRPC plugin implementing `AgentPlugin` interface | +| **Agent Object** | Configuration binding a plugin to an App, with access controls | +| **App Object** | Resource container providing LLMs, tools, datasources, and credentials | + +**Important points:** + +1. **Plugins are reusable**: A single plugin can power multiple Agent Objects +2. **Apps provide resources**: The App determines which LLMs the agent can call +3. **Access via Groups**: Users access agents based on group membership +4. **Budget enforcement**: LLM calls are routed through the proxy, enforcing budgets +5. **Portal integration**: Active agents appear in the **Chat** section of the AI Portal alongside managed chats + +## Overview + +Agent plugins enable you to: + +- **Stream Responses**: Real-time server-streaming for interactive conversations +- **Call LLMs**: Access managed LLMs via Context Services or direct SDK calls +- **Execute Tools**: Run registered tools and integrate external services +- **Query Datasources**: Access configured datasources for RAG and context +- **Maintain Context**: Access full conversation history +- **Custom Configuration**: Per-agent config with JSON schema validation +- **Universal Services**: KV storage and logging via Context.Services + +## Unified SDK Integration + +Agent plugins use the **Unified Plugin SDK** (`pkg/plugin_sdk`), just like all other plugin types. Key patterns: + +### Import and Structure + +```go +import "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + +type MyAgentPlugin struct { + plugin_sdk.BasePlugin +} + +func NewMyAgentPlugin() *MyAgentPlugin { + return &MyAgentPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-agent", "1.0.0", "Description", + ), + } +} +``` + +### Lifecycle Methods + +```go Expandable +// Initialize - called when plugin starts +func (p *MyAgentPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + // Extract broker ID for Service API calls + if brokerIDStr, ok := config["_service_broker_id"]; ok { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + } + return nil +} + +// Shutdown - called when plugin stops +func (p *MyAgentPlugin) Shutdown(ctx plugin_sdk.Context) error { + return nil +} +``` + +### Agent Capability + +Implement the `AgentPlugin` capability: + +```go Expandable +// HandleAgentMessage processes incoming messages and streams responses +func (p *MyAgentPlugin) HandleAgentMessage( + req *pb.AgentMessageRequest, + stream pb.PluginService_HandleAgentMessageServer) error { + + // Stream chunks to user + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: "Hello!", + IsFinal: false, + }) + + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_DONE, + IsFinal: true, + }) + + return nil +} +``` + +### Serving the Plugin + +```go +func main() { + plugin_sdk.Serve(NewMyAgentPlugin()) +} +``` + +### Service API Access + +Agent plugins can call LLMs, execute tools, and query datasources via the `ai_studio_sdk` helper functions (requires broker ID): + +```go +// Call LLM +llmStream, err := ai_studio_sdk.CallLLM( + ctx, llmID, model, messages, temperature, maxTokens, tools, stream, +) + +// Execute tool +result, err := ai_studio_sdk.ExecuteTool(ctx, toolID, operation, params) + +// Query datasource +result, err := ai_studio_sdk.QueryDatasource(ctx, dsID, query) +``` + +## Quick Start + +### 1. Project Structure + +``` +my-agent-plugin/ +├── server/ +│ ├── main.go # Plugin server +│ ├── plugin.manifest.json # Plugin manifest +│ └── config.schema.json # Configuration schema +└── go.mod +``` + +### 2. Create Manifest + +[server/plugin.manifest.json](https://github.com/TykTechnologies/ai-studio/blob/main/examples/plugins/studio/echo-agent/server/plugin.manifest.json): + +```json Expandable +{ + "id": "com.example.my-agent", + "name": "My Agent", + "version": "1.0.0", + "plugin_type": "agent", + "description": "Custom conversational agent", + "permissions": { + "services": [ + "llms.proxy", + "tools.execute", + "datasources.query" + ] + }, + "ui": { + "slots": [] + } +} +``` + +### 3. Implement Agent Plugin + +[server/main.go](https://github.com/TykTechnologies/ai-studio/blob/main/examples/plugins/studio/echo-agent/server/main.go): + +```go Expandable +package main + +import ( + _ "embed" + "fmt" + "io" + "log" + + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" + mgmt "github.com/TykTechnologies/midsommar/v2/proto/ai_studio_management" +) + +//go:embed plugin.manifest.json +var manifestFile []byte + +//go:embed config.schema.json +var configSchemaFile []byte + +type MyAgent struct { + plugin_sdk.BasePlugin +} + +func NewMyAgent() *MyAgent { + return &MyAgent{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-agent", + "1.0.0", + "Custom conversational agent", + ), + } +} + +// Initialize is called when plugin starts +func (p *MyAgent) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + log.Printf("My Agent initializing") + + // Extract broker ID for Service API access + brokerIDStr := "" + if id, ok := config["_service_broker_id"]; ok { + brokerIDStr = id + } else if id, ok := config["service_broker_id"]; ok { + brokerIDStr = id + } + + if brokerIDStr != "" { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + log.Printf("My Agent: Set broker ID %d for Service API", brokerID) + } + + return nil +} + +// Shutdown is called when plugin stops +func (p *MyAgent) Shutdown(ctx plugin_sdk.Context) error { + log.Printf("My Agent shutting down") + return nil +} + +// GetManifest returns the plugin manifest +func (p *MyAgent) GetManifest() ([]byte, error) { + return manifestFile, nil +} + +// GetConfigSchema returns the configuration schema +func (p *MyAgent) GetConfigSchema() ([]byte, error) { + return configSchemaFile, nil +} + +// HandleAgentMessage processes incoming messages and streams responses +func (p *MyAgent) HandleAgentMessage( + req *pb.AgentMessageRequest, + stream pb.PluginService_HandleAgentMessageServer) error { + + log.Printf("Received message: %s", req.UserMessage) + + // Select LLM (use first available or configured default) + var selectedLLM *pb.AgentLLMInfo + if len(req.AvailableLlms) > 0 { + selectedLLM = req.AvailableLlms[0] + } + + if selectedLLM == nil { + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: "No LLM available", + IsFinal: true, + }) + } + + // Build messages from history + current message + messages := buildMessages(req.History, req.UserMessage) + + // Call LLM via SDK + llmStream, err := ai_studio_sdk.CallLLM( + stream.Context(), + selectedLLM.Id, + selectedLLM.DefaultModel, + messages, + 0.7, // temperature + 1000, // max tokens + nil, // tools + false, // non-streaming + ) + if err != nil { + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: fmt.Sprintf("Failed to call LLM: %v", err), + IsFinal: true, + }) + } + + // Receive and forward response + var llmContent string + for { + resp, err := llmStream.Recv() + if err == io.EOF { + break + } + if err != nil { + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: fmt.Sprintf("Error receiving response: %v", err), + IsFinal: true, + }) + } + + if !resp.Success { + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: fmt.Sprintf("LLM error: %s", resp.ErrorMessage), + IsFinal: true, + }) + } + + llmContent += resp.Content + + if resp.Done { + break + } + } + + // Send content chunk + if err := stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: llmContent, + IsFinal: false, + }); err != nil { + return err + } + + // Send done chunk + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_DONE, + Content: "completed", + IsFinal: true, + }) +} + +func buildMessages(history []*pb.AgentConversationMessage, userMessage string) []*mgmt.LLMMessage { + messages := make([]*mgmt.LLMMessage, 0, len(history)+1) + + // Add history + for _, msg := range history { + messages = append(messages, &mgmt.LLMMessage{ + Role: msg.Role, + Content: msg.Content, + }) + } + + // Add current message + messages = append(messages, &mgmt.LLMMessage{ + Role: "user", + Content: userMessage, + }) + + return messages +} + +func main() { + log.Printf("Starting My Agent") + plugin_sdk.Serve(NewMyAgent()) +} +``` + +### 4. Build and Deploy + +```bash Expandable +# Build plugin +go build -o my-agent main.go + +# Create plugin in AI Studio +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "name": "My Agent", + "slug": "my-agent", + "command": "file:///path/to/my-agent", + "hook_type": "agent", + "plugin_type": "agent", + "is_active": true + }' +``` + +### 5. Create Agent Object + +Create an Agent Object that binds the plugin to an App: + +**Prerequisites:** +- An active plugin with `hook_type: agent` +- An App with at least one LLM assigned, active credential, and optionally tools and datasources + +```bash Expandable +curl -X POST http://localhost:3000/api/v1/agents \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "My Custom Agent", + "description": "An agent that helps with specific tasks", + "plugin_id": 1, + "app_id": 1, + "config": { + "system_prompt": "You are a helpful assistant", + "temperature": 0.7 + }, + "group_ids": [1, 2], + "is_active": true + }' +``` + +**Agent Object Fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `name` | `string` | Display name for the agent | +| `description` | `string` | Description shown to users | +| `plugin_id` | `uint` | ID of the agent plugin to use | +| `app_id` | `uint` | ID of the App providing resources | +| `config` | `object` | Plugin-specific configuration (passed as `ConfigJson`) | +| `group_ids` | `[]uint` | Groups that can access this agent (empty = public) | +| `is_active` | `bool` | Whether the agent is available to users | +| `namespace` | `string` | Optional namespace for multi-tenant deployments | + +### 6. Use in Chat Interface + +Active agents appear in the **Chat** section of the AI Portal alongside managed chats. Users see agents they have access to based on group membership. + +**SSE Communication Flow:** + +1. **Establish SSE connection**: + ``` + GET /api/agents/{id}/stream?token=... + ``` + +2. **Receive session info** (first message): + ```json + { + "session_id": "agent-1-123", + "agent_id": 1, + "agent_name": "My Agent", + "available_llms": 3, + "available_tools": 2, + "available_datasources": 1 + } + ``` + +3. **Send messages** via POST: + ```bash + curl -X POST "http://localhost:3000/api/agents/1/message?session_id=agent-1-123" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "message": "Hello, can you help me?", + "history": [] + }' + ``` + +4. **Receive streaming response** via SSE: + ``` + event: content + data: {"type":"CONTENT","content":"Hello! How can I help?","is_final":false} + + event: done + data: {"type":"DONE","content":"completed","is_final":true} + ``` + +## Agent Message Request + +The `AgentMessageRequest` provides rich context for your agent: + +```go +type AgentMessageRequest struct { + SessionId string // Unique session ID + UserMessage string // Current user message + AvailableTools []*AgentToolInfo // Tools agent can use + AvailableDatasources []*AgentDatasourceInfo // Datasources available + AvailableLlms []*AgentLLMInfo // LLMs available + ConfigJson string // Agent configuration (JSON) + History []*AgentConversationMessage // Conversation history + Context *PluginContext // Request context +} +``` + +### Available Tools + +```go +for _, tool := range req.AvailableTools { + log.Printf("Tool: %s (%s) - %s", tool.Name, tool.Slug, tool.Description) +} +``` + +### Available Datasources + +```go +for _, ds := range req.AvailableDatasources { + log.Printf("Datasource: %s (%s) - %s", ds.Name, ds.DbSourceType, ds.Description) +} +``` + +### Available LLMs + +```go +for _, llm := range req.AvailableLlms { + log.Printf("LLM: %s - %s %s", llm.Name, llm.Vendor, llm.DefaultModel) +} +``` + +### Configuration + +Parse custom configuration from JSON: + +```go +type Config struct { + SystemPrompt string `json:"system_prompt"` + Temperature float64 `json:"temperature"` + MaxTokens int `json:"max_tokens"` +} + +var config Config +if req.ConfigJson != "" { + if err := json.Unmarshal([]byte(req.ConfigJson), &config); err == nil { + log.Printf("Using config: system_prompt=%s, temp=%.2f", + config.SystemPrompt, config.Temperature) + } +} +``` + +### Conversation History + +```go +log.Printf("Conversation has %d messages", len(req.History)) +for i, msg := range req.History { + log.Printf(" [%d] %s: %s", i, msg.Role, msg.Content) +} +``` + +## Streaming Responses + +Agent plugins use server-streaming gRPC to send real-time responses: + +### Chunk Types + +```go +const ( + CONTENT = "CONTENT" // Response content + TOOL_CALL = "TOOL_CALL" // Tool is being called + TOOL_RESULT = "TOOL_RESULT" // Tool result + THINKING = "THINKING" // Agent reasoning + ERROR = "ERROR" // Error occurred + DONE = "DONE" // Response complete +) +``` + +### Send Content + +```go +stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: "Here is my response...", + IsFinal: false, +}) +``` + +### Send Thinking + +```go +stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_THINKING, + Content: "Analyzing the question...", + IsFinal: false, +}) +``` + +### Send Tool Call + +```go +metadata := map[string]interface{}{ + "tool_name": "weather_api", + "operation": "get_forecast", + "parameters": map[string]string{"city": "San Francisco"}, +} +metadataJSON, _ := json.Marshal(metadata) + +stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_TOOL_CALL, + Content: "Calling weather API...", + MetadataJson: string(metadataJSON), + IsFinal: false, +}) +``` + +### Send Error + +```go +stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: "Failed to process request: invalid input", + IsFinal: true, +}) +``` + +### Send Done + +```go +stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_DONE, + Content: "completed", + IsFinal: true, // Always set IsFinal=true for DONE +}) +``` + +## Complete Example: Echo Agent + +A simple agent that wraps LLM responses with custom formatting: + +```go Expandable +package main + +import ( + _ "embed" + "encoding/json" + "fmt" + "io" + "log" + + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" + mgmt "github.com/TykTechnologies/midsommar/v2/proto/ai_studio_management" +) + +//go:embed plugin.manifest.json +var manifestFile []byte + +//go:embed config.schema.json +var configSchemaFile []byte + +type EchoAgentPlugin struct { + plugin_sdk.BasePlugin + prefix string + suffix string + includeMetadata bool +} + +type Config struct { + Prefix string `json:"prefix"` + Suffix string `json:"suffix"` + IncludeMetadata bool `json:"include_metadata"` +} + +func NewEchoAgentPlugin() *EchoAgentPlugin { + return &EchoAgentPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "echo-agent", + "1.0.0", + "Wraps LLM responses with prefix/suffix", + ), + prefix: "<<", + suffix: ">>", + includeMetadata: false, + } +} + +func (p *EchoAgentPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + log.Printf("EchoAgent: Initialize called") + + // Extract broker ID for Service API access + brokerIDStr := "" + if id, ok := config["_service_broker_id"]; ok { + brokerIDStr = id + } else if id, ok := config["service_broker_id"]; ok { + brokerIDStr = id + } + + if brokerIDStr != "" { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + log.Printf("EchoAgent: Set broker ID %d for Service API", brokerID) + } + + log.Println("✅ EchoAgent: Initialized successfully") + return nil +} + +func (p *EchoAgentPlugin) Shutdown(ctx plugin_sdk.Context) error { + log.Println("EchoAgent: Shutdown called") + return nil +} + +func (p *EchoAgentPlugin) GetManifest() ([]byte, error) { + return manifestFile, nil +} + +func (p *EchoAgentPlugin) GetConfigSchema() ([]byte, error) { + return configSchemaFile, nil +} + +func (p *EchoAgentPlugin) HandleAgentMessage( + req *pb.AgentMessageRequest, + stream pb.PluginService_HandleAgentMessageServer) error { + + log.Printf("EchoAgent: Received message: %s", req.UserMessage) + + // Parse config from request if present + if req.ConfigJson != "" { + var config Config + if err := json.Unmarshal([]byte(req.ConfigJson), &config); err == nil { + if config.Prefix != "" { + p.prefix = config.Prefix + } + if config.Suffix != "" { + p.suffix = config.Suffix + } + p.includeMetadata = config.IncludeMetadata + log.Printf("EchoAgent: Using custom config - prefix: %s, suffix: %s", + p.prefix, p.suffix) + } + } + + // Select LLM to use + var selectedLLM *pb.AgentLLMInfo + if len(req.AvailableLlms) > 0 { + selectedLLM = req.AvailableLlms[0] + } + + // Fall back to echo mode if no LLM available + if selectedLLM == nil { + log.Println("EchoAgent: No LLM available, using echo mode") + return p.echoMode(req.UserMessage, stream) + } + + log.Printf("EchoAgent: Using LLM: %s (ID: %d, Vendor: %s, Model: %s)", + selectedLLM.Name, selectedLLM.Id, selectedLLM.Vendor, selectedLLM.DefaultModel) + + // Call LLM via SDK + return p.callLLM(req, selectedLLM, stream) +} + +// echoMode is the fallback mode that just echoes the message +func (p *EchoAgentPlugin) echoMode(userMessage string, stream pb.PluginService_HandleAgentMessageServer) error { + wrappedContent := fmt.Sprintf("%s %s %s", p.prefix, userMessage, p.suffix) + log.Printf("EchoAgent: Sending wrapped echo response: %s", wrappedContent) + + // Send content chunk + if err := stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: wrappedContent, + IsFinal: false, + }); err != nil { + return err + } + + // Send done chunk + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_DONE, + Content: "completed", + IsFinal: true, + }) +} + +// callLLM calls the LLM via SDK and streams back the wrapped response +func (p *EchoAgentPlugin) callLLM( + req *pb.AgentMessageRequest, + llm *pb.AgentLLMInfo, + stream pb.PluginService_HandleAgentMessageServer) error { + + ctx := stream.Context() + + // Build LLM messages from history + current message + messages := []*mgmt.LLMMessage{} + + // Add history + for _, histMsg := range req.History { + messages = append(messages, &mgmt.LLMMessage{ + Role: histMsg.Role, + Content: histMsg.Content, + }) + } + + // Add current user message + messages = append(messages, &mgmt.LLMMessage{ + Role: "user", + Content: req.UserMessage, + }) + + log.Printf("EchoAgent: Calling LLM %d with %d messages via SDK", llm.Id, len(messages)) + + // Use SDK's CallLLM helper + llmStream, err := ai_studio_sdk.CallLLM( + ctx, + llm.Id, + llm.DefaultModel, + messages, + 0.7, // temperature + 1000, // max tokens + nil, // no tools + false, // non-streaming + ) + if err != nil { + log.Printf("EchoAgent: Failed to call LLM via SDK: %v", err) + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: fmt.Sprintf("Failed to call LLM: %v", err), + IsFinal: true, + }) + } + + // Receive response from LLM + var llmContent string + for { + resp, err := llmStream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Printf("EchoAgent: Error receiving from LLM: %v", err) + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: fmt.Sprintf("Error receiving LLM response: %v", err), + IsFinal: true, + }) + } + + if !resp.Success { + log.Printf("EchoAgent: LLM returned error: %s", resp.ErrorMessage) + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_ERROR, + Content: fmt.Sprintf("LLM error: %s", resp.ErrorMessage), + IsFinal: true, + }) + } + + llmContent += resp.Content + + if resp.Done { + break + } + } + + log.Printf("EchoAgent: Received LLM response (%d chars)", len(llmContent)) + + // Wrap LLM response with prefix/suffix + wrappedContent := fmt.Sprintf("%s %s %s", p.prefix, llmContent, p.suffix) + + // Send wrapped content + if err := stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: wrappedContent, + IsFinal: false, + }); err != nil { + return err + } + + // Optionally include metadata + if p.includeMetadata { + metadata := map[string]interface{}{ + "llm_id": llm.Id, + "llm_name": llm.Name, + "llm_model": llm.DefaultModel, + "tokens": len(llmContent), + } + metadataJSON, _ := json.Marshal(metadata) + + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: fmt.Sprintf("\n\n---\nMetadata: %s", string(metadataJSON)), + MetadataJson: string(metadataJSON), + IsFinal: false, + }) + } + + // Send done chunk + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_DONE, + Content: "completed", + IsFinal: true, + }) +} + +func main() { + log.Printf("🤖 Starting Echo Agent Plugin") + plugin_sdk.Serve(NewEchoAgentPlugin()) +} +``` + +### Configuration Schema + +[config.schema.json](https://github.com/TykTechnologies/ai-studio/blob/main/examples/plugins/studio/echo-agent/server/config.schema.json): + +```json Expandable +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Echo Agent Configuration", + "properties": { + "prefix": { + "type": "string", + "description": "Prefix to add before LLM response", + "default": "<<" + }, + "suffix": { + "type": "string", + "description": "Suffix to add after LLM response", + "default": ">>" + }, + "include_metadata": { + "type": "boolean", + "description": "Include metadata in response", + "default": false + } + } +} +``` + +## Advanced Patterns + +### Multi-Step Reasoning + +```go Expandable +func (p *MyAgent) HandleAgentMessage( + req *pb.AgentMessageRequest, + stream pb.PluginService_HandleAgentMessageServer) error { + + // Step 1: Think + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_THINKING, + Content: "Analyzing the question...", + IsFinal: false, + }) + + // Step 2: Search datasource + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_TOOL_CALL, + Content: "Searching knowledge base...", + IsFinal: false, + }) + + searchResults := p.searchKnowledgeBase(req.UserMessage) + + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_TOOL_RESULT, + Content: fmt.Sprintf("Found %d results", len(searchResults)), + IsFinal: false, + }) + + // Step 3: Generate response with context + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_THINKING, + Content: "Generating response with retrieved context...", + IsFinal: false, + }) + + // Build prompt with context + contextualPrompt := buildPromptWithContext(req.UserMessage, searchResults) + + // Call LLM + response := p.callLLMWithContext(contextualPrompt) + + // Step 4: Send final response + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_CONTENT, + Content: response, + IsFinal: false, + }) + + return stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_DONE, + Content: "completed", + IsFinal: true, + }) +} +``` + +### Tool Integration + +```go Expandable +func (p *MyAgent) executeTool(ctx context.Context, toolID uint32, operation string, params map[string]interface{}) (string, error) { + // Execute tool via SDK + result, err := ai_studio_sdk.ExecuteTool(ctx, toolID, operation, params) + if err != nil { + return "", err + } + + return result.Data, nil +} + +func (p *MyAgent) HandleAgentMessage( + req *pb.AgentMessageRequest, + stream pb.PluginService_HandleAgentMessageServer) error { + + // Check if user wants to search + if strings.Contains(strings.ToLower(req.UserMessage), "search") { + // Find search tool + var searchTool *pb.AgentToolInfo + for _, tool := range req.AvailableTools { + if tool.Slug == "web-search" { + searchTool = tool + break + } + } + + if searchTool != nil { + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_TOOL_CALL, + Content: "Searching the web...", + IsFinal: false, + }) + + result, err := p.executeTool(stream.Context(), searchTool.Id, "search", map[string]interface{}{ + "query": extractSearchQuery(req.UserMessage), + }) + + if err == nil { + stream.Send(&pb.AgentMessageChunk{ + Type: pb.AgentMessageChunk_TOOL_RESULT, + Content: fmt.Sprintf("Search results: %s", result), + IsFinal: false, + }) + } + } + } + + // Continue with LLM call... + return p.callLLM(req, stream) +} +``` + +## Best Practices + +### Performance + +- Use non-blocking I/O for external calls +- Stream responses as they arrive +- Set appropriate timeouts +- Cache frequently accessed data +- Minimize LLM calls where possible + +### Error Handling + +- Always send ERROR chunk on failures +- Set `IsFinal=true` for ERROR chunks +- Provide descriptive error messages +- Log errors with context (session ID, plugin ID) +- Implement graceful fallbacks + +### User Experience + +- Send THINKING chunks for long operations +- Stream content as it's generated +- Provide progress updates +- Use metadata for rich responses +- Always send DONE chunk + +### Security + +- Validate all inputs +- Sanitize user messages +- Don't expose sensitive data in responses +- Use permission scopes appropriately +- Log security-relevant events + +## API Reference + +### Agent Management (Admin Only) + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/v1/agents` | GET | List all accessible agents | +| `/api/v1/agents` | POST | Create new agent config | +| `/api/v1/agents/{id}` | GET | Get agent details | +| `/api/v1/agents/{id}` | PUT | Update agent config | +| `/api/v1/agents/{id}` | DELETE | Delete agent config | +| `/api/v1/agents/{id}/activate` | POST | Activate agent | +| `/api/v1/agents/{id}/deactivate` | POST | Deactivate agent | + +### Agent Communication (Users) + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/agents/{id}/stream` | GET | Establish SSE connection | +| `/api/agents/{id}/message` | POST | Send message to active session | + +### SessionAware Pattern (Recommended) + +Agent plugins should implement the `SessionAware` interface to warm up the Service API connection: + +```go +// OnSessionReady is called when the session broker is ready +func (p *MyAgentPlugin) OnSessionReady(ctx plugin_sdk.Context) { + // Warm up Service API connection + if ai_studio_sdk.IsInitialized() { + _, _ = ai_studio_sdk.GetPluginsCount(context.Background()) + } +} + +// OnSessionClosing is called when the session is closing +func (p *MyAgentPlugin) OnSessionClosing(ctx plugin_sdk.Context) { + // Clean up session resources +} +``` + +### Service Broker ID + +For LLM calls via the Service API, always set the service broker ID from the request: + +```go +func (p *MyAgentPlugin) HandleAgentMessage(req *pb.AgentMessageRequest, stream pb.PluginService_HandleAgentMessageServer) error { + // Critical: Set service broker ID for LLM calls + if req.ServiceBrokerId != 0 { + ai_studio_sdk.SetServiceBrokerID(req.ServiceBrokerId) + } + // ... rest of handler +} +``` + +## Troubleshooting + + + + + +- Check `plugin_type` is `"agent"` +- Verify agent configuration is created for an app +- Ensure plugin is active (`is_active: true`) +- Check logs for initialization errors + + + + + +- Verify `llms.proxy` permission in manifest +- Check SDK is initialized +- Ensure LLMs are available in app configuration +- Review LLM provider credentials + + + + + +- Ensure you're sending chunks sequentially +- Always set `IsFinal=true` for final chunk +- Send DONE chunk at the end +- Check for errors in `stream.Send()` + + + + + +- Verify plugin context is passed correctly +- Check broker ID is set for service API calls +- Ensure session ID is provided +- Review plugin initialization + + + + + +## Working Example + +See the **Echo Agent** example in the repository: + +**Path**: [`examples/plugins/studio/echo-agent/`](https://github.com/TykTechnologies/ai-studio/tree/main/examples/plugins/studio/echo-agent) + +The Echo Agent demonstrates: +- Basic `HandleAgentMessage` implementation +- LLM selection from available LLMs +- Calling LLMs via `ai_studio_sdk.CallLLM()` +- Streaming responses back to users +- Configuration handling via JSON schema +- Session warmup with `OnSessionReady` + +## Limitations (Experimental) + +- No built-in conversation persistence (agents must manage their own state if needed) +- Tool execution must be implemented by the agent +- No automatic retry on LLM failures +- Single concurrent conversation per session diff --git a/ai-management/ai-studio/plugins/studio-ui.mdx b/ai-management/ai-studio/plugins/studio-ui.mdx new file mode 100644 index 0000000000..f03cfc5ab6 --- /dev/null +++ b/ai-management/ai-studio/plugins/studio-ui.mdx @@ -0,0 +1,1054 @@ +--- +title: "AI Studio UI Plugins" +description: "How to build AI Studio UI plugins to extend the admin dashboard with custom interfaces, dashboards, and management tools using the Unified Plugin SDK." +keywords: "AI Studio, AI Management, Plugin SDK" +sidebarTitle: "AI Studio UI" +--- +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +```mermaid +graph LR + A[Admin Dashboard] -->|Loads| B[WebComponent] + B -->|RPC| C[Plugin Backend] +``` + +AI Studio UI plugins extend the **admin dashboard** with custom interfaces using WebComponents. Build rich admin panels, custom dashboards, monitoring tools, and specialized management interfaces that integrate seamlessly with the AI Studio platform using the **Unified Plugin SDK**. + +> **Looking for portal (end-user) pages?** See the [AI Portal UI Plugins Guide](/ai-management/ai-studio/plugins/portal-ui) for building pages visible to portal users, not just admins. + +## Overview + +UI plugins enable you to: + +- **Add Custom Pages**: Register new routes in the dashboard +- **Extend Sidebar**: Add sections, links, and navigation +- **Serve WebComponents**: Use any frontend framework (React, Vue, Lit, etc.) +- **Call Service APIs**: Access LLMs, tools, datasources, analytics, and more +- **Store Plugin Data**: Use built-in key-value storage +- **Define RPC Methods**: Create custom backend endpoints +- **Multi-Capability**: Combine UI with middleware hooks (PostAuth, Response, Object Hooks, etc.) + +## Unified SDK Integration + +UI plugins use the **Unified Plugin SDK** (`pkg/plugin_sdk`) and can combine UI capabilities with other plugin capabilities like PostAuth, Response, or Object Hooks. + +### Key Features + +- **BasePlugin**: Convenience struct for lifecycle management +- **UIProvider**: Capability for serving UI assets and RPC methods +- **Context Services**: Access KV storage, logging, and Studio Services +- **Multi-Capability**: Implement multiple interfaces in one plugin +- **Broker Connection**: Automatic Service API access for UI interactions + +### Example: Multi-Capability Plugin + +A single plugin can provide both UI and middleware functionality: + +```go Expandable +import "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + +type MyUIPlugin struct { + plugin_sdk.BasePlugin +} + +// Implement UIProvider capability +func (p *MyUIPlugin) GetAsset(assetPath string) ([]byte, string, error) { + // Serve UI assets +} + +func (p *MyUIPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + // Handle RPC calls from UI +} + +// Implement PostAuthHandler capability (optional) +func (p *MyUIPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Process requests +} + +// Implement ObjectHooks capability (optional) +func (p *MyUIPlugin) GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) { + // Register hooks +} +``` + +**Example**: `examples/plugins/studio/llm-rate-limiter-multiphase/` combines UIProvider + PostAuth + Response in one plugin. + +##Quick Start + +### 1. Project Structure + +``` +my-ui-plugin/ +├── server/ +│ ├── main.go # Plugin server +│ ├── plugin.manifest.json # Plugin manifest +│ └── config.schema.json # Configuration schema +├── ui/ +│ ├── webc/ +│ │ └── dashboard.js # WebComponent +│ └── assets/ +│ └── icon.svg # Static assets +└── go.mod +``` + +### 2. Create Manifest + +[server/manifest.json](https://github.com/TykTechnologies/ai-studio/blob/main/examples/plugins/studio/custom-auth-ui/server/manifest.json): + +```json Expandable +{ + "id": "com.example.my-plugin", + "name": "My Plugin", + "version": "1.0.0", + "plugin_type": "ai_studio", + "permissions": { + "services": [ + "llms.read", + "apps.read", + "kv.readwrite" + ] + }, + "ui": { + "slots": [ + { + "slot": "sidebar.section", + "label": "My Plugin", + "icon": "/assets/icon.svg", + "items": [ + { + "type": "route", + "path": "/admin/my-plugin", + "title": "Dashboard", + "mount": { + "kind": "webc", + "tag": "my-plugin-dashboard", + "entry": "/ui/webc/dashboard.js", + "props": { + "apiBase": "/plugin/com.example.my-plugin/rpc" + } + } + } + ] + } + ] + }, + "rpc": { + "basePath": "/plugin/com.example.my-plugin/rpc" + } +} +``` + +### 3. Implement Plugin Server + +[server/main.go](https://github.com/TykTechnologies/ai-studio/blob/main/examples/plugins/studio/custom-auth-ui/server/main.go): + +```go Expandable +package main + +import ( + "context" + "embed" + "encoding/json" + "fmt" + "log" + + "github.com/TykTechnologies/midsommar/v2/pkg/ai_studio_sdk" + "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk" + pb "github.com/TykTechnologies/midsommar/v2/proto" +) + +//go:embed ui assets plugin.manifest.json +var embeddedAssets embed.FS + +type MyPlugin struct { + plugin_sdk.BasePlugin +} + +func NewMyPlugin() *MyPlugin { + return &MyPlugin{ + BasePlugin: plugin_sdk.NewBasePlugin( + "my-ui-plugin", + "1.0.0", + "Custom UI plugin with dashboard", + ), + } +} + +// Initialize is called when plugin starts +func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error { + log.Printf("My Plugin initializing") + + // Extract broker ID for Service API access + brokerIDStr := "" + if id, ok := config["_service_broker_id"]; ok { + brokerIDStr = id + } else if id, ok := config["service_broker_id"]; ok { + brokerIDStr = id + } + + if brokerIDStr != "" { + var brokerID uint32 + fmt.Sscanf(brokerIDStr, "%d", &brokerID) + ai_studio_sdk.SetServiceBrokerID(brokerID) + log.Printf("Set broker ID %d for Service API", brokerID) + } + + return nil +} + +// Shutdown is called when plugin stops +func (p *MyPlugin) Shutdown(ctx plugin_sdk.Context) error { + log.Printf("My Plugin shutting down") + return nil +} + +// GetAsset serves static files +func (p *MyPlugin) GetAsset(assetPath string) ([]byte, string, error) { + if assetPath[0] == '/' { + assetPath = assetPath[1:] + } + + content, err := embeddedAssets.ReadFile(assetPath) + if err != nil { + return nil, "", err + } + + mimeType := detectMimeType(assetPath) + return content, mimeType, nil +} + +// GetManifest returns the plugin manifest +func (p *MyPlugin) GetManifest() ([]byte, error) { + return embeddedAssets.ReadFile("plugin.manifest.json") +} + +// HandleCall processes RPC methods +func (p *MyPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + ctx := context.Background() + + switch method { + case "get_data": + return p.getData(ctx) + case "save_settings": + return p.saveSettings(ctx, payload) + default: + return nil, fmt.Errorf("unknown method: %s", method) + } +} + +// RPC method implementation +func (p *MyPlugin) getData(ctx context.Context) ([]byte, error) { + // Call service API to get LLMs + llmsResp, err := ai_studio_sdk.ListLLMs(ctx, 1, 10) + if err != nil { + return nil, err + } + + // Return data as JSON + return json.Marshal(map[string]interface{}{ + "llms": llmsResp.Llms, + "total": llmsResp.TotalCount, + }) +} + +func (p *MyPlugin) saveSettings(ctx context.Context, payload []byte) ([]byte, error) { + // Store settings in KV storage + _, err := ai_studio_sdk.WritePluginKV(ctx, "settings", payload) + if err != nil { + return nil, err + } + + return json.Marshal(map[string]string{ + "status": "saved", + }) +} + +// GetConfigSchema returns configuration schema +func (p *MyPlugin) GetConfigSchema() ([]byte, error) { + schema := map[string]interface{}{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": map[string]interface{}{ + "api_key": map[string]interface{}{ + "type": "string", + "description": "API key for external service", + }, + }, + } + return json.Marshal(schema) +} + +func detectMimeType(path string) string { + if strings.HasSuffix(path, ".js") { + return "application/javascript" + } else if strings.HasSuffix(path, ".css") { + return "text/css" + } else if strings.HasSuffix(path, ".svg") { + return "image/svg+xml" + } + return "application/octet-stream" +} + +func main() { + log.Printf("Starting My Plugin") + plugin_sdk.Serve(NewMyPlugin()) +} +``` + +### 4. Create WebComponent + +`ui/webc/dashboard.js`: + +```javascript Expandable +class MyPluginDashboard extends HTMLElement { + connectedCallback() { + this.innerHTML = ` +
+

My Plugin Dashboard

+
Loading...
+ +
+ `; + + this.loadData(); + + this.querySelector('#refresh').addEventListener('click', () => { + this.loadData(); + }); + } + + async loadData() { + try { + const apiBase = this.getAttribute('apiBase') || '/plugin/com.example.my-plugin/rpc'; + + const response = await fetch(`${apiBase}/get_data`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.getAuthToken()}`, + }, + body: JSON.stringify({}), + }); + + const data = await response.json(); + + this.querySelector('#content').innerHTML = ` +

Found ${data.total} LLMs

+
    + ${data.llms.map(llm => `
  • ${llm.name} (${llm.vendor})
  • `).join('')} +
+ `; + } catch (error) { + console.error('Failed to load data:', error); + this.querySelector('#content').innerHTML = `

Error: ${error.message}

`; + } + } + + getAuthToken() { + // Get token from localStorage or session + return localStorage.getItem('auth_token') || ''; + } +} + +customElements.define('my-plugin-dashboard', MyPluginDashboard); +``` + +### 5. Build and Deploy + +```bash Expandable +# Build plugin +cd server +go build -o my-plugin + +# Create plugin in AI Studio +curl -X POST http://localhost:3000/api/v1/plugins \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "name": "My Plugin", + "slug": "my-plugin", + "command": "file:///path/to/my-plugin", + "hook_type": "studio_ui", + "plugin_type": "ai_studio", + "is_active": true, + "load_immediately": true + }' +``` + +## Manifest Structure + +### Top-Level Fields + +```json +{ + "id": "com.example.plugin", // Unique identifier (reverse domain) + "name": "Plugin Name", // Display name + "version": "1.0.0", // Semantic version + "plugin_type": "ai_studio", // Must be "ai_studio" + "description": "Plugin description", // Optional + "permissions": { }, // Permission scopes + "ui": { }, // UI configuration + "rpc": { }, // RPC configuration + "assets": [], // Static asset list + "compat": { } // Compatibility info +} +``` + +### Permissions + +```json Expandable +{ + "permissions": { + "services": [ + "llms.read", // List and read LLM configurations + "llms.proxy", // Call LLMs via proxy + "llms.write", // Create/update LLMs + "tools.read", // List and read tools + "tools.execute", // Execute tools + "tools.write", // Create/update tools + "datasources.read", // List and read datasources + "datasources.query", // Query datasources + "apps.read", // List and read apps + "apps.write", // Create/update apps + "plugins.read", // List and read plugins + "analytics.read", // Read analytics data + "kv.read", // Read KV storage + "kv.readwrite" // Read/write KV storage + ] + } +} +``` + +### UI Configuration + +```json Expandable +{ + "ui": { + "slots": [ + { + "slot": "sidebar.section", // UI slot name + "label": "My Plugin", // Section label + "icon": "/assets/icon.svg", // Icon path + "items": [ + { + "type": "route", // Route type + "path": "/admin/my-plugin", // URL path + "title": "Dashboard", // Page title + "mount": { + "kind": "webc", // WebComponent + "tag": "my-dashboard", // Custom element tag + "entry": "/ui/webc/dashboard.js", // JS file + "props": { // Props passed to component + "apiBase": "/plugin/com.example/rpc" + } + } + } + ] + } + ] + } +} +``` + +### Available UI Slots + +- `sidebar.section`: Add sidebar section with nested items +- `sidebar.link`: Add individual sidebar link +- `settings.section`: Add settings page section +- `app.detail.tab`: Add tab to app detail page +- `llm.detail.tab`: Add tab to LLM detail page + +### RPC Configuration + +```json +{ + "rpc": { + "basePath": "/plugin/com.example/rpc" + } +} +``` + +RPC calls are automatically routed to your plugin's `HandleCall()` method. + +## Service API Access + +The Service API provides 100+ gRPC operations accessible via the SDK: + +### LLM Operations + +```go +// List LLMs +llmsResp, err := ai_studio_sdk.ListLLMs(ctx, page, limit) + +// Get LLM by ID +llm, err := ai_studio_sdk.GetLLM(ctx, llmID) + +// Call LLM via proxy (streaming) +stream, err := ai_studio_sdk.CallLLM(ctx, llmID, model, messages, + temperature, maxTokens, tools, true) + +// Simple non-streaming LLM call +response, err := ai_studio_sdk.CallLLMSimple(ctx, llmID, model, + "Hello, world!") +``` + +### Tool Operations + +```go +// List tools +toolsResp, err := ai_studio_sdk.ListTools(ctx, page, limit) + +// Get tool by ID +tool, err := ai_studio_sdk.GetTool(ctx, toolID) + +// Execute tool +result, err := ai_studio_sdk.ExecuteTool(ctx, toolID, operationID, params) +``` + +### Plugin Operations + +```go +// List plugins +pluginsResp, err := ai_studio_sdk.ListPlugins(ctx, page, limit) + +// Get plugin by ID +plugin, err := ai_studio_sdk.GetPlugin(ctx, pluginID) + +// Get counts +pluginsCount, err := ai_studio_sdk.GetPluginsCount(ctx) +llmsCount, err := ai_studio_sdk.GetLLMsCount(ctx) +``` + +### KV Storage Operations + +```go +// Write data +created, err := ai_studio_sdk.WritePluginKV(ctx, key, data) + +// Read data +data, err := ai_studio_sdk.ReadPluginKV(ctx, key) + +// Delete data +err := ai_studio_sdk.DeletePluginKV(ctx, key) + +// List keys +keys, err := ai_studio_sdk.ListPluginKVKeys(ctx, prefix) +``` + +### App Operations + +```go +// List apps +appsResp, err := ai_studio_sdk.ListApps(ctx, page, limit) + +// Get app by ID +app, err := ai_studio_sdk.GetApp(ctx, appID) +``` + +### Analytics Operations + +```go +// Get usage statistics +usage, err := ai_studio_sdk.GetUsageStats(ctx, startTime, endTime, filters) + +// Get cost analytics +costs, err := ai_studio_sdk.GetCostAnalytics(ctx, startTime, endTime) +``` + +## Multi-Capability Patterns + +UI plugins using the unified SDK can implement multiple capabilities in a single plugin, combining dashboard UI with request/response processing, object validation, or other hooks. + +### Combining UI + PostAuth + +Create a plugin that both displays data and processes requests: + +```go Expandable +type RateLimiterPlugin struct { + plugin_sdk.BasePlugin + limits map[uint32]int // app_id -> limit +} + +// UIProvider capability - serve dashboard +func (p *RateLimiterPlugin) GetAsset(assetPath string) ([]byte, string, error) { + return embeddedAssets.ReadFile(assetPath) +} + +func (p *RateLimiterPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + switch method { + case "get_limits": + return json.Marshal(p.limits) + case "set_limit": + var req struct { + AppID uint32 `json:"app_id"` + Limit int `json:"limit"` + } + json.Unmarshal(payload, &req) + p.limits[req.AppID] = req.Limit + return json.Marshal(map[string]string{"status": "ok"}) + } + return nil, fmt.Errorf("unknown method") +} + +// PostAuthHandler capability - enforce limits +func (p *RateLimiterPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) { + // Check rate limit from state + limit, exists := p.limits[ctx.AppID] + if !exists { + limit = 100 // default + } + + // Check current count from KV + key := fmt.Sprintf("rate:%d", ctx.AppID) + data, _ := ctx.Services.KV().Read(ctx, key) + count := 0 + if data != nil { + json.Unmarshal(data, &count) + } + + if count >= limit { + return &pb.PluginResponse{ + Block: true, + ErrorMessage: "Rate limit exceeded", + }, nil + } + + // Increment counter + count++ + countData, _ := json.Marshal(count) + ctx.Services.KV().Write(ctx, key, countData) + + return &pb.PluginResponse{Modified: false}, nil +} +``` + +### Combining UI + Object Hooks + +Create a plugin that validates objects and provides an approval dashboard: + +```go Expandable +type ApprovalPlugin struct { + plugin_sdk.BasePlugin + pendingApprovals []PendingApproval +} + +type PendingApproval struct { + ID string + ObjectType string + ObjectJSON string + Status string +} + +// UIProvider - approval dashboard +func (p *ApprovalPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + switch method { + case "list_pending": + return json.Marshal(p.pendingApprovals) + case "approve": + var req struct{ ID string } + json.Unmarshal(payload, &req) + // Approve and remove from pending + return json.Marshal(map[string]string{"status": "approved"}) + case "reject": + var req struct{ ID string } + json.Unmarshal(payload, &req) + // Reject and remove from pending + return json.Marshal(map[string]string{"status": "rejected"}) + } + return nil, fmt.Errorf("unknown method") +} + +// ObjectHooks capability - require approval +func (p *ApprovalPlugin) GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error) { + return []*pb.ObjectHookRegistration{ + { + ObjectType: "datasource", + HookTypes: []string{"before_create"}, + Priority: 10, + }, + }, nil +} + +func (p *ApprovalPlugin) HandleObjectHook(ctx plugin_sdk.Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error) { + // Add to pending approvals + approval := PendingApproval{ + ID: generateID(), + ObjectType: req.ObjectType, + ObjectJSON: req.ObjectJson, + Status: "pending", + } + p.pendingApprovals = append(p.pendingApprovals, approval) + + // Store in KV for persistence + data, _ := json.Marshal(p.pendingApprovals) + ctx.Services.KV().Write(ctx, "pending_approvals", data) + + // Block operation until manual approval + return &pb.ObjectHookResponse{ + AllowOperation: false, + RejectionReason: "Pending manual approval", + }, nil +} +``` + +### Combining UI + Response + +Monitor and modify responses with a dashboard: + +```go Expandable +type ResponseMonitorPlugin struct { + plugin_sdk.BasePlugin + stats ResponseStats +} + +// UIProvider - monitoring dashboard +func (p *ResponseMonitorPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + if method == "get_stats" { + return json.Marshal(p.stats) + } + return nil, fmt.Errorf("unknown method") +} + +// ResponseHandler capability - track responses +func (p *ResponseMonitorPlugin) OnBeforeWrite(ctx plugin_sdk.Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error) { + // Track response statistics + p.stats.TotalResponses++ + p.stats.TotalTokens += req.Tokens + + // Store in KV + data, _ := json.Marshal(p.stats) + ctx.Services.KV().Write(ctx, "response_stats", data) + + return &pb.ResponseWriteResponse{Modified: false}, nil +} +``` + +### Benefits of Multi-Capability Plugins + +1. **Unified State**: Share data structures between UI and middleware +2. **Single Deployment**: One plugin provides multiple features +3. **Consistent Configuration**: Single manifest, config, and initialization +4. **Simplified Management**: Deploy, update, and monitor as one unit +5. **Rich Dashboards**: Display real-time data from middleware hooks + +### Working Example + +See `examples/plugins/studio/llm-rate-limiter-multiphase/` for a complete multi-capability plugin that implements: +- **PostAuth**: Check rate limits before requests +- **Response**: Update counters after responses +- **UI Provider**: Dashboard showing rate limit status + +## Complete Example: Rate Limiting Dashboard + +Here's a complete example showing all features: + +### Manifest + +```json Expandable +{ + "id": "com.tyk.rate-limiting-ui", + "version": "1.0.0", + "name": "Rate Limiting UI", + "description": "Enhanced UI for rate limiting configuration", + "permissions": { + "services": [ + "plugins.read", + "llms.read", + "tools.read", + "kv.readwrite" + ] + }, + "ui": { + "slots": [ + { + "slot": "sidebar.section", + "label": "Rate Limiting", + "icon": "/assets/rate-limit.svg", + "items": [ + { + "type": "route", + "path": "/admin/rate-limiting/dashboard", + "title": "Rate Limiting Dashboard", + "mount": { + "kind": "webc", + "tag": "rate-limiting-dashboard", + "entry": "/ui/webc/dashboard.js" + } + }, + { + "type": "route", + "path": "/admin/rate-limiting/settings", + "title": "Global Settings", + "mount": { + "kind": "webc", + "tag": "rate-limiting-settings", + "entry": "/ui/webc/settings.js" + } + } + ] + } + ] + }, + "rpc": { + "basePath": "/plugin/com.tyk.rate-limiting-ui/rpc" + } +} +``` + +### RPC Methods + +```go Expandable +func (p *RateLimitingUIPlugin) HandleCall(method string, payload []byte) ([]byte, error) { + ctx := context.Background() + + switch method { + case "get_statistics": + return p.getStatistics(ctx) + case "get_rate_limits": + return p.getRateLimits(ctx) + case "set_rate_limit": + return p.setRateLimit(ctx, payload) + case "get_available_tools": + return p.getAvailableTools(ctx) + default: + return nil, fmt.Errorf("unknown method: %s", method) + } +} + +func (p *RateLimitingUIPlugin) getStatistics(ctx context.Context) ([]byte, error) { + // Get real plugin and LLM counts + pluginsCount, err := ai_studio_sdk.GetPluginsCount(ctx) + if err != nil { + return nil, err + } + + llmsCount, err := ai_studio_sdk.GetLLMsCount(ctx) + if err != nil { + return nil, err + } + + stats := map[string]interface{}{ + "total_plugins": pluginsCount, + "total_llms": llmsCount, + "blocked_requests": 142, + "success_rate": 0.991, + } + + return json.Marshal(stats) +} + +func (p *RateLimitingUIPlugin) getAvailableTools(ctx context.Context) ([]byte, error) { + // List tools using service API + toolsResp, err := ai_studio_sdk.ListTools(ctx, 1, 50) + if err != nil { + return nil, err + } + + // Convert to UI format + tools := make([]map[string]interface{}, len(toolsResp.Tools)) + for i, tool := range toolsResp.Tools { + tools[i] = map[string]interface{}{ + "id": tool.Id, + "name": tool.Name, + "slug": tool.Slug, + "description": tool.Description, + "type": tool.ToolType, + } + } + + return json.Marshal(map[string]interface{}{ + "tools": tools, + "total": toolsResp.TotalCount, + }) +} + +func (p *RateLimitingUIPlugin) setRateLimit(ctx context.Context, payload []byte) ([]byte, error) { + var config map[string]interface{} + if err := json.Unmarshal(payload, &config); err != nil { + return nil, err + } + + // Store in KV storage + _, err := ai_studio_sdk.WritePluginKV(ctx, "rate_limits", payload) + if err != nil { + return nil, err + } + + return json.Marshal(map[string]string{ + "status": "updated", + }) +} +``` + +## Frontend Frameworks + +### React + +```javascript Expandable +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom/client'; + +function RateLimitingDashboard({ apiBase }) { + const [stats, setStats] = useState(null); + + useEffect(() => { + fetchStats(); + }, []); + + async function fetchStats() { + const response = await fetch(`${apiBase}/get_statistics`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${getAuthToken()}`, + }, + }); + const data = await response.json(); + setStats(data); + } + + if (!stats) return
Loading...
; + + return ( +
+

Rate Limiting Dashboard

+
+
Plugins: {stats.total_plugins}
+
LLMs: {stats.total_llms}
+
Blocked: {stats.blocked_requests}
+
+
+ ); +} + +class RateLimitingDashboardElement extends HTMLElement { + connectedCallback() { + const apiBase = this.getAttribute('apiBase'); + const root = ReactDOM.createRoot(this); + root.render(); + } +} + +customElements.define('rate-limiting-dashboard', RateLimitingDashboardElement); +``` + +### Vue + +```javascript Expandable +import { createApp } from 'vue'; + +const DashboardApp = { + data() { + return { + stats: null, + }; + }, + mounted() { + this.fetchStats(); + }, + methods: { + async fetchStats() { + const response = await fetch(`${this.apiBase}/get_statistics`, { + method: 'POST', + }); + this.stats = await response.json(); + }, + }, + template: ` +
+

Rate Limiting Dashboard

+
+
Plugins: {{ stats.total_plugins }}
+
LLMs: {{ stats.total_llms }}
+
+
+ `, +}; + +class RateLimitingDashboardElement extends HTMLElement { + connectedCallback() { + const apiBase = this.getAttribute('apiBase'); + createApp(DashboardApp, { apiBase }).mount(this); + } +} + +customElements.define('rate-limiting-dashboard', RateLimitingDashboardElement); +``` + +## Best Practices + +### Security + +- Always validate inputs in RPC methods +- Use HTTPS for production deployments +- Sanitize HTML in WebComponents +- Don't expose sensitive data in frontend +- Use CSP headers in manifest + +### Performance + +- Lazy-load heavy components +- Cache frequently accessed data +- Use KV storage for plugin state +- Minimize Service API calls +- Bundle and minify assets + +### User Experience + +- Show loading states +- Handle errors gracefully +- Provide feedback for actions +- Use consistent UI patterns +- Support dark mode if platform does + +### Development + +- Use TypeScript for type safety +- Add proper error handling +- Log important operations +- Test with different data scenarios +- Document RPC methods + +## Troubleshooting + + + + + +- Check manifest syntax (valid JSON) +- Verify `plugin_type` is `"ai_studio"` +- Ensure `load_immediately` is `true` in plugin registration +- Check logs for initialization errors + + + + + +- Verify asset path is correct (leading `/`) +- Check browser console for JS errors +- Ensure custom element is defined +- Verify mime types are correct + + + + + +- Check permission scopes in manifest +- Verify SDK is initialized (`ai_studio_sdk.IsInitialized()`) +- Check context has valid authentication +- Review service API error messages + + + + + +- Ensure assets are embedded (`//go:embed`) +- Check `GetAsset()` normalizes paths correctly +- Verify mime type detection +- Check asset paths in manifest match filesystem + + + + diff --git a/ai-management/ai-studio/proxy.mdx b/ai-management/ai-studio/proxy.mdx new file mode 100644 index 0000000000..406a8a0f62 --- /dev/null +++ b/ai-management/ai-studio/proxy.mdx @@ -0,0 +1,120 @@ +--- +title: "Edge Gateway (Data Plane) Component" +description: "Explore Edge Gateway component of Tyk AI Studio, including architecture, configuration synchronization, and management" +keywords: "AI Studio, AI Management, Edge Gateway" +sidebarTitle: "Edge Gateway" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +The Edge Gateway operates as an independent, dedicated AI proxy. It processes AI requests, enforces policies, and reports analytics to the control plane. It is optimized for high performance and resilience in production. + +The Edge Gateway operates as an independent, dedicated AI proxy. It processes AI requests, enforces policies, and reports analytics to the control plane. It is optimized for high performance and resilience in production. + +> **Note:** The Edge Gateway is sometimes referred to as the Microgateway in configuration files. However, **Edge Gateway** is the preferred terminology. + +* **Unified Access Point:** Provides a single, consistent endpoint for applications to interact with various LLMs. +* **Security Enforcement:** Handles authentication, authorization, and applies security policies. +* **Policy Management:** Enforces rules related to budget limits, model access, and applies custom [Filters](/ai-management/ai-studio/filters). +* **Observability:** Logs detailed analytics data for each request, feeding the [Analytics & Monitoring](/ai-management/ai-studio/analytics) system. +* **Vendor Abstraction:** Hides the complexities of different LLM provider APIs, especially through the OpenAI-compatible endpoint. + +## Edge Gateway Variants + +There are two Edge Gateway variants: + +| Variant | Where it runs | Capabilities | Use case | +|---------|---------------|-------------|----------| +| **Embedded Gateway** | Inside AI Studio | LLM proxying, tool calling (REST + MCP), datasource querying. No filters, no middleware, no plugins. | Testing LLM configurations, powering the Chat interface | +| **Edge Gateway** | Standalone binary, deployed at edge | Full middleware pipeline: authentication, filters, plugins, analytics, budget enforcement, tool calling (REST + MCP), datasource querying | Production data plane in hub-and-spoke deployments | + +Both variants rely on the core proxy library and access control mechanisms. The embedded gateway is lightweight, while the Edge Gateway offers the full feature set. Tools, datasources, and OAuth state are synced to edge gateways through the hub-spoke configuration system. + +## Core Features + +1. **Request Routing:** Incoming requests include an `llmSlug` in their path (e.g., `/llm/call/{llmSlug}/...`). The Proxy uses this slug (auto-generated from the LLM configuration name) to identify the target [LLM Configuration](/ai-management/ai-studio/llm-management) and route the request accordingly. + +2. **Authentication & Authorization:** + * Validates the API key provided by the client application. + * Identifies the associated Application and User. + * Checks if the Application/team has permission to access the requested LLM Configuration based on [RBAC rules](/ai-management/ai-studio/user-management). + +3. **Policy Enforcement:** Before forwarding the request to the backend LLM, the Proxy enforces policies defined in the LLM Configuration or globally: + * **Budget Checks:** Verifies if the estimated cost exceeds the configured [Budgets](/ai-management/ai-studio/llm-management) for the App or LLM. + * **Model Access:** Ensures the requested model is allowed for the specific LLM configuration. + * **Filters:** Applies configured request [Filters](/ai-management/ai-studio/filters) to modify the incoming request payload. + +4. **Analytics Logging:** After receiving the response from the backend LLM (and potentially applying response Filters), the Proxy logs detailed information about the interaction (user, app, model, tokens used, cost, latency, etc.) to the [Analytics](/ai-management/ai-studio/analytics) database. + + +## Proxy Modes + +The Edge Gateway (and embedded gateway) offer two ways to proxy LLM traffic: + +| Mode | Endpoint | Description | Tradeoff | +|------|----------|-------------|----------| +| **SDK-Compatible** (Unified) | `/llm/call/{slug}/...` | Pass-through to the vendor's native API format. No request manipulation beyond analytics/budget tracking. | Full feature access, resilient to vendor API changes. Best for users working directly with a vendor's SDK. | +| **OpenAI-Compatible** | `/llm/call/{slug}/v1/chat/completions` | Accepts only OpenAI-format input and translates to the upstream vendor's API format. | Maximum client-side compatibility (one format for all vendors), but reduced feature access for vendor-specific capabilities. | + +Both modes support streaming and non-streaming responses. + +There are also two **legacy endpoints** (`/llm/rest/{slug}/...` and `/llm/stream/{slug}/...`) from before the unified endpoint existed. While not actively used by end-users, the underlying code is still used internally by the proxy to handle each response style. + +### LLM Slug + +The `{llmSlug}` in the endpoint path is automatically generated from the LLM configuration name when you create it. For example, an LLM named "My OpenAI Config" would have a slug like `my-openai-config`. + +## Model Router + +The Edge Gateway also includes a **Model Router** component that enables intelligent routing of requests across multiple LLM providers based on cost, performance, or availability. This allows you to build resilient AI applications that automatically failover or load balance between different models. + +For more information on configuring and using this feature, see the [Model Router documentation](/ai-management/ai-studio/model-router). + +## Configuration Reference + +To know more about configuring Edge Gateways, see the [Configuration Reference](/ai-management/ai-studio/edge-gateway-env) for detailed documentation on all environment variables. + +## Troubleshooting + + + + + +- Check network connectivity between the edge and control plane +- Verify the edge gateway is running and healthy +- Check edge gateway logs for connection errors +- Ensure firewall rules allow gRPC traffic (default port 50051) + + + + + +- Wait a few seconds for the heartbeat cycle to complete +- Check if the edge is connected (not disconnected) +- Verify the edge gateway logs for configuration load errors +- Check if the edge has sufficient permissions to fetch configuration + + + + + +- Try pushing configuration again +- Check for configuration validation errors in edge logs +- Verify the edge and control plane are running compatible versions +- Check for database replication lag if using PostgreSQL replication + + + + + +- Verify all edges have successfully loaded the new configuration +- Check for any disconnected edges that can't receive updates +- Refresh the page to ensure the latest status is displayed + + + + diff --git a/ai-management/ai-studio/quickstart.mdx b/ai-management/ai-studio/quickstart.mdx new file mode 100644 index 0000000000..3f4bad9470 --- /dev/null +++ b/ai-management/ai-studio/quickstart.mdx @@ -0,0 +1,455 @@ +--- +title: "Install Tyk AI Studio on Docker" +description: "Installation guide for the Tyk AI Studio on Docker" +keywords: "AI Studio, AI Management, Installation, Docker, Docker Compose" +sidebarTitle: "Docker" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + + +This guide focuses on the Enterprise Edition of Tyk AI Studio. For the Community Edition, please refer to the [Tyk AI Studio GitHub repository](https://github.com/TykTechnologies/ai-studio/blob/main/docs/site/docs/deployment-docker.md). + +The Community Edition uses different Docker images (`tykio/tyk-ai-studio` and `tykio/tyk-microgateway`) and does not require a license key. + + +This guide covers deploying Tyk AI Studio with a Edge Gateway using Docker Compose. In this architecture, AI Studio acts as the **control plane** (hub) and the Edge Gateway acts as the **data plane** (spoke), receiving configuration via gRPC. + +## Prerequisites + +- [Docker Engine](https://docs.docker.com/engine/install/) 20.10+ and Docker Compose v2 +- At least 4 GB RAM available +- A Tyk AI License key (contact support@tyk.io or your account manager to obtain) + + +Running on Podman, containerd, or another container runtime? See [Container Runtimes](/deployment-and-operations/container-runtimes). + + +## Generate Secrets + +Before starting, generate the required secret keys. These will be used in the configuration files to secure communication and encrypt data: + +```bash +# Secret key for encryption (used for secrets management and SSO) +openssl rand -hex 16 +# Example output: a35b3f7b0fb4dd3a048ba4fc6e9fe0a8 + +# Encryption key for Edge Gateway communication (must be exactly 32 hex chars) +openssl rand -hex 16 +# Example output: 822d3d1e0e2d849263e45fc7bb842364 + +# gRPC auth token (for hub-spoke communication) +openssl rand -hex 16 +# Example output: 9f2c4a6b8d0e1f3a5c7d9e1b3a5c7d9e +``` + +Save these values — you will need them for both the AI Studio and Edge Gateway configuration files. + +## Instructions + +### 1. Create Directory Structure + +Create a new directory for your project and set up the required folders: + +```bash +mkdir -p tyk-ai-studio/confs +mkdir -p tyk-ai-studio/studio-data +mkdir -p tyk-ai-studio/mgw-data +mkdir -p tyk-ai-studio/mgw-plugins +cd tyk-ai-studio +``` + +### 2. Create `compose.yaml` + +Create a `compose.yaml` file with the following content. This configuration sets up AI Studio, the Edge Gateway, and a PostgreSQL database. + +```yaml Expandable +networks: + tyk-network: + +services: + tyk-ai-studio: + image: tykio/tyk-ai-studio-ent:v2.0.0 + networks: + - tyk-network + volumes: + - ./confs/studio.env:/opt/tyk-ai-studio/.env + - ./studio-data:/opt/tyk-ai-studio/data + env_file: + - ./confs/studio.env + depends_on: + postgres: + condition: service_healthy + ports: + - "8080:8080" # Admin UI + REST API + - "9090:9090" # Embedded AI Gateway + restart: always + + microgateway: + image: tykio/tyk-microgateway-ent:v2.0.0 + networks: + - tyk-network + volumes: + - ./confs/microgateway.env:/opt/tyk-microgateway/.env + - ./confs/analytics-pulse.yaml:/opt/tyk-microgateway/analytics-pulse.yaml + - ./mgw-data:/opt/tyk-microgateway/data + - ./mgw-plugins:/var/lib/microgateway + env_file: + - ./confs/microgateway.env + depends_on: + tyk-ai-studio: + condition: service_started + ports: + - "9091:8080" # AI Gateway (external 9091 -> internal 8080) + restart: always + + postgres: + image: postgres:16 + networks: + - tyk-network + environment: + POSTGRES_USER: tyk + POSTGRES_PASSWORD: your-db-password + POSTGRES_DB: tyk_ai_studio + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tyk -d tyk_ai_studio"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + restart: always + +volumes: + pgdata: +``` + +### 3. Create `confs/studio.env` + +Create the AI Studio configuration file. Replace the `CHANGE-ME` values with the secrets you generated earlier, and add your Tyk AI License key. + +```env Expandable +# ============================================================================= +# Core Settings +# ============================================================================= +DEVMODE=true # Set to false when using HTTPS; required for login over plain HTTP +ALLOW_REGISTRATIONS=true +SITE_URL=http://localhost:8080 +ADMIN_EMAIL=admin@example.com +FROM_EMAIL=noreply@example.com + +# ============================================================================= +# Database +# ============================================================================= +DATABASE_TYPE=postgres +DATABASE_URL=postgresql://tyk:your-db-password@postgres:5432/tyk_ai_studio?sslmode=disable + +# ============================================================================= +# Security — CHANGE THESE (use values from "Generate Secrets" above) +# ============================================================================= +TYK_AI_SECRET_KEY=CHANGE-ME-generate-with-openssl-rand-hex-16 +MICROGATEWAY_ENCRYPTION_KEY=CHANGE-ME-generate-with-openssl-rand-hex-16 + +# ============================================================================= +# Hub-Spoke: Control Plane Mode +# ============================================================================= +GATEWAY_MODE=control +GRPC_PORT=50051 +GRPC_HOST=0.0.0.0 +GRPC_TLS_INSECURE=true +GRPC_AUTH_TOKEN=CHANGE-ME-generate-with-openssl-rand-hex-16 + +# ============================================================================= +# Proxy — Point to external Edge Gateway URL +# ============================================================================= +PROXY_URL=http://localhost:9091 +TOOL_DISPLAY_URL=http://localhost:9091 +DATASOURCE_DISPLAY_URL=http://localhost:9091 + +# ============================================================================= +# Logging +# ============================================================================= +LOG_LEVEL=info + +# ============================================================================= +# Enterprise Edition — REQUIRED for EE images, must be set before first start +# ============================================================================= +TYK_AI_LICENSE=your-license-key + +# ============================================================================= +# Plugin Marketplace (Optional — enables browsing and installing plugins) +# ============================================================================= +# AI_STUDIO_OCI_CACHE_DIR must be set to enable the marketplace. +# Without it, the Marketplace page will be empty. +AI_STUDIO_OCI_CACHE_DIR=./data/cache/plugins +AI_STUDIO_OCI_REQUIRE_SIGNATURE=false # cosign not available in distroless images + +# ============================================================================= +# SMTP (Optional — required for email invites/notifications) +# ============================================================================= +# SMTP_SERVER=smtp.example.com +# SMTP_PORT=587 +# SMTP_USER=apikey +# SMTP_PASS=your-smtp-password +``` + +### 4. Create `confs/microgateway.env` + +Create the Edge Gateway configuration file. Ensure the security tokens match the ones used in `studio.env`. + +```env Expandable +# ============================================================================= +# Server Configuration +# ============================================================================= +PORT=8080 +HOST=0.0.0.0 +READ_TIMEOUT=300s +WRITE_TIMEOUT=300s +SHUTDOWN_TIMEOUT=30s + +# ============================================================================= +# Database (SQLite — default for edge deployments) +# ============================================================================= +DATABASE_TYPE=sqlite +DATABASE_DSN=file:./data/edge.db?cache=shared&mode=rwc +DB_AUTO_MIGRATE=true + +# ============================================================================= +# Hub-Spoke: Edge Mode +# ============================================================================= +GATEWAY_MODE=edge +CONTROL_ENDPOINT=tyk-ai-studio:50051 +EDGE_ID=edge-1 +EDGE_NAMESPACE=default +EDGE_HEARTBEAT_INTERVAL=30s +EDGE_ALLOW_INSECURE=true +EDGE_TLS_ENABLED=false + +# ============================================================================= +# Security — MUST MATCH AI Studio values +# ============================================================================= +EDGE_AUTH_TOKEN=CHANGE-ME-must-match-studio-GRPC_AUTH_TOKEN +ENCRYPTION_KEY=CHANGE-ME-must-match-studio-MICROGATEWAY_ENCRYPTION_KEY + +# ============================================================================= +# Gateway +# ============================================================================= +GATEWAY_TIMEOUT=300s +GATEWAY_ENABLE_FILTERS=true +GATEWAY_ENABLE_ANALYTICS=true + +# ============================================================================= +# Analytics +# ============================================================================= +ANALYTICS_ENABLED=true +ANALYTICS_BUFFER_SIZE=1000 +ANALYTICS_FLUSH_INTERVAL=10s +ANALYTICS_RETENTION_DAYS=90 + +# ============================================================================= +# Analytics Pulse Plugin (sends data to control plane) +# ============================================================================= +PLUGINS_CONFIG_PATH=/opt/tyk-microgateway/analytics-pulse.yaml + +# ============================================================================= +# Cache +# ============================================================================= +CACHE_ENABLED=true +CACHE_TTL=1h + +# ============================================================================= +# Logging +# ============================================================================= +LOG_LEVEL=info + +# ============================================================================= +# OCI Plugin Support +# ============================================================================= +OCI_PLUGINS_CACHE_DIR=/var/lib/microgateway/plugins +OCI_PLUGINS_REQUIRE_SIGNATURE=false # cosign not available in distroless images + +# ============================================================================= +# Enterprise Edition — REQUIRED for EE images, must be set before first start +# ============================================================================= +TYK_AI_LICENSE=your-license-key +``` + +### 5. Create `confs/analytics-pulse.yaml` + +This configures the Edge Gateway to send analytics data back to the AI Studio control plane: + +```yaml Expandable +version: "1.0" + +data_collection_plugins: + - name: "analytics_pulse" + enabled: true + hook_types: ["analytics", "budget", "proxy_log"] + replace_database: false + priority: 100 + config: + interval_seconds: 10 + max_batch_size: 1000 + max_buffer_size: 10000 + compression_enabled: true + include_proxy_summaries: true + include_request_response_data: true + edge_retention_hours: 24 + excluded_vendors: ["mock", "test"] + timeout_seconds: 30 + max_retries: 3 + retry_interval_secs: 5 +``` + +### 6. Start Services + + +**Important:** Make sure all configuration files (`studio.env`, `microgateway.env`, `analytics-pulse.yaml`) exist before running `docker compose up`. If a file-mounted volume target does not exist, Docker will create it as a directory, causing errors. + + +Start the services using Docker Compose: + +```bash +docker compose up -d +``` + +### 7. Verify + +Check that all services are running correctly: + +```bash +# Check all services are running +docker compose ps + +# Check AI Studio is responding +curl -s http://localhost:8080/health + +# Check Edge Gateway is responding +curl -s http://localhost:9091/health + +# Check AI Studio logs for successful edge connection +docker compose logs tyk-ai-studio | grep -i "edge\|grpc" +``` + +### Accessing the Portal + +Once the services are running, you can access the different components: + +- **AI Studio UI**: `http://localhost:8080` +- **Embedded Gateway**: `http://localhost:9090` +- **Edge Gateway**: `http://localhost:9091` + +## First User Registration + +After starting the service, you need to create your first admin user: + +1. **Access the application**: Open your browser and navigate to `http://localhost:8080` +2. **Register with admin email**: Use the EXACT email address you set in the `ADMIN_EMAIL` environment variable in `studio.env`. +3. **Complete registration**: The first user who registers with the admin email will automatically become the administrator. + + +**Important**: The first user registration must use the same email address specified in the `ADMIN_EMAIL` environment variable. This user will have full administrative privileges. + + +## Shared Secrets Reference + +These values **must match** between the AI Studio and Edge Gateway configuration files: + +| AI Studio Variable | Edge Gateway Variable | Purpose | +|---|---|---| +| `GRPC_AUTH_TOKEN` | `EDGE_AUTH_TOKEN` | Authenticates the gRPC connection | +| `MICROGATEWAY_ENCRYPTION_KEY` | `ENCRYPTION_KEY` | Encrypts synced configuration data | +| `TYK_AI_LICENSE` | `TYK_AI_LICENSE` | Enterprise license | + +## Port Reference + +| Port | Component | Purpose | +|------|-----------|---------| +| 8080 | AI Studio | Admin UI + REST API | +| 9090 | AI Studio | Embedded AI Gateway | +| 50051 | AI Studio | gRPC control server (internal) | +| 9091 | Edge Gateway | Edge AI Gateway (mapped from internal 8080) | +| 5432 | PostgreSQL | Database | + +## Using an External Database + +To use an existing PostgreSQL instance instead of the bundled container, remove the `postgres` service and `pgdata` volume from `compose.yaml`, then update `studio.env`: + +```env +DATABASE_TYPE=postgres +DATABASE_URL=postgresql://user:password@your-db-host:5432/tyk_ai_studio?sslmode=require +``` + +## Upgrading + +To upgrade to a newer version: + +```bash +docker compose pull +docker compose up -d +``` + +## Troubleshooting + + + + + +Check the logs for specific services: + +```bash +docker compose logs +```` + + + + + +* Verify `CONTROL_ENDPOINT` in `microgateway.env` matches the AI Studio service name and gRPC port (e.g., `tyk-ai-studio:50051`) +* Verify `EDGE_AUTH_TOKEN` matches `GRPC_AUTH_TOKEN` +* Verify `ENCRYPTION_KEY` matches `MICROGATEWAY_ENCRYPTION_KEY` +* Check that `GATEWAY_MODE=control` is set in `studio.env` + + + + + +* Ensure the `postgres` container is healthy: `docker compose ps` +* Verify `DATABASE_URL` credentials match the `POSTGRES_USER` / `POSTGRES_PASSWORD` in `compose.yaml` +* For external databases, verify network connectivity and SSL mode + + + + + +The Plugin Marketplace requires `AI_STUDIO_OCI_CACHE_DIR` to be set. Without it, the marketplace service does not start and no plugins will appear. Add this to your `studio.env`: + +```env +AI_STUDIO_OCI_CACHE_DIR=./data/cache/plugins +``` + +Restart AI Studio after making this change. + +The marketplace is enabled by default (`MARKETPLACE_ENABLED=true`), but it will not function without the OCI cache directory configured. + + + + + +If ports **8080, 9090, or 9091** are already in use, change the **left-hand side** of the port mapping in `compose.yaml`: + +```yaml +ports: + - "8585:8080" # Map to 8585 instead of 8080 +``` + +Then update `SITE_URL` in `studio.env` accordingly. + + + + diff --git a/ai-management/ai-studio/secrets.mdx b/ai-management/ai-studio/secrets.mdx new file mode 100644 index 0000000000..90a54eacba --- /dev/null +++ b/ai-management/ai-studio/secrets.mdx @@ -0,0 +1,73 @@ +--- +title: "Manage Secrets in Tyk AI Studio" +description: "How to configure secret management in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Secret Management" +sidebarTitle: "Secrets Management" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio provides a secure mechanism for storing and managing sensitive information, such as API keys, passwords, authentication tokens, or other credentials required by various platform configurations. + +## Purpose + +The Secrets Management system aims to: + +* **Prevent Exposure:** Avoid hardcoding sensitive values directly in configuration files or UI fields. +* **Centralize Management:** Provide a single place to manage and update credentials. +* **Enhance Security:** Store sensitive data encrypted at rest. + +## Core Concepts + +* **Secret:** A named key-value pair where the 'key' is the **Variable Name** used for reference, and the 'value' is the actual sensitive data (e.g., an API key string). + +* **Encryption:** + * Secret values are **encrypted at rest** within Tyk AI Studio's storage. + * The encryption and decryption process relies on a key derived from the **`TYK_AI_SECRET_KEY` environment variable** set when running the Tyk AI Studio instance. + * **CRITICAL:** The `TYK_AI_SECRET_KEY` must be kept confidential and managed securely. Loss of this key will render existing secrets unusable. Changing the key will require re-entering all secrets. + +* **Reference Syntax:** Secrets are referenced within configuration fields (like API key fields in LLM or Tool setups) using a specific syntax: + ``` + $SECRET/VariableName + ``` + Replace `VariableName` with the exact name given to the secret when it was created (e.g., `$SECRET/OPENAI_API_KEY`, `$SECRET/JIRA_AUTH_TOKEN`). + +* **Runtime Resolution:** When a configuration uses a field containing a secret reference (e.g., `$SECRET/MY_KEY`): + 1. The configuration itself stores the `$SECRET/MY_KEY` string, *not* the actual secret value. + 2. Only when the system needs the actual value at runtime (e.g., the [Proxy](/ai-management/ai-studio/proxy) preparing a request for an LLM, or a [Tool](/ai-management/ai-studio/tools) calling its external API), Tyk AI Studio retrieves the encrypted secret value, decrypts it using the `TYK_AI_SECRET_KEY`, and injects the plain text value into the operation. + 3. The decrypted value is typically used immediately and not persisted further. + +## Creating & Managing Secrets (Admin) + +Administrators manage secrets via the Tyk AI Studio UI or API: + +1. Navigate to the Secrets management section. +2. Create a new secret by providing: + * **Variable Name:** A unique identifier (letters, numbers, underscores) used in the `$SECRET/VariableName` reference. + * **Secret Value:** The actual sensitive string (e.g., `sk-abc123xyz...`). +3. Save the secret. It is immediately encrypted and stored. + + Secrets UI + +Secrets can be updated or deleted as needed. Updating a secret value will automatically apply the new value wherever the `$SECRET/VariableName` reference is used, without needing to modify the configurations themselves. + +## Usage Examples + +Secrets are commonly used in: + +* **[LLM Configurations](/ai-management/ai-studio/llm-management):** Storing API keys for providers like OpenAI, Anthropic, Google Vertex AI, etc. + * *Example:* In the API Key field for an OpenAI configuration: `$SECRET/OPENAI_API_KEY` +* **[Tool Configurations](/ai-management/ai-studio/tools):** Storing API keys, authentication tokens (Bearer, Basic Auth), or other credentials needed to interact with the external API the tool represents. + * *Example:* In a field for a custom header for a JIRA tool: `Authorization: Basic $SECRET/JIRA_BASIC_AUTH_TOKEN` +* **[Data Source Configurations](/ai-management/ai-studio/datasources-rag):** Storing API keys or connection credentials for vector databases (e.g., Pinecone, Milvus) or embedding service providers. + * *Example:* In the API Key field for a Pinecone vector store: `$SECRET/PINECONE_API_KEY` + +## Security Considerations + +* **Protect `TYK_AI_SECRET_KEY`:** This is the master key for secrets. Treat it with the same level of security as database passwords or root credentials. Use environment variable management best practices. +* **Principle of Least Privilege:** Grant administrative access (which includes secrets management) only to trusted users. +* **Regular Rotation:** Consider policies for regularly rotating sensitive credentials by updating the Secret Value in Tyk AI Studio. diff --git a/ai-management/ai-studio/sso.mdx b/ai-management/ai-studio/sso.mdx new file mode 100644 index 0000000000..db04005730 --- /dev/null +++ b/ai-management/ai-studio/sso.mdx @@ -0,0 +1,77 @@ +--- +title: "SSO Integration" +description: "How to configure SSO in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Single Sign On" +sidebarTitle: "SSO Integration" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio supports Single Sign-On (SSO) integration, allowing users to authenticate using their existing credentials from external Identity Providers (IdPs). This simplifies login, enhances security, and centralizes user management. + +## Purpose + +The SSO integration aims to: + +* Allow users to log in to Tyk AI Studio using their familiar corporate or social identity credentials. +* Eliminate the need for separate Tyk AI Studio-specific passwords. +* Improve security by leveraging the organization's existing IdP infrastructure and policies (e.g., MFA). +* Streamline user provisioning and de-provisioning (depending on IdP capabilities and configuration). + + + + +## Technology: Tyk Identity Broker (TIB) + +Tyk AI Studio leverages the embedded **Tyk Identity Broker (TIB)** component to handle SSO integrations. TIB acts as a bridge between Tyk AI Studio (the Service Provider or SP) and various external Identity Providers (IdPs). + +## Supported Protocols & Providers + +TIB enables Tyk AI Studio to integrate with IdPs supporting standard protocols, including: + +* **OpenID Connect (OIDC):** Commonly used by providers like Google, Microsoft Entra ID (Azure AD), Okta, Auth0. +* **SAML 2.0:** Widely used in enterprise environments (e.g., Okta, Ping Identity, ADFS). +* **LDAP:** For integration with traditional directory services like Active Directory. +* **Social Logins:** Providers like GitHub, GitLab, etc. (often via OIDC). + +## Configuration (Admin) + +Administrators configure SSO providers within the Tyk AI Studio administration interface (likely via TIB's configuration settings exposed through Tyk AI Studio): + +1. **Select Protocol:** Choose the appropriate protocol (OIDC, SAML, etc.). +2. **Provider Details:** Enter the specific configuration details required by the chosen protocol and IdP. + * **OIDC Example:** Client ID, Client Secret, Issuer URL, Discovery Endpoint. + * **SAML Example:** IdP SSO URL, IdP Issuer/Entity ID, IdP Public Certificate, SP Entity ID (Tyk AI Studio's identifier). +3. **Profile Mapping:** Configure how attributes received from the IdP (e.g., email, name, group memberships) map to Tyk AI Studio user profiles. + * Identify which IdP attribute contains the unique user identifier (e.g., `email`, `sub`, `preferred_username`). + * Map IdP attributes to Tyk AI Studio user fields (e.g., `given_name` -> First Name, `family_name` -> Last Name). +4. **Group Mapping (Optional but Recommended):** Configure rules to automatically assign users to Tyk AI Studio [Groups](/ai-management/ai-studio/user-management) based on group information received from the IdP. + * *Example:* If the IdP sends a `groups` claim containing "Tyk AI Studio Admins", map this to automatically add the user to the "Administrators" group in Tyk AI Studio. +5. **Enable Provider:** Activate the configured IdP for user login. + + SSO Config UI + +## Login Flow + +When SSO is enabled: + +1. User navigates to the Tyk AI Studio login page. +2. User clicks a button like "Login with [Your IdP Name]" (e.g., "Login with Google", "Login with Okta"). +3. User is redirected to the external IdP's login page. +4. User authenticates with the IdP (using their corporate password, MFA, etc.). +5. Upon successful authentication, the IdP redirects the user back to Tyk AI Studio (via TIB) with an authentication assertion (e.g., OIDC ID token, SAML response). +6. TIB validates the assertion and extracts user profile information. +7. Tyk AI Studio finds an existing user matching the unique identifier or provisions a new user account based on the received profile information (Just-In-Time Provisioning). +8. Group memberships may be updated based on configured mapping rules. +9. The user is logged into Tyk AI Studio. + +## Benefits + +* **Improved User Experience:** One less password to remember. +* **Enhanced Security:** Leverages established IdP security policies. +* **Centralized Control:** User access can often be managed centrally via the IdP. +* **Simplified Onboarding/Offboarding:** User access to Tyk AI Studio can be tied to their status in the central IdP. diff --git a/ai-management/ai-studio/studio-admin.mdx b/ai-management/ai-studio/studio-admin.mdx new file mode 100644 index 0000000000..c8697f1a15 --- /dev/null +++ b/ai-management/ai-studio/studio-admin.mdx @@ -0,0 +1,40 @@ +--- +title: "AI Studio Administrator" +description: "Overview of the AI Studio Administrator persona in Tyk AI Studio" +keywords: "AI Studio, Admin, Governance, Cost Management, Security" +sidebarTitle: "Overview" +--- + +The **AI Studio Administrator** is the primary manager of the Tyk AI Studio installation. This persona is responsible for the operational integrity, security, and financial governance of the AI platform. They ensure that AI resources are available, secure, and used within budget. + +## Lifecycle + +The typical lifecycle of an AI Studio Administrator involves four key stages: + +1. **Configure**: Setting up the fundamental infrastructure, including connecting to [LLM Providers](/ai-management/ai-studio/llm-management) (e.g., OpenAI, Anthropic) and configuring [Vector Databases](/ai-management/ai-studio/datasources-rag) for RAG. +2. **Govern**: Establishing global guardrails, such as [Policies](/ai-management/ai-studio/filters) for PII masking, rate limiting, and role-based access control (RBAC). +3. **Onboard**: Creating [Teams](/ai-management/ai-studio/teams) and [Users](/ai-management/ai-studio/user-management), assigning roles, and allocating resource quotas. +4. **Monitor**: Continuously tracking organization-wide token usage, enforcing [Budgets](/ai-management/ai-studio/budgeting), and auditing security alerts via the [Dashboard](/ai-management/ai-studio/analytics). + +## Core Features + +### Provider Management +Centralized management of LLM credentials and configurations. Admins can add, update, or deprecate models from providers like OpenAI, Anthropic, or Mistral without requiring changes to consumer applications. +* **Key Capability**: Unified API key management (stored as [Secrets](/ai-management/ai-studio/secrets)) to prevent credential sprawl. + +### Policy & Security +Enforcement of global security and compliance rules. +* **PII Detection**: Automatically redact sensitive information (credit cards, emails) from prompts before they reach the LLM. +* **RBAC**: Granular control over which teams can access specific models or tools. +* **Rate Limiting**: Protect upstream provider quotas by limiting requests per user or app. + +### Cost Management +Comprehensive financial controls to prevent "bill shock." +* **Budgets**: Set monthly spending limits at the Organization, Team, or App level. +* **Alerts**: Receive notifications when spending approaches defined thresholds (e.g., 80% of budget). +* **Chargeback Reporting**: Detailed analytics to attribute costs to specific teams or projects. + +### Audit Logs +Full traceability of system access and configuration changes. +* **Access Logs**: Track who accessed which model and when. +* **Configuration History**: Audit trail of changes to policies, budgets, and provider settings. diff --git a/ai-management/ai-studio/teams.mdx b/ai-management/ai-studio/teams.mdx new file mode 100644 index 0000000000..22d2182a0e --- /dev/null +++ b/ai-management/ai-studio/teams.mdx @@ -0,0 +1,62 @@ +--- +title: "Manage Teams in Tyk AI Studio" +description: "Understand how to use Teams in Tyk AI Studio to organize users and manage role-based access control (RBAC) for LLM providers, data sources, and tools." +keywords: "AI Studio, AI Management, Teams" +sidebarTitle: "Teams" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | +Teams in Tyk AI Studio help you organize [Users](/ai-management/ai-studio/users) and easily manage their access to [LLM providers](/ai-management/ai-studio/llm-management), [data sources](/ai-management/ai-studio/datasources-rag), and [tools](/ai-management/ai-studio/tools). By linking Teams to specific [Catalogs](/ai-management/ai-studio/catalogs), you ensure users access only the AI resources relevant to their role. + +```mermaid +graph LR + User1[User: Alice] --> TeamA[Team: Developers] + User2[User: Bob] --> TeamA + User3[User: Charlie] --> TeamB[Team: Support] + + TeamA -->|Access| LLMCat[LLM Catalog: GPT-4] + TeamA -->|Access| ToolCat[Tool Catalog: Code Search] + + TeamB -->|Access| LLMCat2[LLM Catalog: Claude] + TeamB -->|Access| DataCat[Data Catalog: KB Articles] +``` + +### Use cases +- **Role-Based Access Control**: Group developers into an "Engineering" Team and grant them access to advanced LLM models and coding tools, while grouping support staff into a "Support" Team with access to customer knowledge bases. +- **Resource Isolation**: Ensure that sensitive data sources (like HR documents) are only accessible to the "HR" Team by linking the specific data [Catalog](/ai-management/ai-studio/catalogs) only to that Team. +- **Simplified Onboarding**: When a new employee joins, simply add them to the relevant Team to automatically grant them access to all the necessary AI tools and models for their department. + +### Community vs Enterprise Edition +In the **Community Edition**, the Teams feature is not available for custom configuration. Instead, there is a single, built-in **"Default" Team**. All users are automatically assigned to this Default Team, and it is permanently linked to the default catalogs. + +In the **Enterprise Edition**, you have full access to create, manage, and delete custom Teams, allowing for granular Role-Based Access Control (RBAC) across your organization. Note that even in the Enterprise Edition, the "Default" Team cannot be deleted. + +## What is a Team? + +A Team acts as the central access control mechanism in Tyk AI Studio. Instead of assigning permissions to individual [Users](/ai-management/ai-studio/users), administrators assign [Catalogs](/ai-management/ai-studio/catalogs) (LLM providers, Data sources, and Tools) to a Team. Users are then added as members of the Team. + +This architecture simplifies permission management, as a user's access rights are dynamically inherited from their Team memberships. A user can belong to multiple Teams, and a Team can have multiple Catalogs of different types. + +## Configuration +When configuring a Team, the following options are available: +- **Team Name**: A descriptive name for the Team (e.g., "Solutions Architects", "Marketing"). +- **Manage Team Members**: An interface to search and add existing users to the Team, or remove current members. +- **Add Catalogs**: Sections to link the Team to specific catalogs: + - **LLM providers catalogs**: Grants access to specific AI models. + - **Data sources catalogs**: Grants access to specific datasets or knowledge bases. + - **Tools catalogs**: Grants access to specific tools (e.g., web search, calculators). + +## How to Create a Team +To create a new Team in Tyk AI Studio: +1. Navigate to the **Teams** section in the AI Studio dashboard. +2. Click on the **Create team** button. +3. Enter a descriptive **Team name**. +4. In the **Manage team members** section, search for existing [Users](/ai-management/ai-studio/users) and add them to the Team. +5. In the **Add catalogs** section, select one or more [Catalogs](/ai-management/ai-studio/catalogs) (LLM providers, Data sources, or Tools) to make them available to this Team. +6. Click **Save** to create the Team and apply the access rules. + + Create Team Form \ No newline at end of file diff --git a/ai-management/ai-studio/telemetry.mdx b/ai-management/ai-studio/telemetry.mdx new file mode 100644 index 0000000000..50e78755d2 --- /dev/null +++ b/ai-management/ai-studio/telemetry.mdx @@ -0,0 +1,41 @@ +--- +title: "Telemetry for Tyk AI Studio" +description: "How to configure telemetry in Tyk AI Studio?" +keywords: "AI Studio, AI Management, Telemetry" +sidebarTitle: "Telemetry" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio collects anonymized usage statistics to help improve the product and understand how features are being used. + +## What Data is Collected + +The telemetry system collects aggregate statistics every hour such as: + +- **User counts** by type (admin, developer, chat users) +- **LLM model counts** and token usage +- **Application counts** and proxy usage +- **Chat counts** and interaction statistics +- **team counts** + +**No personal data, content, or sensitive information is collected.** + +## Privacy & Security + +All data is **anonymized** before transmission to ensure user privacy. The system does **not collect any personally identifiable information (PII)**, and **no chat content, prompts, or responses** are ever transmitted. + +Additionally, **API keys and credentials are never included** in telemetry data. To further protect privacy, instance identifiers are **hashed and rotated daily**. All telemetry data is securely transmitted over **HTTPS** to `https://telemetry.tyk.technology`. + +## Disabling Telemetry + +Telemetry is **enabled by default** but can be disabled by setting the following environment variable: + +```bash +TELEMETRY_ENABLED=false +``` + diff --git a/ai-management/ai-studio/tools.mdx b/ai-management/ai-studio/tools.mdx new file mode 100644 index 0000000000..9597747abf --- /dev/null +++ b/ai-management/ai-studio/tools.mdx @@ -0,0 +1,95 @@ +--- +title: "Manage Tools in Tyk AI Studio" +description: "Discover how to extend LLM capabilities in Tyk AI Studio by integrating external APIs and services using the Tool System and OpenAPI specifications." +keywords: "AI Studio, AI Management, Tools" +sidebarTitle: "Tools & Extensibility" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio's Tool System allows Large Language Models (LLMs) to interact with external APIs and services, dramatically extending their capabilities beyond simple text generation. This enables LLMs to perform actions, retrieve real-time data, and integrate with other systems. + +## Purpose + +Tools bridge the gap between conversational AI and external functionalities. By defining tools, you allow LLMs interacting via the [Chat Interface](/ai-management/ai-studio/chat-interface) or API to: + +* Access real-time information (e.g., weather, stock prices, database records). +* Interact with other software (e.g., search JIRA tickets, update CRM records, trigger webhooks). +* Perform complex calculations or data manipulations using specialized services. + +## Core Concepts + +* **Tool Definition:** A Tool in Tyk AI Studio is essentially a wrapper around an external API. Its structure and available operations are defined using an **OpenAPI Specification (OAS)** (v3.x, JSON or YAML). +* **Allowed Operations:** From the provided OAS, administrators select the specific `operationIds` that the LLM is permitted to invoke. This provides granular control over which parts of an API are exposed. +* **Authentication:** Tools often require authentication to access the target API. Tyk AI Studio handles this securely by integrating with [Secrets Management](/ai-management/ai-studio/secrets). You configure the authentication method (e.g., Bearer Token, Basic Auth) defined in the OAS and reference a stored Secret containing the actual credentials. +* **Privacy Levels:** Each Tool is assigned a privacy level. This level is compared against the privacy level of the [LLM Configuration](/ai-management/ai-studio/llm-management) being used. A Tool can only be used if its privacy level is less than or equal to the LLM's level, preventing sensitive tools from being used with potentially less secure or external LLMs. + + Privacy levels define how data is protected by controlling LLM access based on its sensitivity: + - **Public (0)** – Safe to share (e.g., blogs, press releases). + - **Internal (25)** – Company-only info (e.g., reports, policies). + - **Confidential (50)** – Sensitive business data (e.g., financials, strategies). + - **Restricted/PII (100)** – Personal data (e.g., names, emails, customer info). + + *Note: Privacy levels are stored as integer scores in the system. The values shown in parentheses are the typical score mappings.* +* **Tool Catalogues:** Tools are grouped into logical collections called Catalogues. This simplifies management and access control. +* **Filters:** Optional [Filters](/ai-management/ai-studio/filters) can be applied to tool interactions to pre-process requests sent to the tool or post-process responses received from it (e.g., for data sanitization). +* **Documentation:** Administrators can provide additional natural language documentation or instructions specifically for the LLM, guiding it on how and when to use the tool effectively. +* **Dependencies:** Tools can declare dependencies on other tools, although the exact usage pattern might vary. + +## Availability + +Tools are available on both **AI Studio** (embedded gateway) and **Edge Gateway** (edge gateways). Tool configurations, OpenAPI specs, auth credentials, and app access associations are synced to edge gateways via the hub-spoke configuration system. Tools support namespace filtering for enterprise multi-tenant deployments. + +Tools are accessible in three ways: + +1. **Chat Interface** — LLMs invoke tools automatically during conversations (the primary use case). +2. **REST API** — Each tool is also available as a direct REST API endpoint for developers to call programmatically, independent of LLM interactions. +3. **[MCP Interface](/ai-management/mcps/overview)** — An MCP-compliant shim wraps the OpenAPI-generated tools, providing an MCP-API compatible interface. This works with tools that require authentication and provides MCP compatibility without a separate MCP proxy server. +## How it Works + +When a user interacts with an LLM via the [Chat Interface](/ai-management/ai-studio/chat-interface): + +1. The LLM receives the user prompt and the definitions of available tools (based on team permissions and Chat Experience configuration). +2. If the LLM determines that using one or more tools is necessary to answer the prompt, it generates a request to invoke the specific tool operation(s) with the required parameters. +3. Tyk AI Studio intercepts this request. +4. It validates the request, checks permissions, and retrieves necessary secrets for authentication. +5. Tyk AI Studio applies any configured request Filters. +6. It calls the external API defined by the Tool. +7. It receives the response from the external API. +8. Tyk AI Studio applies any configured response Filters. +9. It sends the tool's response back to the LLM. +10. The LLM uses the tool's response to formulate its final answer to the user. + +## Creating & Managing Tools (Admin) + +Administrators define and manage Tools via the UI or API: + +1. **Define Tool:** Provide a name, description, and privacy level. +2. **Upload OpenAPI Spec:** Provide the OAS document (JSON/YAML). +3. **Select Operations:** Choose the specific `operationIds` the LLM can use. +4. **Configure Authentication:** Select the OAS security scheme and link to a stored [Secret](/ai-management/ai-studio/secrets) for credentials. +5. **Add Documentation:** Provide natural language instructions for the LLM. +6. **Assign Filters (Optional):** Add request/response filters. + + Tool Config + +## Organizing & Assigning Tools (Admin) + +* **Create Catalogues:** Group related tools into Tool Catalogues (e.g., "CRM Tools", "Search Tools"). +* **Assign to Teams:** Assign Tool Catalogues to specific [teams](/ai-management/ai-studio/user-management). This grants users in those groups *potential* access to the tools within the catalogue. + + Catalogue Config + +## Using Tools (User) + +Tools become available to end-users within the [Chat Interface](/ai-management/ai-studio/chat-interface) if: + +1. The specific Chat Experience configuration includes the relevant Tool Catalogue. +2. The user belongs to a Team that has been assigned that Tool Catalogue. +3. The Tool's privacy level is compatible with the LLM being used in the Chat Experience. + +The LLM will then automatically decide when to use these available tools based on the conversation. diff --git a/ai-management/ai-studio/user-management.mdx b/ai-management/ai-studio/user-management.mdx new file mode 100644 index 0000000000..d49dbdfe12 --- /dev/null +++ b/ai-management/ai-studio/user-management.mdx @@ -0,0 +1,89 @@ +--- +title: "User Management & RBAC" +description: "How to configure user management in Tyk AI Studio?" +keywords: "AI Studio, AI Management, User Management, RBAC" +sidebarTitle: "Overview" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Tyk AI Studio includes a comprehensive system for managing users, their authentication methods, and controlling their access to platform resources using Teams and Role-Based Access Control (RBAC). + +## Purpose + +The User Management & RBAC system provides administrators with the tools to: + +* Manage the lifecycle of user accounts. +* Define how users authenticate (UI sessions, API keys). +* Organize users into logical teams. +* Grant fine-grained access to Tyk AI Studio resources (LLMs, Tools, Data Sources, Chat Experiences) based on team membership. +* Assign platform-level permissions using roles. + +## Core Concepts + +* **User:** Represents an individual interacting with Tyk AI Studio. Users are typically identified by an email address or username and can be created manually by administrators, via invitation, self-registration (if enabled), or provisioned through [SSO Integration](/ai-management/ai-studio/sso). +* **Authentication:** The process of verifying a user's identity. + * **Session-based:** For users logging into the Tyk AI Studio UI (using username/password or SSO). + * **API Key:** For applications or scripts interacting with Tyk AI Studio APIs (like the [Proxy](/ai-management/ai-studio/proxy)). +* **API Key:** A unique, long-lived token generated by a user. Applications use this key (typically in an `Authorization: Bearer ` header) to authenticate requests on behalf of the user who generated it. +* **[Team](/ai-management/ai-studio/teams):** A collection of users. Teams are the primary mechanism for assigning access rights to resources. A user can belong to multiple teams. +* **Resource:** Any entity within Tyk AI Studio whose access needs to be controlled. This includes: + * [LLM Configurations](/ai-management/ai-studio/llm-management) + * Tool Catalogues (collections of [Tools](/ai-management/ai-studio/tools)) + * Data Source Catalogues (collections of [Data Sources](/ai-management/ai-studio/datasources-rag)) + * Chat Experiences (configurations for the [Chat Interface](/ai-management/ai-studio/chat-interface)) +* **Role:** Defines a set of broad, platform-level permissions. Common roles include: + * **Admin:** Full access to configure and manage the Tyk AI Studio platform. + * **Standard User:** Access to use assigned resources (e.g., chat, query LLMs) but limited or no administrative capabilities. +* **RBAC (Role-Based Access Control):** Tyk AI Studio's access control model. Access is granted primarily by assigning resource access to **Teams**, and then adding **Users** to those Teams. **Roles** provide overarching platform permissions. +* **User Entitlements:** The complete set of permissions a specific user has at any given time. This is calculated based on their assigned Role and the combined permissions granted through all the Teams they belong to. Systems like the Proxy check these entitlements before allowing an action. + +## User Lifecycle Management (Admin) + +Administrators manage users via the UI or API: + +* **Creation:** Create user accounts manually, send invitations, or manage users provisioned via SSO. +* **Team Assignment:** Add or remove users from various Teams. +* **Role Assignment:** Assign a primary Role (e.g., Admin, Standard) to each user. +* **Status Management:** Activate or deactivate user accounts. +* **API Key Management:** Admins may have visibility into user API keys (though users typically generate their own). + + User Management UI + +## Team Management (Admin) + +Teams are central to managing permissions: + +* **Creation/Deletion:** Create and manage teams (e.g., "Developers", "Sales Team", "Product Docs Users"). +* **User Assignment:** Add/remove users from teams. +* **Resource Assignment:** Grant access to specific LLM Configurations, Tool Catalogues, or Data Source Catalogues *to the team*. Any user in that team inherits this access. + + Group Management UI + +## Authentication Methods + +* **UI Login:** Users access the web interface by logging in with their credentials (username/password) or via a configured [SSO Provider](/ai-management/ai-studio/sso). This establishes a browser session. +* **API Key Authentication:** + 1. A user generates an API Key via their profile settings in the UI. + 2. The user securely provides this key to their application or script. + 3. The application includes the key in the `Authorization` header for requests to Tyk AI Studio APIs: + ``` + Authorization: Bearer + ``` + 4. Tyk AI Studio validates the key and associates the request with the user who generated it. + +## Access Control Flow Example (API Request) + +When an application makes a request to the [Proxy](/ai-management/ai-studio/proxy) using an API Key: + +1. **Key Validation:** Tyk AI Studio validates the API Key. +2. **User Identification:** The system identifies the User associated with the key. +3. **Team Membership:** The system determines all Teams the User belongs to. +4. **Resource Check:** The request targets a specific resource (e.g., an LLM Configuration via its `routeId`). +5. **Permission Verification:** Tyk AI Studio checks if *any* of the user's Teams have been granted access to the requested resource. +6. **Entitlement Check:** Additional checks based on the user's Role and specific entitlements might occur (e.g., budget checks, model restrictions). +7. **Access Granted/Denied:** If all checks pass, the request proceeds; otherwise, it's denied (e.g., 401 Unauthorized or 403 Forbidden). diff --git a/ai-management/ai-studio/users.mdx b/ai-management/ai-studio/users.mdx new file mode 100644 index 0000000000..74a3eee8c2 --- /dev/null +++ b/ai-management/ai-studio/users.mdx @@ -0,0 +1,76 @@ +--- +title: "Manage Users in Tyk AI Studio" +description: "Learn how to create and manage Users in Tyk AI Studio, including configuring roles, access permissions, and API keys for administrators, developers, and end-users." +keywords: "AI Studio, AI Management, Users" +sidebarTitle: "Users" +--- + +## Availability + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| [Community](/ai-management/ai-studio/overview#community-edition) & [Enterprise](/ai-management/ai-studio/overview#enterprise-edition) | Self-Managed, Hybrid | + +Users in Tyk AI Studio represent individuals who interact with the platform. They can be administrators managing the system or consumers accessing the AI portal and chat interfaces. + +```mermaid +graph TD + User[User] -->|Belongs to| Team[Team] + Team -->|Grants access to| LLMCatalog[LLM Catalog] + Team -->|Grants access to| DataCatalog[Data Catalog] + Team -->|Grants access to| ToolCatalog[Tool Catalog] + User -->|Has Role| Admin[Admin User] + User -->|Has Access| Portal[AI Portal] + User -->|Has Access| Chat[AI Chat] +``` + +### Use cases +- **Administrative Management**: Super admins can create user accounts for other administrators to help manage AI Studio configurations, [Teams](/ai-management/ai-studio/teams), and [Catalogs](/ai-management/ai-studio/catalogs). +- **Developer Access**: Developers can be granted access to the [AI Portal](/ai-management/ai-studio/ai-portal) to consume LLM APIs using their generated API keys. +- **End-User Chat**: Non-technical users can be given access to the [AI Chat](/ai-management/ai-studio/chat-interface) interface to interact with approved LLMs and data sources safely. + +### Community vs Enterprise Edition +In the **Community Edition**, basic user management is available, and all users are automatically assigned to a single, built-in "Default" [Team](/ai-management/ai-studio/teams). +In the **Enterprise Edition**, you can create multiple Teams, assign users to specific Teams for granular access control, and configure Single Sign-On (SSO) provisioning. + +## What is a User? + +A User in Tyk AI Studio is the fundamental identity for authentication and authorization. Each user has basic information (Name, Email, Password) and specific access flags. + +Users do not directly get assigned to AI resources (like LLMs or Data Sources). Instead, their access is governed by the [Teams](/ai-management/ai-studio/teams) they belong to. When a user is added to a Team, they inherit access to all the [Catalogs](/ai-management/ai-studio/catalogs) associated with that Team. + +### Default Roles and the Initial User +When setting up Tyk AI Studio, the very first user created is automatically granted the **Super Admin** role. This initial user is critical because they bypass standard authorization checks and are the only one who can configure SSO, set up the initial LLM providers, and create other administrative users. + +The available roles and access flags for users include: +- **Super Admin**: Full access to all system configurations, including SSO and initial setup. +- **Admin User**: Can manage AI Studio configurations, Teams, and Catalogs. +- **Developer**: Typically granted access to the AI Portal to generate API keys and integrate with LLMs. +- **End User**: Typically granted access only to the AI Chat interface. + +### API Key Generation +Every user in Tyk AI Studio can generate an **API Key**. + +**What is this API Key used for?** +This API key is specifically used for programmatic access to the Tyk [AI Studio management APIs](/ai-management/ai-studio/ai-studio-swagger) (the Admin API) and the AI Portal. + +## Configuration +When configuring a User, the following options are available: +- **Name**: The full name of the user. +- **Email**: The user's email address, used for login. +- **Password**: The user's password for authentication. +- **Admin User**: Grants the user administrative privileges to manage AI Studio. +- **Show Portal**: Grants the user access to the AI Portal interface. +- **Show Chat**: Grants the user access to the AI Chat interface. +- **Email Verified**: Indicates if the user's email has been verified. + +## How to Create a User +To create a new User in Tyk AI Studio: +1. Navigate to the **Users** section in the AI Studio dashboard. +2. Click on the **Add User** button. +3. Fill in the required basic information: **Name**, **Email**, and **Password**. +4. Configure the user's permissions by toggling the appropriate switches (e.g., **Admin User**, **Show Portal**, **Show Chat**). +5. Click **Add User** to create the user. +6. (Optional) After creation, you can view the user's details to generate or copy their API Key, or assign them to specific [Teams](/ai-management/ai-studio/teams). + + Create User Form \ No newline at end of file diff --git a/ai-management/mcp-gateway/core-concepts.mdx b/ai-management/mcp-gateway/core-concepts.mdx new file mode 100644 index 0000000000..e732aa2ab5 --- /dev/null +++ b/ai-management/mcp-gateway/core-concepts.mdx @@ -0,0 +1,400 @@ +--- +title: "MCP Gateway: Core Concepts" +description: "The technical foundation for working with Tyk's MCP Gateway: what MCP is, the gateway's role in the protocol flow, the session lifecycle, the proxy definition, middleware levels, and the policy model for per-consumer access control and rate limiting." +keywords: "MCP, Model Context Protocol, MCP Gateway, MCP proxy, JSON-RPC, SSE, Server-Sent Events, Streamable HTTP, Tyk OAS, middleware, policies, mcp_primitives, mcp_access_rights, json_rpc_methods, x-tyk-api-gateway, initialize, tools/call, resources/read, prompts/get, MCP primitives" +sidebarTitle: "Core Concepts" +--- + + +This page covers the technical foundation for working with Tyk's MCP Gateway: the gateway's role in the protocol flow, the MCP protocol concepts it builds on, the session lifecycle, the proxy definition, middleware levels, and the policy model for per-consumer access control and rate limiting. + +## The gateway role + +Without a gateway, AI agents connect directly to MCP servers over HTTP. Each server is responsible for its own authentication, access control, and rate limiting, or has none at all. There is no central point to see which agents are calling which tools, no consistent way to revoke access, and no protection against a slow or unavailable server. For the full picture of why this creates operational risk at scale, see [MCP Gateway overview](/ai-management/mcp-gateway/overview). + +Tyk sits between MCP clients and your upstream MCP servers. Unlike a generic reverse proxy that treats all traffic as an opaque HTTP stream, Tyk understands the MCP protocol. It parses the JSON-RPC request body on every `POST /mcp` to identify the method being called and the specific primitive being accessed. This is what makes primitive-level control possible. + +Because Tyk knows that a particular request is a `tools/call` for `get_current_weather` (not just a `POST` to `/mcp`), it can: + +- Rate limit that tool independently, without affecting other tools on the same server +- Block access to a specific resource URI without restricting the entire resources category +- Enforce a timeout on a slow tool without affecting fast tools +- Strip that tool from `tools/list` responses for consumers whose policy does not permit it +- Record the exact tool name in analytics so you can see precisely which primitives agents are calling + +The fundamental difference from a REST proxy is how operations are identified. REST APIs use URL paths and HTTP methods: Tyk routes `GET /weather` differently from `GET /weather/forecast`. MCP routes all traffic through a single endpoint and identifies the operation from the request body: + +```text +REST API (Tyk OAS): + GET /weather → weather current conditions + GET /weather/forecast → weather forecast + +MCP proxy (Tyk MCP): + POST /mcp { "method": "tools/call", "params": { "name": "get-weather" } } + POST /mcp { "method": "tools/call", "params": { "name": "get-forecast" } } +``` + +MCP proxies share the same authentication mechanisms, policy engine, and analytics infrastructure as your REST and GraphQL APIs. The body inspection is the only difference in how operations are identified and matched. + + +MCP definitions are supported in Tyk OAS format only. They are not available as Tyk Classic API definitions. + + +### Transport + +MCP uses **Streamable HTTP** as its transport. The MCP specification defines a single endpoint path (`/mcp`) that supports two HTTP methods with distinct roles. Tyk proxies both. + +**`POST /mcp`**: JSON-RPC messages. Clients send JSON-RPC 2.0 messages to `POST /mcp`. The upstream MCP server can respond with: + +- **`200 application/json`**: a single JSON-RPC response, for operations that complete immediately. +- **`200 text/event-stream`**: a Server-Sent Events stream carrying multiple JSON-RPC messages, used when the server streams results or sends progress notifications. +- **`202 Accepted`**: an acknowledgement for JSON-RPC notifications that do not expect a response body. + +Tyk executes the middleware chain against the incoming `POST` request (authenticating, applying rate limits, checking allowlists) and then proxies it to the upstream. Tyk preserves the response content type and streams SSE responses through to the client without buffering or transforming the body. + +**`GET /mcp`**: Server-Sent Events. Clients open a persistent `GET /mcp` connection to receive server-initiated messages. The upstream MCP server uses this channel to push progress notifications, resource update notifications, and server-to-client requests such as `sampling/createMessage`. Tyk proxies the SSE stream transparently, maintaining the long-lived connection for the duration of the session. + +### Protocol headers + +MCP defines several protocol-specific headers that Tyk passes through unchanged in both directions: + +| Header | Direction | Purpose | +|---|---|---| +| `MCP-Protocol-Version` | Client → Server | Required. Specifies the MCP protocol revision (for example, `2025-11-25`). | +| `Mcp-Session-Id` | Server → Client, then Client → Server | Session identifier. Returned by the server after initialization; clients echo it on subsequent requests. | +| `Last-Event-ID` | Client → Server | SSE resume token sent when reconnecting to a `GET /mcp` stream. | +| `Origin` | Client → Server | Used by servers for origin-based security validation. | + +The client and upstream MCP server handle version negotiation and session management. Tyk does not modify any MCP protocol headers. + +--- + +## What is MCP? + + +New to MCP? The [MCP introduction](https://modelcontextprotocol.io/introduction) is a good starting point before reading further. + + +The Model Context Protocol (MCP) is an open standard that defines how AI applications connect to external tools, data sources, and services. It uses a client-server model: an **MCP client** (an AI agent, LLM orchestration framework, or application) connects to an **MCP server** that exposes capabilities, and the two communicate using JSON-RPC 2.0 messages carried over HTTP. + +**Tyk supports the `2025-11-25` revision of the MCP specification.** + +### Primitives + +MCP servers expose three types of capability, collectively called **primitives**. + +| Primitive | Description | Discovery method | Invocation method | +|---|---|---|---| +| **Tool** | A callable function that takes structured arguments and returns a result. Used for actions and computed queries. | `tools/list` | `tools/call` | +| **Resource** | A readable data source identified by a URI. Used for documents, files, and live data feeds. | `resources/list` | `resources/read` | +| **Prompt** | A reusable prompt template the server exposes for common tasks. | `prompts/list` | `prompts/get` | + +Each primitive has a name (or URI for resources) that uniquely identifies it within the server: + +- A `tools/call` request names the tool in `params.name` +- A `resources/read` request names the resource URI in `params.uri` +- A `prompts/get` request names the prompt in `params.name` + +This name is what Tyk uses to apply primitive-level middleware and policy controls. + +### Client-side primitives + +MCP also defines primitives that run in the opposite direction: capabilities that servers can request from clients rather than expose themselves. + +| Primitive | Description | Method | +|---|---|---| +| **Sampling** | Server requests the client to perform LLM inference on its behalf and return the result. | `sampling/createMessage` | +| **Roots** | Server requests the list of filesystem roots (directories or URIs) the client is willing to share. | `roots/list` | +| **Elicitation** | Server requests structured input from the user, mediated through the client. | `elicitation/create` | + + +Tyk passes client-side primitive messages through to the upstream and back. Primitive-level middleware configuration (rate limits, access control, timeouts) applies to server-side primitives only. + + +### The request format + +Every MCP operation is a JSON-RPC 2.0 message sent to `POST /mcp`. Each message carries a `method` field that identifies the operation and, for invocation requests, a `params` object that names the specific primitive being accessed. + +A typical tool call looks like this: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "get_current_weather", + "arguments": { "location": "London" } + } +} +``` + +The `method` field identifies the category of operation: `tools/call` in this example. The `params.name` field identifies the specific tool. Tyk reads both fields on every incoming `POST /mcp` request to determine which middleware to execute before forwarding to the upstream. + +### JSON-RPC methods + +The MCP specification defines a fixed set of JSON-RPC method names, grouped into four categories. + +| Method | Category | Description | +|---|---|---| +| `initialize` | Session | Opens the session and negotiates capabilities. | +| `notifications/initialized` | Session | Client confirms session is ready. | +| `tools/list` | Tools | Returns the list of tools the server exposes. | +| `tools/call` | Tools | Invokes a named tool with the supplied arguments. | +| `resources/list` | Resources | Returns the list of resources the server exposes. | +| `resources/read` | Resources | Reads the content of a named resource URI. | +| `prompts/list` | Prompts | Returns the list of prompt templates the server exposes. | +| `prompts/get` | Prompts | Retrieves a named prompt template, optionally with arguments. | +| `sampling/createMessage` | Sampling | Requests the client to perform LLM sampling on behalf of the server. | +| `notifications/tools/list_changed` | Notifications | Server-initiated notification that the tool list has changed. | + +Tyk can apply rate limits, access control, and middleware at the method level (for example, capping all `tools/call` requests) and at the primitive level (for example, rate limiting a specific named tool). The distinction matters: method-level controls apply to every invocation of that method regardless of which primitive is named; primitive-level controls apply only when that specific tool, resource, or prompt is requested. + +--- + +## Session lifecycle + +MCP is a stateful protocol. When a client sends an `initialize` request, the upstream server responds with an `Mcp-Session-Id` header. The client includes this identifier on all subsequent requests, allowing the server to associate them with the established session context. Tyk passes the header through unmodified and does not maintain session state itself. + +A typical session proceeds through four phases. + +1. **Handshake**: the client and server negotiate capabilities and establish a session identifier. +2. **Discovery**: the client queries the server's available tools, resources, and prompts. +3. **Invocation**: the client calls primitives; Tyk applies rate limiting, access control, and observability on each request. +4. **Session close**: the client terminates the session; the session identifier is invalidated. + +### Handshake + +Tyk authenticates the `initialize` request before proxying it to the upstream. The upstream responds with an `Mcp-Session-Id` header that Tyk passes through to the client unchanged. The client confirms readiness with a `notifications/initialized` message, and the session is established. + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant U as Upstream MCP Server + + rect rgb(240, 236, 255) + Note over C,U: Handshake + C->>T: POST /mcp — initialize + Note over T: Authenticate request + T->>U: initialize (proxied unchanged) + U-->>T: 200 OK · Mcp-Session-Id: abc123 + T-->>C: 200 OK · Mcp-Session-Id: abc123 (passed through) + C->>T: POST /mcp — notifications/initialized · Mcp-Session-Id: abc123 + T->>U: notifications/initialized (proxied) + U-->>T: 202 Accepted + T-->>C: 202 Accepted + end +``` + +### Discovery + +Once the session is open, the client calls `tools/list` (and optionally `resources/list` and `prompts/list`) to learn what the server exposes. Tyk intercepts the upstream's response and strips any primitives the consumer is not permitted to see, based on the policy attached to their key. The client receives a filtered view of the server's capabilities. + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant U as Upstream MCP Server + + rect rgb(235, 248, 255) + Note over C,U: Discovery + C->>T: POST /mcp — tools/list · Mcp-Session-Id: abc123 + T->>U: tools/list (proxied) + U-->>T: Full tool list + Note over T: Filters to permitted tools only + T-->>C: Filtered tool list (scoped to consumer entitlements) + end +``` + +### Invocation + +Each primitive call passes through Tyk's full middleware chain. Rate limiting, access control, and observability all fire on every `tools/call` request. If scope enforcement is configured, Tyk validates the inbound token's scopes against the primitive's requirements before proxying. If token exchange is configured, Tyk exchanges the inbound token for a backend-scoped token — the inbound SSO token never reaches the upstream MCP server. If the consumer's policy permits the named tool and rate limits allow the request, Tyk proxies it to the upstream and returns the response. This phase repeats for every tool, resource, or prompt the client invokes. + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant U as Upstream MCP Server + + rect rgb(235, 255, 244) + Note over C,U: Invocation (repeats per primitive call) + C->>T: POST /mcp — tools/call · Mcp-Session-Id: abc123 + Note over T: Rate limiting · access control · observability + T->>U: tools/call (proxied) + U-->>T: Tool response + T-->>C: Tool response + end +``` + +### Session close + +When the client has finished, it sends `DELETE /mcp` with the `Mcp-Session-Id` header. Tyk proxies the request to the upstream, which terminates the session. After session close, the `Mcp-Session-Id` is invalid; a new `initialize` request is required to open a new session. + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant U as Upstream MCP Server + + rect rgb(255, 240, 240) + Note over C,U: Session close + C->>T: DELETE /mcp · Mcp-Session-Id: abc123 + T->>U: DELETE /mcp (proxied) + U-->>T: 200 OK + T-->>C: 200 OK + Note over C,U: Session closed + end +``` + +If the `GET /mcp` SSE connection drops, the client can reconnect by opening a new `GET /mcp` request with the `Last-Event-ID` header set to the ID of the last event received. Tyk passes this through to the upstream, which resumes the stream from that point. + +--- + +## The proxy definition + +The **MCP proxy definition** is the configuration object that describes the proxy. It is a Tyk OAS API definition, an OpenAPI 3.0 document extended with `x-tyk-api-gateway`, and it is both the single source of truth for everything about the proxy and its registry entry: the listen path, the upstream URL, authentication, middleware, and versioning. + +The `x-tyk-api-gateway` extension has four top-level sections: + +| Section | What it configures | +|---|---| +| `info` | API name, active state, and internal identifier | +| `server` | Client-facing settings: listen path, authentication, IP access control, custom domain | +| `upstream` | Upstream MCP server: target URL, load balancing, upstream rate limits, mTLS | +| `middleware` | Request processing: global, per-method, and per-primitive middleware | + +A minimal MCP proxy definition looks like this: + +```json +{ + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "paths": { + "/mcp": { + "post": { "operationId": "mcpTransportPost", "responses": { "200": { "description": "JSON-RPC response" } } }, + "get": { "operationId": "mcpSSEGet", "responses": { "200": { "description": "SSE stream" } } } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "Weather MCP proxy", + "state": { "active": true } + }, + "server": { + "listenPath": { "value": "/weather/", "strip": true }, + "authentication": { "enabled": true } + }, + "upstream": { + "url": "https://weather-mcp.example.com" + } + } +} +``` + +Clients connect to Tyk at `https://my-gateway.example.com/weather/mcp`. Tyk authenticates the request, applies any configured middleware, and proxies it to `https://weather-mcp.example.com/mcp`. Tyk serves both the `POST` and `GET` transport endpoints under the same listen path. + +You manage MCP proxy definitions through the [Tyk Gateway API](/ai-management/mcp-gateway/mcp-api-extensions) (at `/tyk/mcps`) or the [Tyk Dashboard](/ai-management/mcp-gateway/managing-proxies). For the full definition structure and field reference, see [MCP OAS definitions](/ai-management/mcp-gateway/mcp-proxy-definitions). + +### Tool naming and discovery + +Every tool, resource, and prompt on an MCP server has a **name** (or URI for resources) that uniquely identifies it within that server. Understanding how names are assigned, and how Tyk uses them, is a prerequisite for configuring primitive-level middleware, blocking specific tools, or writing policies with allowlists and blocklists. + +**In MCP Proxy pass-through mode** (Tyk proxying an existing MCP server), tool names come from the upstream server verbatim. Tyk does not modify or namespace them. If the upstream server exposes a tool named `get-weather`, that is the name Tyk uses for middleware matching, policy rules, and analytics. + +**In HTTP-to-MCP translation mode** (Tyk converting a REST API into MCP tools via [AI Studio](/ai-management/ai-studio/overview)), tool names are derived from the `operationId` field of each OpenAPI operation. An operation with `operationId: getWeatherForecast` becomes a tool named `getWeatherForecast`. If your OpenAPI spec does not set `operationId` on every operation, Tyk cannot create a stable tool name. Adding `operationId` values to every exposed operation is a prerequisite for this mode. + +**Discovery filtering** applies at the `tools/list`, `resources/list`, and `prompts/list` level. When a client requests the list of available primitives, Tyk intercepts the upstream's response and removes any entries that the consumer's policy does not permit. The client only sees and can only call the primitives its key is authorized to access. + +**Name collisions**: When proxying multiple MCP servers behind a single gateway, or when combining proxy pass-through and HTTP-to-MCP translation, tool names from different upstream servers may collide. Each MCP proxy definition is a separate Tyk API with its own listen path, so collisions are isolated per proxy: a tool named `search` on one proxy does not conflict with `search` on another. However, if you are using a single proxy to aggregate multiple upstream MCP servers (via the `mcp-registry` plugin), ensure that upstream tool names do not overlap, or use the plugin's namespacing configuration to prefix tool names per upstream. + +--- + +## Middleware + +Tyk applies middleware to MCP traffic at three levels, all configured in the `x-tyk-api-gateway.middleware` section of the proxy definition. The levels are evaluated in order from broadest to most specific. + +### Global middleware + +Global middleware applies to every request that reaches the proxy, before any method-level or primitive-level processing. Use it for server-wide concerns: CORS configuration, traffic logging, header injection into upstream requests, or custom plugins that should run on all traffic. + +### Operation middleware + +Operation middleware applies to all requests for a specific JSON-RPC method, regardless of which primitive is called. Configure it in `middleware.operations`, keyed by the JSON-RPC method name with its HTTP method suffix (for example, `tools/callPOST`). Use it for method-wide policies, such as a rate limit that applies to all `tools/call` requests without distinguishing between individual tools. + +```json +{ + "middleware": { + "operations": { + "tools/callPOST": { + "rateLimit": { "enabled": true, "rate": 500, "per": 60 } + } + } + } +} +``` + +This rate limit applies to every `tools/call` request, regardless of which tool is named in `params`. + +### Primitive middleware + +Primitive middleware applies to a specific tool, resource, or prompt. Configure it in one of three maps (`middleware.mcpTools`, `middleware.mcpResources`, or `middleware.mcpPrompts`), keyed by the primitive's identifier: the tool name, resource URI (or URI pattern), or prompt name. In addition to the standard middleware capabilities, two OAuth 2.0 features operate at this level: `scopeCheck` validates the inbound token's scopes against the primitive's `security:` requirements, and `exchange` replaces the `Authorization` header with a backend-scoped token before the request reaches the upstream (see [Token exchange](/api-management/authentication/token-exchange)). + +```json +{ + "middleware": { + "mcpTools": { + "execute-query": { + "allow": { "enabled": true }, + "rateLimit": { "enabled": true, "rate": 10, "per": 60 } + } + } + } +} +``` + +This configuration allowlists the `execute-query` tool and applies a rate limit of 10 requests per minute, independently of any other tools on the same proxy. + +When both operation-level and primitive-level middleware are configured, both apply. A `tools/call` request to `execute-query` must pass the operation-level limit (500/min for all tool calls) and the primitive-level limit (10/min for this tool). Access control follows the same pattern: allowlisting specific tools in `mcpTools` puts the entire tools category into allowlist mode, blocking any tool not explicitly listed. + +Tyk evaluates the three primitive categories (tools, resources, and prompts) independently. Allowlisting a tool has no effect on whether resources or prompts are accessible. + +See [MCP middleware](/ai-management/mcp-gateway/mcp-middleware) for the full list of available capabilities: access control, request and response transformation, traffic management (rate limiting, timeouts, circuit breakers), virtual endpoints, and observability controls. + +--- + +## Policies + +A **Tyk security policy** is a reusable template of access rights and usage limits that you apply to one or more API keys. You define a policy once and issue keys that inherit its rules automatically, rather than configuring each key individually. When you update the policy, every key bound to it picks up the change. + +Policies give you per-consumer control at every level of the MCP protocol: + +- **Proxy access**: control which MCP proxies a consumer key can reach +- **JSON-RPC method access**: restrict which protocol operations a consumer can use (for example, allow `tools/call` but block `sampling/createMessage`) +- **Primitive access**: define per-consumer allowlists and blocklists for individual tools, resources, and prompts, using regular expressions to match by name +- **Per-primitive rate limits**: set independent rate limits on specific primitives, so a consumer exhausting their quota on one tool does not affect their access to others +- **Quotas**: cap total call volume over a renewal period + +Primitive access control is enforced at two points in the MCP protocol. + +**At invocation time**: Tyk checks `tools/call`, `resources/read`, and `prompts/get` requests against the consumer's permitted primitives. Blocked calls return a JSON-RPC error before the request reaches the upstream. + +**At discovery time**: Tyk intercepts `tools/list`, `resources/list`, and `prompts/list` responses from the upstream and strips out any primitives the consumer cannot see. Each consumer receives a filtered view of the server's capabilities from the moment they connect. + +### Policies versus middleware + +Both middleware and policies can enforce limits on MCP primitives, but they operate on different subjects. + +**Middleware** applies to all traffic through the proxy: a primitive rate limit in `mcpTools` caps the call rate for a tool across every caller combined. It protects the upstream from overload. + +**Policies** apply per consumer. A primitive rate limit in a policy caps the call rate for one specific key, with each consumer's counters tracked independently. This is how you enforce different entitlements for different consumers: a standard tier with read-only access and lower limits, a premium tier with access to sensitive tools and higher quotas. + +The two work together: middleware sets the ceiling for all traffic, policies determine what each consumer is entitled to within that ceiling. + +Tyk also supports scope-based access control via the `oauth2` scheme's `scopeCheck` feature. This is complementary to policy-based access control: policies control which primitives a consumer key can reach; scope check validates that the inbound token carries the required OAuth scopes for each primitive. The two can be combined — a consumer must both hold a key permitted by policy and present a token with the required scopes. See [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication) for configuration details. + +See [MCP proxy policies](/ai-management/mcp-gateway/policies) for the full configuration reference, Dashboard UI walkthrough, and API examples. diff --git a/ai-management/mcp-gateway/faq.mdx b/ai-management/mcp-gateway/faq.mdx new file mode 100644 index 0000000000..ff74dedf34 --- /dev/null +++ b/ai-management/mcp-gateway/faq.mdx @@ -0,0 +1,373 @@ +--- +title: "MCP Gateway: FAQ" +description: "Answers to frequently asked questions about Tyk's MCP Gateway: how it works, what it protects against, how authentication and sessions behave, what you can observe, and how to govern AI agent traffic at scale." +keywords: "MCP, Model Context Protocol, MCP Gateway FAQ, MCP security, MCP authentication, MCP sessions, MCP rate limiting, AI agent governance, prompt injection, MCP tools, MCP SSE" +sidebarTitle: "FAQ" +--- + +This FAQ answers common questions about Tyk's MCP Gateway: how it works, how it secures AI agent traffic, and how to govern MCP access at scale. Questions are grouped by topic so you can jump directly to the area you need. + +--- + +## The basics + +### Do I need to modify my MCP server to use Tyk? + +No. Tyk sits in front of your existing MCP server as a reverse proxy. Your upstream server does not need to know about Tyk; it receives standard MCP requests over HTTP and responds normally. All authentication, access control, rate limiting, and observability are applied at the gateway layer. + +### Does Tyk work with both local and remote MCP servers? + +Tyk is designed for **remote MCP servers**: servers reachable over HTTP or HTTPS from the gateway. If your MCP server currently runs locally over stdio (the standard desktop configuration), you will need to either deploy it as an HTTP service or use one of the available stdio-to-HTTP bridge tools to expose it as a remote endpoint before Tyk can proxy it. + +### Which transport methods does Tyk support? + +Tyk supports the Streamable HTTP transport defined in the MCP specification (version 2025-11-25): + +- **`POST /mcp`**: for JSON-RPC messages from client to server. Tyk applies the middleware chain and proxies the request, streaming SSE responses through if the upstream returns them. +- **`GET /mcp`**: for long-lived Server-Sent Events connections carrying server-initiated messages. Tyk maintains the connection and passes events through transparently. + +The older HTTP+SSE transport (two separate endpoints: a POST endpoint and a `/sse` endpoint) used by earlier MCP versions is not supported. Check that your upstream server implements Streamable HTTP. + +### What MCP protocol versions does Tyk support? + +Tyk passes the `MCP-Protocol-Version` header through to the upstream unchanged; version negotiation is handled between the client and the upstream MCP server. The current MCP specification version is `2025-11-25`. See [MCP OAS definition](/ai-management/mcp-gateway/mcp-proxy-definitions) for details on configuring the proxy definition. + +### Can Tyk proxy multiple MCP servers? + +Yes. Each remote MCP server gets its own **MCP proxy definition** in Tyk, with its own listen path, upstream URL, authentication configuration, and middleware. An AI agent that needs to reach GitHub's MCP server and an internal database tools server connects to two separate Tyk endpoints, each governed independently. There is no limit on the number of MCP proxies you can create. + +--- + +## The Dashboard + +### What can I configure for an MCP proxy in the Dashboard? + +The MCP Designer has two tabs. + +The **Settings** tab covers core proxy configuration (name, upstream server URL, gateway assignment) and proxy-level middleware that applies to all traffic through the proxy: + +| Middleware | What it does | +|---|---| +| CORS | Cross-origin resource sharing headers for browser-based clients | +| Transform Request Headers | Add, remove, or modify headers on every upstream request | +| Transform Response Headers | Add, remove, or modify headers on every response | +| Context Variables | Make Tyk request metadata available in transforms and plugins | +| Traffic Logs | Configure analytics capture behavior | +| Plugin Config / Bundle | Configure custom plugin drivers and bundle sources | + +The **Primitives** tab lists the tools, resources, and prompts defined for this proxy and lets you manage per-primitive middleware (access control, rate limits, timeouts, circuit breakers, and more) without editing the definition directly. + +### What requires editing the proxy definition directly? + +The Primitives tab covers most per-primitive middleware. A few options require the definition editor (**Actions → View MCP Proxy Definition**) or the Dashboard API: + +- **Transform request body**: advanced request body transformation via Go template +- **`middleware.operations`**: method-level middleware applying to all calls of a specific JSON-RPC method (for example, all `tools/call` requests) +- **Policy ACL fields** (`mcp_access_rights`, `json_rpc_methods_access_rights`): per-consumer primitive and method access control configured via the Dashboard API + + +`urlRewrite`, `transformRequestMethod`, and `mockResponse` are accepted by the configuration schema but have no effect on MCP primitives. See [MCP middleware](/ai-management/mcp-gateway/mcp-middleware) for the full capability reference. + + +### How do I add middleware to a specific tool, resource, or prompt? + +Open the proxy and click the **Primitives** tab. Click **Add Primitive**, select the type (Tool, Resource, or Prompt), and enter the name exactly as the upstream MCP server advertises it. + +Click the primitive in the list to open its detail view, then click **Add Middleware**. Available middleware includes Allow, Block, Rate Limit, Circuit Breaker, Request Size Limit, Ignore Authentication, Transform Request/Response Headers, Virtual Endpoint, Track Endpoint, Do Not Track Endpoint, and Post Plugins. + +Each middleware option maps directly to the corresponding field in `x-tyk-api-gateway.middleware.mcpTools`, `mcpResources`, or `mcpPrompts` in the proxy definition. + +### How do I set a rate limit that applies to all consumers of a proxy? + +Use the Primitives tab. Add the tool as a primitive, click it, and add **Rate Limit** middleware. This rate limit applies in aggregate, across all keys calling that tool on this proxy combined. It protects the upstream from overload regardless of which consumer is calling. + +For per-consumer rate limits that apply independently per API key, use the `mcp_primitives` field in a security policy. See [MCP proxy policies](/ai-management/mcp-gateway/policies). + +--- + +## Security + +### Research shows that most public MCP servers have no authentication. How does Tyk address this? + +This is one of the most documented problems with the current MCP ecosystem. Studies in 2025 found that a significant proportion of publicly accessible MCP servers had no authentication at all, exposing their full tool catalogues to anyone who could reach the endpoint. + +Tyk adds authentication in front of your upstream server without requiring any changes to it. All authentication methods Tyk supports for REST APIs (Bearer tokens, API keys, JWT, OAuth 2.0, and mutual TLS) apply identically to MCP. Your upstream server never needs to validate credentials; Tyk does it before the request gets there. + + +### How do I stop an AI agent from calling destructive tools? + +Use the **Primitives** tab on the MCP Designer. Add each tool you want to permit as a primitive (type: Tool), then add **Allow** middleware to it. Once any tool has an Allow rule, Tyk switches the entire tools category into allowlist mode; any tool not explicitly listed is blocked with a JSON-RPC error before the request reaches the upstream. + +Alternatively, configure the allowlist directly in the proxy definition using `"allow": { "enabled": true }` entries in `x-tyk-api-gateway.middleware.mcpTools`. + +This means new tools added to the upstream MCP server are **not automatically accessible** through the gateway. You must explicitly add them to the allowlist. This is the recommended default posture: deny by default, permit explicitly. + +See [MCP middleware: access control](/ai-management/mcp-gateway/mcp-middleware#access-control) and [How to block an MCP tool](/ai-management/mcp-gateway/how-to-block-tool). + +### Over-permissioning is a known MCP problem. How does Tyk help? + +Over-permissioning (agents having access to more tools and data than they need) compounds in the absence of a central control point. When every agent connects directly to every MCP server, there is no practical way to enforce least-privilege access at scale. + +Tyk makes over-permissioning addressable through three mechanisms: + +1. **Per-proxy tool allowlists**: configure which tools are accessible through each proxy, regardless of what the upstream server exposes. Applies to all consumers of that proxy. +2. **Security policies: proxy access**: issue API keys against policies that grant access only to the specific MCP proxies a given agent needs. An agent that needs GitHub MCP does not automatically have access to your internal database MCP server. +3. **Security policies: primitive ACLs**: use `mcp_access_rights` in the policy access rights entry to restrict which specific tools, resources, and prompts an individual consumer can invoke, without affecting other consumers of the same proxy. This lets you grant two agents access to the same MCP proxy while giving each a different subset of its tools. + +### What happens if an agent's API key is compromised? + +Revoke the key from the Tyk Dashboard under **Keys**. Access to every MCP proxy that key was permitted to reach is cut off immediately on the next request, with no grace period and no upstream credential rotation required. The upstream MCP server is unaffected; only requests carrying the revoked Tyk key are blocked. + +If the key was scoped to specific MCP proxies via a policy, other agents on the same policy are not affected. + +--- + +## Authentication and OAuth + +### What authentication methods can AI agents use? + +Any authentication method Tyk supports for REST APIs also applies to MCP: Bearer tokens (API keys), JWT, OAuth 2.0 (with external token introspection), and mutual TLS. The inbound authentication method is configured per proxy in the `server.authentication` section of the API definition. + +For most deployments, Bearer token authentication using Tyk-issued API keys is the simplest starting point. For organizations with an existing OAuth 2.1 identity provider, JWT authentication allows agents to present tokens issued by your IdP directly. + +### Does Tyk support the MCP OAuth 2.1 specification? + +Yes. The MCP specification (version 2025-11-25) bases its authorization model on OAuth 2.1 and recommends that MCP servers expose **Protected Resource Metadata** (RFC 9728) so clients can discover authorization servers automatically. + +Tyk implements this end-to-end: + +- The `/.well-known/oauth-protected-resource` endpoint is served natively by Tyk when PRM is enabled, with no upstream changes required. +- On authentication failure, Tyk includes a `WWW-Authenticate: Bearer resource_metadata=...` header pointing to the PRM document. +- OAuth 2.1-aware MCP clients follow this header, discover the authorization server, obtain a token, and retry, without any pre-configuration. +- On the upstream side, Tyk can acquire and inject OAuth tokens using the client credentials flow (Enterprise Edition), so upstream MCP servers that require OAuth tokens receive them automatically. + +See [MCP Gateway: OAuth 2.1 authentication](/ai-management/mcp-gateway/oauth-2-1) for the complete guide. + +### What is Protected Resource Metadata and do I need to configure it? + +PRM is the machine-readable discovery document at `/.well-known/oauth-protected-resource`. It tells OAuth clients which authorization server to use and which scopes the API supports. The MCP specification recommends all MCP servers expose it. + +You do not strictly need it if you are issuing Tyk API keys manually and configuring agents with the key directly. PRM becomes valuable when: + +- You have many AI agents that should self-configure their auth without manual setup. +- You are using an OAuth 2.1 identity provider and want agents to obtain tokens automatically. +- You need to comply with the MCP specification's authorization recommendations. + +Configure it by adding a `protectedResourceMetadata` block to the proxy's `server.authentication` section. See [MCP Gateway: OAuth 2.1 authentication (configuring PRM)](/ai-management/mcp-gateway/oauth-2-1#configuring-prm). + +### Our upstream MCP servers each require different credentials. How do we manage that? + +Configure the upstream authentication independently per proxy. Tyk supports three patterns: + +- **Static token injection**: inject a fixed Bearer token (such as a GitHub PAT) into every proxied request via global `transformRequestHeaders` middleware. +- **OAuth client credentials**: have Tyk obtain and refresh an OAuth token from the upstream's authorization server using configured client credentials (Enterprise Edition). +- **Basic authentication**: inject HTTP basic auth credentials via `upstream.authentication.basicAuth`. + +In every case, the agent presents a Tyk credential and Tyk presents the correct vendor credential to the upstream. Agents never need to know about or hold vendor-specific credentials. + +--- + +## Sessions and connections + +### MCP is described as stateful. How does Tyk handle sessions? + +The MCP protocol establishes sessions identified by an `Mcp-Session-Id` header. After initialisation, clients include this identifier on every subsequent request so the server can associate them with established session state. + +Tyk passes the `Mcp-Session-Id` header through unchanged in both directions. It does not maintain session state itself; session management is an upstream concern. If your upstream MCP server is load-balanced across multiple instances, configure session-aware routing (sticky sessions) in Tyk's upstream load-balancing settings to ensure requests from the same session consistently reach the same instance. + +### What happens if the SSE connection drops? + +Tyk passes through the `Last-Event-ID` header when a client reconnects to the `GET /mcp` SSE stream. The upstream MCP server uses this to resume the stream from the last delivered event. Tyk maintains the long-lived connection for the duration of the session and re-establishes it transparently if it drops at the gateway layer. + +### Does Tyk buffer SSE responses? + +No. Tyk streams SSE responses through to the client without buffering the body. This preserves the real-time nature of server-sent events and avoids memory pressure on long-running tool calls that produce incremental output. + +--- + +## Access control + +### What happens when a vendor adds new tools to their MCP server? + +If you are using a tool allowlist (any tool with `"allow": { "enabled": true }`), new tools added to the upstream server are **blocked by default**; they do not appear on the allowlist, so Tyk rejects calls to them before they reach the upstream. You must explicitly add new tools to the allowlist to make them accessible. + +This is intentional. It prevents new upstream tools from becoming automatically accessible to AI agents without review, which is especially important for third-party MCP servers where you do not control the tool catalogue. + +If you have not configured any allowlist rules, all tools are accessible by default and new upstream tools become reachable immediately. + +### Can I block specific MCP protocol operations for a consumer without affecting others? + +Yes. Use `json_rpc_methods_access_rights` in the policy access rights entry for the MCP proxy. This controls which JSON-RPC protocol methods (such as `tools/call`, `tools/list`, `resources/read`, or `sampling/createMessage`) the consumer's key is allowed to use. + +To restrict a consumer to tool listing and tool calls only, blocking server-initiated sampling requests: + +```json +"json_rpc_methods_access_rights": { + "allowed": ["tools/call", "tools/list"] +} +``` + +A non-empty `allowed` list acts as an explicit allowlist; only the listed methods are accessible. Use `blocked` to exclude specific methods while leaving all others accessible. This applies per consumer key; other keys with different policies are unaffected. Configure this field via the Dashboard API; it is not available in the Dashboard UI policy editor. See [MCP proxy policies](/ai-management/mcp-gateway/policies#access-rights-entry). + +### Can I give different agents access to different tools on the same MCP server? + +Yes, and there are two approaches depending on how different the access needs to be. + +**Separate proxy definitions** work well when agents have clearly distinct roles: for example, a read-only agent and a write-access agent. Create two proxies targeting the same upstream, each with a different tool allowlist. This is straightforward to reason about and administer. + +**Policy-level primitive ACLs** (`mcp_access_rights`) work well when you have many agents with overlapping but slightly different access needs and creating a separate proxy per agent is not practical. Set `mcp_access_rights` in the policy's access rights entry for the MCP proxy to control exactly which tools, resources, and prompts that policy's consumers can invoke. Each consumer key gets its own enforced view of the proxy's primitive catalogue. + +```json +"mcp_access_rights": { + "tools": { "allowed": ["search", "get_document"] }, + "resources": { "blocked": ["internal://.*"] } +} +``` + +The two approaches compose: a proxy-level allowlist controls what any consumer can ever call; policy ACLs then further restrict what specific consumers can call within that set. Configure `mcp_access_rights` via the Dashboard API. See [MCP proxy policies](/ai-management/mcp-gateway/policies) for details. + +--- + +## Traffic management + +### Does adding Tyk as a gateway add latency? + +Tyk adds a small amount of latency for the middleware chain evaluation, typically 1–5 ms for standard middleware. For most MCP use cases, where tool calls themselves involve network round-trips and computation, this overhead is not significant. + +The latency that matters is introduced by optional middleware you choose to add: content safety plugins (which involve additional API calls to external services like Bedrock Guardrails) and circuit breakers (which introduce an evaluation step). Each of these is a trade-off you control. + +Co-locating your Tyk Gateway with your upstream MCP servers in the same network reduces the baseline overhead further. + +### How does rate limiting work for MCP traffic? + +Tyk applies rate limits at five levels. All active limits are evaluated on every request; whichever is exhausted first blocks the call. + +| Level | Scope | Configured in | +|---|---|---| +| 1. API-level | All consumers, shared ceiling | MCP proxy API definition | +| 2. Policy global | Per consumer key, all APIs in the policy | Policy `rate` / `per` | +| 3. Per MCP proxy | Per consumer key, this proxy only | Per-API limits in policy access rights | +| 4. JSON-RPC method | Per consumer key, per protocol method (e.g. `tools/call`) | `json_rpc_methods` in policy access rights | +| 5. MCP primitive | Per consumer key, per named tool/resource/prompt | `mcp_primitives` in policy access rights | + +Levels 1–3 apply to all traffic aggregated at their scope. Level 4 counts every invocation of a given JSON-RPC method regardless of which primitive is named; a `tools/call` counter covers all tool calls combined. Level 5 counts invocations of one specific named primitive. + +Levels 4 and 5 apply concurrently. A call to `get_report` increments both the `tools/call` method counter (level 4, if set) and the `get_report` primitive counter (level 5, if set). Either reaching its limit blocks the call. + +There is also a **middleware-level primitive rate limit** (configured in the API definition's `mcpTools`/`mcpResources`/`mcpPrompts` maps) that applies to all consumers of a proxy, not just one key. The key distinction: middleware rate limits protect the upstream from aggregate overload; policy rate limits enforce per-consumer entitlements. + +See [MCP proxy policies](/ai-management/mcp-gateway/policies) and [MCP middleware: traffic management](/ai-management/mcp-gateway/mcp-middleware#traffic-management). + +### Can I set different rate limits on the same tool for different consumers? + +Yes. Use `mcp_primitives` in the policy access rights entry to set per-primitive rate limits that apply only to keys bound to that policy. Two consumers on different policies can have completely different limits on the same tool, and their counters are tracked independently. + +A standard tier policy might allow 10 calls per minute to an expensive `generate_report` tool, while a premium tier policy allows 100: + +```json +"mcp_primitives": [ + { + "type": "tool", + "name": "generate_report", + "limit": { "rate": 100, "per": 60 } + } +] +``` + +This is the key difference between policy-level primitive limits and middleware-level primitive limits. Middleware limits are shared across all consumers; they protect the upstream from aggregate overload. Policy primitive limits are per-consumer; they enforce individual entitlements. Both can be active simultaneously. See [MCP proxy policies](/ai-management/mcp-gateway/policies) for the full configuration reference. + +### What happens when an upstream MCP server is slow or unavailable? + +Configure a circuit breaker on the tools that call the problematic server. Tyk monitors the failure rate and temporarily stops forwarding requests to that tool when the error rate exceeds a threshold, giving the upstream time to recover. During the open circuit period, Tyk returns a JSON-RPC error to the agent immediately rather than timing out. + +Combine this with per-tool timeouts so that a slow upstream response does not stall the entire MCP session. See [MCP middleware: circuit breakers and timeouts](/ai-management/mcp-gateway/mcp-middleware#traffic-management). + +--- + +## Observability + +### What can I see in analytics for MCP traffic? + +Tyk records analytics at two levels: proxy level and primitive level. + +**Proxy-level charts** (in the Tyk Dashboard under **Monitoring → Activity by MCP**) show total request volume, error counts, and HTTP error code distribution across all your MCP proxies. Use these to compare traffic and error rates between proxies. + +**Primitive-level charts** on the same page break the data down by individual tool, resource, or prompt: call volumes over time, most frequently called primitives, highest error rates, and slowest average latency. These charts let you identify which specific tools agents are using most, which are failing, and which are your performance bottlenecks, without needing external tooling. + +All MCP analytics appear alongside your REST and GraphQL API data, giving a unified view of the entire API estate. See [MCP observability](/ai-management/mcp-gateway/mcp-observability). + +### How do I see which AI agents are calling which tools? + +Issue a separate API key per agent (or per agent team). Keys can have an alias set at creation time that identifies the agent. Analytics are reported per key, so filtering by key shows total call volume per agent against each MCP proxy. This is why per-agent key issuance is recommended over shared keys. + +### What MCP-specific information appears in access logs? + +When structured access logging is enabled, Tyk adds four MCP-specific fields to every log record for an MCP request: + +| Field | Description | +|---|---| +| `mcp_method` | JSON-RPC method invoked, for example `tools/call` | +| `mcp_primitive_type` | Primitive category: `tool`, `resource`, or `prompt` | +| `mcp_primitive_name` | Name of the specific primitive invoked | +| `mcp_error_code` | Gateway-mapped JSON-RPC error code, present only on gateway errors | + +These fields let you filter and aggregate MCP traffic in your log management tooling using the same access log pipeline you use for REST APIs. See [MCP access logs](/ai-management/mcp-gateway/mcp-access-logs). + +### Does Tyk export OpenTelemetry metrics for MCP traffic? + +Yes. When OpenTelemetry is enabled on the gateway, Tyk emits four MCP-specific metric instruments: + +| Instrument | Type | What it measures | +|---|---|---| +| `tyk.mcp.requests.total` | Counter | Total requests, with dimensions for method, primitive type, tool name, error code, and API | +| `tyk.mcp.method.distribution` | Counter | Request counts broken down by JSON-RPC method | +| `tyk.mcp.upstream.duration` | Histogram | Upstream response time per tool | +| `tyk.mcp.request.duration` | Histogram | End-to-end request duration including gateway overhead | + +These metrics can be scraped by Prometheus and used to build dashboards in Grafana or your preferred metrics platform. See [MCP metrics](/ai-management/mcp-gateway/mcp-metrics) for the full dimension reference and PromQL examples, and [How to build a Grafana dashboard for MCP traffic](/ai-management/mcp-gateway/how-to-grafana-mcp-dashboard) for a step-by-step guide. + +### Can I exclude high-frequency health-check tools from analytics noise? + +Yes. Open the proxy, click the **Primitives** tab, add the tool as a primitive, and add **Do Not Track Endpoint** middleware to it. Alternatively, add `"doNotTrackEndpoint": { "enabled": true }` to the tool's entry in `x-tyk-api-gateway.middleware.mcpTools` directly. Either way, calls to that tool are excluded from analytics logs and dashboards. See [MCP middleware: observability](/ai-management/mcp-gateway/mcp-middleware#observability). + +--- + +## Governance and enterprise + +### We have developers connecting AI agents directly to MCP servers without oversight. How do we regain control? + +This is the **shadow IT** problem specific to AI agents. Because MCP servers are just HTTP endpoints, developers can point any MCP client at them directly using vendor-issued credentials. + +The practical approach is to make the Tyk gateway the approved, supported path and deprecate direct connections: + +1. Deploy Tyk proxies for every MCP server your organization uses or permits. +2. Establish a policy that AI agents must connect via the gateway. +3. Issue Tyk API keys to authorised agents and revoke or rotate the direct vendor credentials. +4. Use Tyk analytics to verify that direct connections have dropped to zero. + +The governance case for this (central visibility, credential management, access control, audit trail) is covered in [How to proxy a remote MCP server through Tyk](/ai-management/mcp-gateway/how-to-proxy-remote-mcp). + +### How do I apply consistent policies across all MCP servers in my organization? + +Create Tyk security policies that span multiple MCP proxies. A single policy can grant access to many proxies and apply a consistent rate limit and quota across all of them. Issuing a key against that policy gives the agent access to every permitted proxy under the same controls. + +For tool-level restrictions that apply to all consumers, each proxy has its own middleware configuration. Changes to the allowlist or rate limits on a proxy take effect immediately for all keys permitted to reach that proxy; you do not need to update each key individually. + +For restrictions that vary by consumer tier, use the policy's MCP-specific access rights fields. Set `mcp_primitives` to enforce different rate limits per primitive for different consumer tiers. Set `mcp_access_rights` to give each tier access to a different subset of tools, resources, and prompts on the same proxy. Because policies are reusable, updating a policy's primitive limits or ACLs applies immediately to every key bound to that policy. + +### Does Tyk provide an MCP service registry? + +Yes. The Tyk Dashboard's MCP section functions as a central service registry for all MCP servers in your organization. Each MCP proxy is a registry entry; it records the upstream server address, listen path, available tools and resources, and the access policies that govern it. + +Teams have a single authoritative source of truth for what MCP capabilities are available, who can access them, and under what conditions. New MCP servers are onboarded by registering a proxy in the Dashboard; until registered, they are not accessible through the gateway. This directly addresses the ungoverned proliferation problem; MCP servers cannot appear outside oversight if the gateway is the required path for agent access. + +Discovery filtering scopes the registry view per consumer: when an agent calls `tools/list`, Tyk returns only the primitives its policy permits. Each agent sees a registry filtered to its own entitlements from the moment it connects. + +See [Managing MCP proxies](/ai-management/mcp-gateway/managing-proxies) for the full management interface. + +### Do we need a separate Tyk instance for MCP or can it share infrastructure with our REST APIs? + +MCP proxies are managed through the same Tyk Gateway and Dashboard instance as your REST and GraphQL APIs. They use the same configuration model, the same policy engine, and produce analytics in the same Dashboard. No separate infrastructure is required. + +The only constraint is that MCP OAS definitions require Tyk OAS format. They cannot be created as Tyk Classic API definitions. + diff --git a/ai-management/mcp-gateway/how-to-block-tool.mdx b/ai-management/mcp-gateway/how-to-block-tool.mdx new file mode 100644 index 0000000000..9481991780 --- /dev/null +++ b/ai-management/mcp-gateway/how-to-block-tool.mdx @@ -0,0 +1,105 @@ +--- +title: "How to block an MCP tool for all consumers" +description: "Prevent a specific MCP tool from being called through a proxy, regardless of consumer policy. The blocked tool remains on the upstream server but is never reachable through the gateway." +keywords: "MCP, Model Context Protocol, block tool, blocklist, MCP middleware, MCP Inspector, Tyk Dashboard, access control" +sidebarTitle: "Block an MCP Tool" +--- + +MCP servers often expose more tools than you want to make available through the gateway. Some tools are administrative, irreversible, or simply not ready for agent access. Rather than deploying a separate server with a reduced tool set, or remembering to exclude the tool from every consumer policy, you can block individual tools at the proxy layer. + +A blocked tool is rejected by Tyk before the request reaches the upstream, and filtered out of `tools/list` responses so agents cannot discover it exists. No consumer key or policy can override a definition-level block: if the tool is blocked, it is invisible and uncallable for everyone. + +This guide blocks the `delete_user` tool on the Mock MCP Server, then uses MCP Inspector to verify that calling it returns an error while all other tools continue to work. + +--- + +## Before you begin + +- The Mock MCP Server running on `http://localhost:7878`. Set up in the [quickstart](/ai-management/mcp-gateway/quickstart). +- An MCP proxy named **Mock MCP Server** with authentication enabled. See [How to secure an MCP proxy](/ai-management/mcp-gateway/how-to-proxy-remote-mcp). +- [Node.js](https://nodejs.org/) 18 or later (to run [MCP Inspector](https://github.com/modelcontextprotocol/inspector)) + +--- + +## Instructions + +### Step 1: Block the tool + +1. In the Tyk Dashboard sidebar, click **MCP**. Find **Mock MCP Server** in the list and click **Edit** to open the proxy designer. + +2. Click the **Primitives** tab. + + ![Primitives tab on the Mock MCP Server proxy](/img/ai-management/mcp-how-to-circuitbreaker-primitives.png) + +3. Click **Add Primitive**. Set **Type** to **Tool** and enter `delete_user` as the name. Click **Add Primitive**. + + ![Add delete_user as a tool primitive](/img/ai-management/mcp-hot-too-block-add-primitive.png) + +4. Click `delete_user` to open the middleware panel. + +5. Click **Add Middleware**. + +6. Select **Block List**. + +7. Click **Add Middleware**. + + ![Block middleware selected for delete_user](/img/ai-management/mcp-how-to-block-primitive-middleware.png) + +8. Click **Save MCP Proxy**. + +--- + +### Step 2: Verify with MCP Inspector + +1. Start MCP Inspector: + + ```bash + npx @modelcontextprotocol/inspector + ``` + +2. Open the URL printed in your terminal. + +3. Set **Transport Type** to `Streamable HTTP`. + +4. Set **URL** to your MCP endpoint (find it under **MCP Proxy URL** in the proxy designer, then append `/mcp`). + +5. Add a header: `Authorization` = `Bearer {your-api-key}`. + +6. Click **Connect**. + +7. Click the **Tools** tab. Notice that `delete_user` no longer appears in the tool list. Tyk filters blocked tools out of `tools/list` responses, so agents cannot discover them at all. + + {/* TODO: Add screenshot of MCP Inspector Tools tab showing the tool list without delete_user */} + +8. To confirm the block is enforced at the call layer, enter `delete_user` manually in the tool name field, provide any value for **user_id**, and click **Run**. + + Tyk blocks the request before it reaches the upstream. The response panel shows: + + ```json + { + "jsonrpc": "2.0", + "error": { + "code": -32002, + "message": "Requested endpoint is forbidden", + "data": { + "http_code": 403 + } + }, + "id": 6 + } + ``` + +9. Select any other tool (`get_users`, `get_posts`, `get_products`) and click **Run**. Those calls succeed normally. Only `delete_user` is blocked. + +--- + +## Block vs. RBAC: when to use each + +Both `block` and RBAC allowlists restrict which tools a consumer can call, but they operate at different layers and serve different purposes. + +**Use `block`** when a tool should never be reachable through this proxy, for anyone. The restriction is set in the proxy definition and cannot be overridden by a policy. It is the right choice for tools that are dangerous, irreversible, or not yet ready for agent access: `delete_user`, `drop_table`, `send_email`. + +**Use RBAC** when different consumers should have different access to the same set of tools. A read-only agent sees only read tools; an admin agent sees all tools. The restriction is set in a security policy and scoped per consumer. See [How to implement RBAC for an MCP proxy](/ai-management/mcp-gateway/how-to-mcp-rbac). + +The two can be combined: block the tools that no one should ever reach, then use RBAC to scope what each consumer can see within what remains. + diff --git a/ai-management/mcp-gateway/how-to-grafana-mcp-dashboard.mdx b/ai-management/mcp-gateway/how-to-grafana-mcp-dashboard.mdx new file mode 100644 index 0000000000..e02dd9f924 --- /dev/null +++ b/ai-management/mcp-gateway/how-to-grafana-mcp-dashboard.mdx @@ -0,0 +1,201 @@ +--- +title: "How to build a Grafana dashboard for MCP traffic" +sidebarTitle: "Monitor MCP Traffic in Grafana" +description: "Build a Grafana dashboard to monitor MCP tool call volumes, error rates, and latency using Tyk Gateway's OpenTelemetry metrics." +keywords: "mcp, grafana, prometheus, opentelemetry, metrics, dashboard, monitoring, latency, errors, tools" +--- + +Without instrumentation, MCP traffic is opaque: you know requests are flowing, but not which tools agents are calling, which are slow, or where failures are concentrated. This guide builds a [Grafana](https://grafana.com/) dashboard for MCP traffic using Tyk Gateway's [OpenTelemetry](https://opentelemetry.io/) metrics. By the end, you will have three panels covering request volume by JSON-RPC method, error rate, and top tools by call volume. + +--- + +## Prerequisites + +### 1. Enable OTel metrics on the gateway + +Tyk exports metrics via OTLP (not a [Prometheus](https://prometheus.io/) scrape endpoint directly). The recommended setup uses an OpenTelemetry Collector to receive OTLP from Tyk and expose a Prometheus scrape endpoint. + +Add the following to your `tyk.conf`: + +```json +"opentelemetry": { + "enabled": true, + "exporter": "grpc", + "endpoint": "localhost:4317" +} +``` + +Then run an OTel Collector configured to receive OTLP on `:4317` and export to Prometheus on `:8889`. See [OpenTelemetry in Tyk](/api-management/traces) for full configuration options. + +### 2. Enable traffic logs on the MCP proxy + +Tyk Gateway only records metrics for APIs that have traffic logging enabled. In the Dashboard, open your MCP proxy definition, navigate to **Advanced Options**, and enable **Traffic Logs**. Without this, no requests are recorded and all panels will be empty. + +### 3. Configure the MCP metric instruments + +The metric instruments are configured in `tyk.conf`, under `opentelemetry.metrics`. Add the following to your gateway config: + +```json +"opentelemetry": { + "enabled": true, + "exporter": "grpc", + "endpoint": "localhost:4317", + "metrics": { + "enabled": true, + "api_metrics": [ + { + "name": "tyk.mcp.requests.total", + "type": "counter", + "description": "MCP request count by method, tool, API, and session", + "dimensions": [ + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_type", "label": "primitive_type", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_error_code", "label": "error_code", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" }, + { "source": "session", "key": "alias", "label": "session", "default": "unknown" } + ] + }, + { + "name": "tyk.mcp.upstream.duration", + "type": "histogram", + "description": "Upstream MCP server latency per tool and API", + "histogram_source": "upstream", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] + }, + { + "name": "tyk.mcp.request.duration", + "type": "histogram", + "description": "End-to-end MCP request latency per tool and API", + "histogram_source": "total", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] + }, + { + "name": "tyk.mcp.method.distribution", + "type": "counter", + "description": "Distribution of MCP method types for session efficiency analysis", + "dimensions": [ + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] + } + ] + } +} +``` + +Restart the gateway after making this change. See [MCP metrics](/ai-management/mcp-gateway/mcp-metrics) for the full reference on each instrument and additional use cases. + +--- + + +OTel metric names are translated when Prometheus scrapes them: dots become underscores, counter instruments get a `_total` suffix, and histogram instruments with a seconds unit get a `_seconds` suffix. So `tyk.mcp.requests.total` becomes `tyk_mcp_requests_total` in PromQL, and `tyk.mcp.upstream.duration` becomes `tyk_mcp_upstream_duration_seconds_bucket`, `tyk_mcp_upstream_duration_seconds_sum`, and `tyk_mcp_upstream_duration_seconds_count`. + + +--- + +## Instructions + +### Step 1: Create the dashboard + +1. In Grafana, go to **Connections → Data Sources → Add new data source**, select **Prometheus**, set the URL to your Prometheus server (e.g. `http://prometheus:9090`), and click **Save & test**. + +2. Go to **Dashboards → New → New dashboard**. + + ![Grafana new dashboard](/img/ai-management/grafana-new-dashboard.png) + +3. Open **Dashboard settings → Variables → Add variable**. + + ![Grafana dashboard settings](/img/ai-management/grafana-dashboard-settings.png) + +4. Set the following fields and click **Save**: + + | Field | Value | + |---|---| + | **Variable type** | Datasource | + | **Name** | `datasource` | + | **Plugin type** | Prometheus | + + ![Grafana data source variable](/img/ai-management/grafana-data-source-variable.png) + +### Step 2: Generate traffic + +1. Make a few tool calls through your MCP proxy to populate the metrics. + + If you need a quick way to generate traffic, use MCP Inspector with the Mock MCP Server; the full setup is covered in [How to secure an MCP proxy](/ai-management/mcp-gateway/how-to-proxy-remote-mcp). Once you have traffic flowing, come back here to build the dashboard. + +### Step 3: Request rate by method + +1. Click **Add → Visualization** and select **Time series**. Set the title to `MCP request rate by method`. + +2. Set the PromQL query to: + + ```promql + sum by (mcp_method) (rate(tyk_mcp_requests_total[$__rate_interval])) + ``` + +3. Set the **Legend** to `{{mcp_method}}` and click **Apply**. + + {/* TODO: Screenshot — Grafana Time series panel showing MCP request rate by method, with multiple series coloured by mcp_method (e.g. tools/call, initialize, tools/list) */} + + +`$__rate_interval` is a Grafana built-in variable that automatically selects an appropriate rate interval based on the dashboard time range and scrape interval. It produces more accurate rate calculations than a hardcoded interval such as `[5m]` and is the recommended choice for rate and histogram queries. + + +### Step 4: Error rate + +1. Click **Add → Visualization** and select **Stat**. Set the title to `MCP error rate`. + +2. Set the PromQL query to: + + ```promql + sum(rate(tyk_mcp_requests_total{error_code!=""}[$__rate_interval])) + / + sum(rate(tyk_mcp_requests_total[$__rate_interval])) + * 100 + ``` + +3. Set the **Unit** to `Percent (0-100)`. + +4. Configure thresholds and click **Apply**: + + | Value | Color | + |---|---| + | 0 | Green | + | 1 | Orange | + | 5 | Red | + + {/* TODO: Screenshot — Grafana Stat panel showing MCP error rate as a percentage, with green/orange/red threshold colouring */} + +### Step 5: Top tools by call volume + +1. Click **Add → Visualization** and select **Bar chart**. Set the title to `Top tools by call volume`. + +2. Set the PromQL query to: + + ```promql + topk(10, sum by (tool_name) (increase(tyk_mcp_requests_total{mcp_method="tools/call"}[$__range]))) + ``` + +3. Set the **Legend** to `{{tool_name}}` and click **Apply**. + + {/* TODO: Screenshot — Grafana Bar chart panel showing top 10 tools by call volume, with tool names on the x-axis */} + + +If the panel shows tool names but all counts are zero immediately after generating traffic, this is normal. The `increase()` function requires at least two Prometheus scrapes to return a non-zero result. Wait for the second OTel export cycle (up to two minutes) and refresh the dashboard. + + +![Grafana MCP dashboard](/img/ai-management/grafana.png) + +## Next steps + +The [MCP metrics](/ai-management/mcp-gateway/mcp-metrics) reference covers additional instruments you can add to this dashboard, including P95 upstream latency per tool, end-to-end latency, error classification by code, and per-session usage. diff --git a/ai-management/mcp-gateway/how-to-mcp-rbac.mdx b/ai-management/mcp-gateway/how-to-mcp-rbac.mdx new file mode 100644 index 0000000000..f7c3211a7c --- /dev/null +++ b/ai-management/mcp-gateway/how-to-mcp-rbac.mdx @@ -0,0 +1,147 @@ +--- +title: "How to implement role-based access control for an MCP proxy" +description: "Use Tyk policies to give different AI agents different tool access on the same MCP proxy, without creating separate proxy definitions." +keywords: "MCP, Model Context Protocol, RBAC, role-based access control, mcp_access_rights, MCP policies, tool allowlist, MCP Inspector, AI agent, Tyk Dashboard" +sidebarTitle: "MCP RBAC" +--- + +After [securing your MCP proxy](/ai-management/mcp-gateway/how-to-proxy-remote-mcp), the next step is controlling what each consumer can do. Role-based access control (RBAC) in Tyk MCP lets you give different agents different views of the same proxy, without creating separate proxy definitions. A policy bound to a key determines which tools that agent can invoke. + +This guide creates two roles on the Mock MCP Server: + +- **Reader**: can only call `get_users`, `get_posts`, `get_products`, and `get_analytics` +- **Admin**: can call all 15 tools + +You'll create a policy for each role, issue role-specific keys, then use [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to verify that each key sees exactly the tools it is permitted to access. + +--- + +## Two approaches to tool-level access control + +Tyk supports two complementary mechanisms for controlling what an AI agent can do on an MCP proxy. + +**Policy-based access control** (this guide) — Tyk policies define an explicit allowlist of tools a consumer is permitted to invoke, and apply platform-level controls to each tool: rate limits, quotas, and throttling. A policy is bound to a key at issuance time and enforced at the gateway, regardless of what the agent's bearer token claims. This works with any authentication method, requires no changes to your identity provider, and lets the platform team manage access centrally without touching the IdP. + +**Scope-based access control** — when you use the [oauth2 security scheme](/api-management/authentication/oauth2-authentication), you can declare required scopes on individual tools in the API definition. Tyk validates those scopes against the inbound token's `scope` claim at runtime, delegating fine-grained access decisions to your IdP. This approach is well suited to environments where access rules change frequently, or where the IdP is already the source of truth for permissions. + +The two mechanisms are complementary and can be combined. Policies enforce platform-level controls — which tools a consumer can reach, and at what rate — while scope check validates what the IdP has actually granted in the token at runtime. Together they give you both platform governance and identity-driven authorization. + +This guide covers policy-based access control only. + +--- + +## How it works + +Tyk policies control consumer access at two levels relevant to this guide: + +**Primitive access**: restricts which specific tools a consumer can invoke. When a key's policy includes an allowed list for tools, Tyk enforces it on both `tools/call` (blocking disallowed tool invocations) and `tools/list` (filtering the response so the agent only sees tools it can use). The upstream server is never reached for blocked calls. + +**Proxy access**: determines which MCP proxies the key can reach at all. + +Both keys in this guide point at the same proxy URL. The difference in behavior comes entirely from the policies applied to each key. + +For the complete policy reference, see [MCP policies](/ai-management/mcp-gateway/policies). + +--- + +## Before you begin + +- The Mock MCP Server running on `http://localhost:7878`. Set up in the [quickstart](/ai-management/mcp-gateway/quickstart). +- An MCP proxy named **Mock MCP Server** with authentication enabled. See [How to secure an MCP proxy](/ai-management/mcp-gateway/how-to-proxy-remote-mcp). +- [Node.js](https://nodejs.org/) 18 or later (to run [MCP Inspector](https://github.com/modelcontextprotocol/inspector)) +- A Dashboard user account with policy management permissions + +--- + +## Instructions + +### Step 1: Create the Reader policy + +1. In the Tyk Dashboard sidebar, click **Policies**, then click **Add Policy**. + +2. On the **Access Rights** tab, find **Mock MCP Server** in the API list and click it to add it. + + ![Select Mock MCP Server from the API list](/img/ai-management/tyk-how-to-rbac-select-api.png) + +3. Scroll to **Primitive based access** within the Mock MCP Server panel and add each permitted tool: + + - Click **Add**, enter `get_users`, set **Type** to **Tool**, and set the status to **Allowed**. Click **Add**. + - Repeat for `get_posts`, `get_products`, and `get_analytics`. + + ![Primitive based access configuration](/img/ai-management/mcp-how-to-rbac-primitive.png) + + Once you add any tool with **Allowed** status, Tyk treats the list as an explicit allowlist: any tool not in the list is blocked for keys on this policy. + +4. Click the **Configurations** tab and set: + - **Policy Name**: `Reader` + - **Policy State**: **Active** + +5. Click **Create Policy**. + + ![Reader policy configuration](/img/ai-management/mcp-how-to-rbac-reader.png) + +### Step 2: Create the Admin policy + +The Admin policy grants unrestricted tool access. Omitting the **Primitive based access** entries means all tools are accessible. + +1. Click **Add Policy**. + +2. On the **Access Rights** tab, add **Mock MCP Server**. + +3. Click the **Configurations** tab and set: + - **Policy Name**: `Admin` + - **Policy State**: **Active** + +4. Click **Create Policy**. + + {/* TODO: Screenshot — Admin policy Configurations tab showing Policy Name "Admin" and Active state */} + +### Step 3: Issue role-specific keys + +1. In the Dashboard sidebar, click **Keys**, then **Add Key**. + +2. Under **Access rights**, click **Apply Policy** and select **Reader**. + + ![Apply Reader policy to key](/img/ai-management/mcp-how-to-rbac-keys.png) + +3. Click the **Configurations** tab and set an **Alias** such as `reader-agent`. + + ![Set alias and create key](/img/ai-management/mcp-how-to-rbac-set-alias.png) + +4. Click **Create Key** and copy the key. + +5. Repeat steps 1–4 to issue a second key, selecting **Admin** as the policy and `admin-agent` as the alias. + +### Step 4: Verify in MCP Inspector + +1. Start MCP Inspector: + + ```bash + npx @modelcontextprotocol/inspector + ``` + +2. Open the URL printed in your terminal. + +#### Test the Reader key + +3. Set **Transport Type** to `Streamable HTTP`. + +4. Set **URL** to your MCP endpoint (find it under **MCP Proxy URL** in the proxy designer, then append `/mcp`). + +5. Add a header: `Authorization` = `Bearer {reader-api-key}` and click **Connect**. + +6. Click the **Tools** tab. You will see exactly four tools: `get_users`, `get_posts`, `get_products`, and `get_analytics`. Tyk has filtered the `tools/list` response based on the Reader policy's allowed list. + +7. Select `get_users` and click **Run**. It succeeds. + + {/* TODO: Screenshot — MCP Inspector Tools tab showing only the 4 allowed tools for the Reader key */} + +#### Test the Admin key + +8. Click **Disconnect**. Replace the key in the `Authorization` header with your Admin key and click **Connect**. + +9. Click the **Tools** tab. All 15 Mock MCP Server tools appear. The Admin policy applies no tool restrictions. + + {/* TODO: Screenshot — MCP Inspector Tools tab showing all 15 tools for the Admin key */} + +Both keys connect to the same proxy at the same URL. The difference in tool availability is driven entirely by the policy. diff --git a/ai-management/mcp-gateway/how-to-proxy-remote-mcp.mdx b/ai-management/mcp-gateway/how-to-proxy-remote-mcp.mdx new file mode 100644 index 0000000000..ffe27e55cc --- /dev/null +++ b/ai-management/mcp-gateway/how-to-proxy-remote-mcp.mdx @@ -0,0 +1,80 @@ +--- +title: "How to secure an MCP proxy" +description: "Secure a remote MCP Server so only authorised agents can connect. This guide uses the Tyk Mock MCP Server and takes around ten minutes." +keywords: "MCP, Model Context Protocol, MCP proxy, API key authentication, MCP security, bearer token, Tyk Dashboard, MCP Inspector" +sidebarTitle: "Secure MCP Proxy" +--- + +After completing the [quickstart](/ai-management/mcp-gateway/quickstart), you have a working MCP proxy, but it accepts connections from any client. This guide secures your remote MCP server so that only agents with a valid key can reach it. + +--- + +## Before you begin + +- A Tyk Gateway (v5.13 or later) connected to your Tyk Dashboard +- The Mock MCP Server running on `http://localhost:7878`. See the [quickstart](/ai-management/mcp-gateway/quickstart). +- An MCP proxy named **Mock MCP Server** already created. Also covered in the quickstart. +- [Node.js](https://nodejs.org/) 18 or later (to run [MCP Inspector](https://github.com/modelcontextprotocol/inspector)) +- A Dashboard user account with MCP write permissions + +--- + +## Instructions + +### Step 1: Enable authentication + +1. In the Tyk Dashboard sidebar, click **MCP**, then click **Edit** next to **Mock MCP Server**. + +2. In the designer, click the **Authentication** switch. + +3. Select **Auth Token** as the authentication method. + +4. Set the token location to **use header value** and leave the header name as `Authorization`. + + ![Auth token header configuration](/img/ai-management/mcp-how-to-secure-add-header.png) + +5. Click **Save MCP Proxy**. + + The proxy now requires a bearer token on every request. Clients that connect without a valid key receive a `401 Unauthorized` response. + +### Step 2: Issue an API key + +1. In the Dashboard sidebar, click **Keys**, then click **Add Key**. + +2. Under **Access rights**, click **Choose API** and select **Mock MCP Server**. + +3. Click **Create Key**. Copy the key shown — you cannot retrieve it after navigating away. + + ![API key created](/img/ai-management/mcp-how-to-secure-add-key.png) + +### Step 3: Verify with MCP Inspector + +1. Start MCP Inspector: + + ```bash + npx @modelcontextprotocol/inspector + ``` + +2. Open the URL printed in your terminal. + +3. Set **Transport Type** to `Streamable HTTP`. + +4. Set **URL** to your MCP endpoint (find it under **MCP Proxy URL** in the proxy designer, then append `/mcp`). + +5. Click **Connect** without adding an `Authorization` header. The connection fails with a `401 Unauthorized` error, confirming authentication is enforced. + + {/* TODO: Screenshot — MCP Inspector showing the 401 Unauthorized error response when connecting without a key */} + +6. Add a header: `Authorization` = `Bearer {your-api-key}` and click **Connect** again. + + ![MCP Inspector connected with API key](/img/ai-management/mcp-how-to-secure-mcp-inspector.png) + +7. Click the **Tools** tab. All 15 Mock MCP Server tools appear. + +--- + +## Limitations and alternatives + +API key authentication via a bearer token header is a straightforward way to secure an MCP proxy, but it has limitations: keys are long-lived, there is no built-in token expiry or rotation, and clients must manage the key securely. + +For more demanding scenarios, Tyk supports a range of [client authentication methods](/api-management/client-authentication), including JWT, mutual TLS, and OAuth 2.1. For MCP specifically, Tyk extends OAuth 2.1 with Protected Resource Metadata so MCP-aware clients can discover authentication requirements automatically. See [MCP Gateway: OAuth 2.1 authentication](/ai-management/mcp-gateway/oauth-2-1). diff --git a/ai-management/mcp-gateway/how-to-rate-limit-tools-per-consumer.mdx b/ai-management/mcp-gateway/how-to-rate-limit-tools-per-consumer.mdx new file mode 100644 index 0000000000..b5a68786f6 --- /dev/null +++ b/ai-management/mcp-gateway/how-to-rate-limit-tools-per-consumer.mdx @@ -0,0 +1,121 @@ +--- +title: "How to rate limit individual MCP tools per consumer" +description: "Apply per-tool rate limits to an MCP proxy so different consumers have different call budgets on the same tool, without creating separate proxy definitions." +keywords: "MCP, Model Context Protocol, per-tool rate limiting, mcp_primitives, security policy, AI agent, MCP Inspector, Tyk Dashboard" +sidebarTitle: "Rate Limit an MCP Tool" +--- + +Not all MCP tools cost the same. A tool that runs a complex query costs far more than one returning cached data. When multiple agents share the same proxy, a single blanket rate limit either over-restricts lightweight tools or under-protects expensive ones. + +Tyk lets you set rate limits on individual tools, per consumer. Each agent key tracks its own independent counter: one agent exhausting their budget on a tool does not affect another agent's counter for the same tool. + +This guide rate limits the `get_analytics` tool on the Mock MCP Server to 3 calls per minute for a specific consumer policy, then uses MCP Inspector to verify the limit is enforced. + +--- + +## Before you begin + +- The Mock MCP Server running on `http://localhost:7878`. Set up in the [quickstart](/ai-management/mcp-gateway/quickstart). +- An MCP proxy named **Mock MCP Server** with authentication enabled. See [How to secure an MCP proxy](/ai-management/mcp-gateway/how-to-proxy-remote-mcp). +- [Node.js](https://nodejs.org/) 18 or later (to run [MCP Inspector](https://github.com/modelcontextprotocol/inspector)) +- A Dashboard user account with policy management permissions + +--- + +## Instructions + +### Step 1: Create a policy with a per-tool rate limit + +1. In the Tyk Dashboard sidebar, click **Policies**, then click **Add Policy**. + +2. On the **Access Rights** tab, find **Mock MCP Server** in the API list and click it to add it. + +3. Expand the Mock MCP Server access rights block and scroll to **Set Usage Limits by MCP Primitives/Methods**. + +4. Click **Add Rate Limit** and configure the limit: + - Set **Rate** to `3` + - Set **Per** to `60` seconds + - Click **Add**, enter `get_analytics`, and set **Type** to **Tool** + + ![Add get_analytics as a tool primitive](/img/ai-management/mcp-how-to-rate-limit-tool.png) + +5. Click **Add** to confirm the primitive. + +6. Click the **Configurations** tab and set: + - **Policy Name**: `Limited Agent` + - **Policy State**: **Active** + +7. Click **Create Policy**. + + ![Create the Limited Agent policy](/img/ai-management/mcp-how-to-limited-agent.png) + +--- + +### Step 2: Issue a key + +1. In the Dashboard sidebar, click **Keys**, then **Add Key**. + +2. Under **Access rights**, click **Apply Policy** and select **Limited Agent**. + +3. Click the **Configurations** tab and set an **Alias** such as `limited-agent` to identify this key in analytics. + +4. Click **Create Key** and copy the key. + +--- + +### Step 3: Verify with MCP Inspector + +1. Start MCP Inspector: + + ```bash + npx @modelcontextprotocol/inspector + ``` + +2. Open the URL printed in your terminal. + +3. Set **Transport Type** to `Streamable HTTP`. + +4. Set **URL** to your MCP endpoint (find it under **MCP Proxy URL** in the proxy designer, then append `/mcp`). + +5. Add a header: `Authorization` = `Bearer {your-api-key}`. + +6. Click **Connect**. + +7. Click the **Tools** tab and select **get_analytics**. + +8. The tool requires a **metric** parameter. Enter `users` (or any of `posts`, `orders`, `revenue`). + +9. Click **Run** three times in quick succession. Each call succeeds. The response panel shows the analytics data from the Mock MCP Server. + +10. Click **Run** a fourth time. Tyk has exhausted the 3 calls per minute budget for this consumer and blocks the request. The response panel shows: + + {/* TODO: Add screenshot of the rate limit error response in MCP Inspector */} + + **MCP error -32001: Streamable HTTP error: Error POSTing to endpoint:** + + ```json + { + "jsonrpc": "2.0", + "error": { + "code": -32003, + "message": "Rate Limit Exceeded", + "data": { + "http_code": 429 + } + }, + "id": 7 + } + ``` + +11. Click any other tool (`get_users`, `get_posts`, `get_products`) and click **Run**. Those calls succeed normally. Only the `get_analytics` counter is exhausted. + +--- + +## How per-consumer and shared limits compose + +The rate limit configured in this guide applies per consumer key: each key on the **Limited Agent** policy has its own independent counter for `get_analytics`. A second key on the same policy has its own separate 3 calls per minute budget. + +You can also apply a shared ceiling across all consumers at the API definition level using the **Primitives** tab on the proxy. A shared limit protects the upstream from aggregate overload, regardless of individual consumer budgets. Both limits are enforced simultaneously: whichever is exhausted first blocks the call. + +For the full picture of how rate limits compose across the middleware and policy layers, see [MCP proxy policies](/ai-management/mcp-gateway/policies). + diff --git a/ai-management/mcp-gateway/managing-proxies.mdx b/ai-management/mcp-gateway/managing-proxies.mdx new file mode 100644 index 0000000000..ab3595d469 --- /dev/null +++ b/ai-management/mcp-gateway/managing-proxies.mdx @@ -0,0 +1,195 @@ +--- +title: "Managing MCP proxies using the Dashboard" +description: "How to create, view, edit, and delete MCP proxies using the Tyk Dashboard UI, covering the proxy list, the creation wizard, the Settings and Primitives tabs, proxy-level middleware, per-primitive middleware, the full definition editor, and permissions." +keywords: "MCP, Model Context Protocol, MCP proxy, create MCP, update MCP, delete MCP, Tyk Dashboard, MCP wizard, listen path, permissions, MCP middleware, primitives tab, settings tab" +sidebarTitle: "Manage MCP Proxies" +--- + +The **MCP** section of the Tyk Dashboard is the central registry of all MCP servers in your organization. Each proxy entry records the upstream server address, the listen path clients use to connect, the tools and resources the server exposes, and the access policies that govern it. Teams have a single authoritative place to see what MCP capabilities are available, onboard new servers, and control who can access them. + +The section gives you a searchable catalog of all registered proxies, a guided creation wizard for onboarding new servers, and the MCP Designer with two tabs: **Settings** for proxy-level configuration and middleware, and **Primitives** for managing per-primitive middleware on individual tools, resources, and prompts. + +For scripted or automated management, use the Dashboard API or Gateway API. See [MCP API extensions](/ai-management/mcp-gateway/mcp-api-extensions) for the full endpoint reference. + + +MCP OAS definitions use the Tyk OAS format. They are not available as Tyk Classic API definitions. For the full definition structure, see [MCP OAS definition](/ai-management/mcp-gateway/mcp-proxy-definitions). + + +--- + +## Permissions + +Access to MCP proxy management is controlled by the `mcp` permission on the user's role. + +| Permission level | What the user can do | +|---|---| +| **Write** | Create, view, edit, and delete MCP proxies. Full access to all UI actions. | +| **Read** | View the proxy list and MCP Designer. Access the definition viewer. Cannot create, edit, save, or delete. | +| **Deny** | No access. The **MCP** sidebar item is not visible. | + +Permissions are assigned in the Dashboard under **User Management → Users**. For organization-wide access control, configure permissions on user groups rather than individual users. + +![MCP permission setting in the Dashboard](/img/ai-management/mcp-permission.png) + +--- + +## The MCP proxies list + +The list page shows every MCP proxy managed by this Dashboard instance. Clicking a row opens the MCP Designer. + +The search input at the top filters proxies by name in real time. Clear the input to return to the full list. + +The **Add MCP Proxy** button opens the creation wizard. + +![MCP proxy list](/img/ai-management/mcp-proxy-list.png) + +--- + +## Create an MCP proxy + +The creation wizard collects the minimum information needed to define a working MCP proxy. It has three steps. + +### Step 1: Basic information + +| Field | Required | What it sets | +|---|---|---| +| **Name** | Yes | Display name for the proxy. Also used to identify it in policy and key configuration. Maps to `x-tyk-api-gateway.info.name`. | +| **Description** | No | Free-text description for team reference. Maps to `info.description`. | + +The name must be unique across all MCP proxies in this Dashboard instance. + +Click **Continue** to proceed. + +![Create MCP proxy, step 1: basic information](/img/ai-management/create-mcp-stage-1.png) + +### Step 2: Register server + +| Field | Required | What it sets | +|---|---|---| +| **Server URL** | Yes | The base URL of the upstream MCP server. Tyk forwards all MCP traffic to this address. Maps to `x-tyk-api-gateway.upstream.url`. | + +Enter the full URL of your upstream MCP server, for example `https://weather-mcp.example.com`. This is the server Tyk proxies to, not the URL clients use to connect to Tyk. + +Click **Continue** to proceed. + +![Create MCP proxy, step 2: register server](/img/ai-management/create-mcp-proxy-stage-2.png) + +### Step 3: Connect gateways + +Select the gateway instances to deploy this proxy to. You can click **Skip for now** to save the proxy without deploying it; it will exist in the Dashboard but will not serve traffic until you return and assign a gateway. + +Click **Finish** to save. The Dashboard displays "MCP proxy successfully created" and returns you to the proxy list. + + +The wizard creates the proxy with bearer token authentication enabled and a listen path derived from the proxy name. For most deployments you'll want to open the MCP Designer to configure authentication, add per-primitive middleware, or set up OAuth discovery. See the [Settings tab](#settings-tab) and [Primitives tab](#primitives-tab) below. + + +--- + +## The MCP Designer + +Clicking a proxy in the list opens the MCP Designer. The MCP Designer has two tabs. + +![MCP Designer tabs](/img/ai-management/mcp-designer-tabs.png) + +### Settings tab + +The **Settings** tab covers two areas: core proxy configuration and proxy-level middleware. + +**Core configuration**: name, upstream server URL, and gateway assignment. To edit these fields, make your changes and click **Save MCP Proxy**. The Dashboard triggers a gateway reload automatically. + +**Authentication**: the authentication method applied to all inbound requests. Select a method from the **Authentication type** dropdown. See [Authentication](/api-management/client-authentication) for all supported methods and configuration options. + +**Proxy-level middleware**: middleware that applies to all requests through this proxy, regardless of which primitive is invoked. The following options are available in the Settings tab: + +| Middleware | What it does | +|---|---| +| **CORS** | Configures cross-origin resource sharing headers for browser-based MCP clients. | +| **Transform Request Headers** | Adds, removes, or modifies HTTP headers on every request forwarded to the upstream. | +| **Transform Response Headers** | Adds, removes, or modifies HTTP headers on every response returned to clients. | +| **Context Variables** | Enables Tyk context variables (request metadata such as IP, key ID, and path) for use in header transforms and plugins. | +| **Traffic Logs** | Configures how request and response data is captured in analytics. | +| **Plugin Config / Bundle** | Configures custom plugin drivers and bundle sources for gateway-side plugin execution. | + +These options map to `x-tyk-api-gateway.middleware.global` in the proxy definition. For full configuration details, see [MCP middleware: proxy level](/ai-management/mcp-gateway/mcp-middleware#dashboard-settings-tab-proxy-level). + +### Primitives tab + +The **Primitives** tab lists every tool, resource, and prompt you have manually registered for this proxy. Each entry shows the primitive's name, its type (Tool, Resource, or Prompt), and the number of middleware rules applied to it. Use the type filter and search input to narrow the list. + +**Adding a primitive** + +Click **Add Primitive** to open the add primitive modal. Select the type (Tool, Resource, or Prompt) and enter the primitive name: this must match the name the upstream MCP server uses when advertising that primitive. Names cannot contain whitespace and must be unique within their type. + +Adding a primitive creates an entry in `x-tyk-api-gateway.middleware.mcpTools`, `mcpResources`, or `mcpPrompts` (depending on type) in the proxy definition. + +**Adding middleware to a primitive** + +Click a primitive to open its detail view, then click **Add Middleware**. The following middleware is available for primitives: + +| Category | Middleware | +|---|---| +| Security & Validation | Allow, Block, Ignore Authentication, Request Size Limit | +| Traffic management | Rate Limit, Circuit Breaker | +| Transformation | Transform Request Headers, Transform Response Headers, Virtual Endpoint | +| Analytics | Track Endpoint, Do Not Track Endpoint | + +Each middleware option maps to the corresponding configuration block inside the primitive's entry in the proxy definition. See [MCP middleware](/ai-management/mcp-gateway/mcp-middleware) for what each option does and how it is configured. + +--- + +## Editing the full definition + +The Dashboard UI covers most proxy configuration. To access advanced options not yet exposed in the UI — such as upstream OAuth, per-primitive token exchange overrides, and traffic management — edit the proxy's MCP OAS definition directly. + +Open the editor via **Actions → View MCP Proxy Definition** on the MCP Designer. + +![View MCP Proxy Definition](/img/ai-management/view-mcp-proxy-definition.png) + +The MCP OAS definition is an OpenAPI 3.0.3 document with an `x-tyk-api-gateway` vendor extension containing all Tyk-specific configuration. The key sections are: + +| Section | What it configures | +|---|---| +| `x-tyk-api-gateway.server.authentication` | Authentication method (bearer token, JWT, OAuth, mTLS) and settings. | +| `x-tyk-api-gateway.server.authentication.securitySchemes[name].oauth2.protectedResourceMetadata` | PRM for OAuth 2.1 discovery: the `/.well-known/oauth-protected-resource` endpoint. | +| `x-tyk-api-gateway.upstream` | Upstream URL, load balancing, upstream authentication, and mTLS. | +| `x-tyk-api-gateway.middleware.mcpTools` | Per-tool middleware: access control, rate limits, timeouts, circuit breakers, request transformation. | +| `x-tyk-api-gateway.middleware.mcpResources` | Per-resource middleware: same options as tools, keyed by resource URI or URI pattern. | +| `x-tyk-api-gateway.middleware.mcpPrompts` | Per-prompt middleware: same options as tools, keyed by prompt name. | +| `x-tyk-api-gateway.middleware.operations` | Method-level middleware applying to all calls of a given JSON-RPC method. | +| `x-tyk-api-gateway.middleware.global` | API-wide middleware applying to all requests. | + +After editing, click **Save MCP Proxy** to save and redeploy. Changes are applied to all connected gateways automatically. + +### Common edits after initial setup + +**Configuring authentication**: The proxy is created with bearer token authentication enabled. To switch to a different method, open the Settings tab and select from the **Authentication type** dropdown. To use the external IdP integration — including scope check, PRM, and token exchange — select **OAuth 2.0**. See [Authentication](/api-management/client-authentication) for all supported methods. + +**Enabling PRM for OAuth discovery**: Open the Settings tab, select **OAuth 2.0** as the authentication type, and enable the **Protected Resource Metadata** toggle. Set the resource URL and add at least one authorization server URL. See [OAuth 2.0 authentication](/api-management/authentication/oauth2-authentication) for full configuration details. + +**Restricting which tools clients can call**: Use the Primitives tab: add the tool as a primitive, then add **Allow** middleware to it. Once any tool in the proxy has an Allow rule, all unlisted tools are blocked. For definition-based configuration, see [MCP middleware: access control](/ai-management/mcp-gateway/mcp-middleware#access-control). + +**Applying rate limits to a specific tool**: Use the Primitives tab: open the tool primitive and add **Rate Limit** middleware. For definition-based configuration, see [MCP middleware: traffic management](/ai-management/mcp-gateway/mcp-middleware#traffic-management). + +**Configuring upstream OAuth**: Add an `authentication.oauth.clientCredentials` block to `x-tyk-api-gateway.upstream` to have Tyk obtain and forward OAuth tokens to your upstream MCP server. See [MCP Gateway: OAuth 2.1 authentication](/ai-management/mcp-gateway/oauth-2-1#upstream-oauth). + +**Adding CORS or global header transforms**: Configure these in the Settings tab under the middleware section. Changes apply to all requests through the proxy. + +For a complete explanation of every field in the definition, see [MCP OAS definition](/ai-management/mcp-gateway/mcp-proxy-definitions). + +--- + +## Delete an MCP proxy + +1. In the sidebar, click **MCP**. +2. Open the proxy you want to delete. +3. Click **Actions → Delete MCP Proxy** and confirm. + +{/* TODO: Screenshot — Actions menu open on an MCP proxy showing the "Delete MCP Proxy" option */} + +Deletion removes the proxy definition from the Dashboard and undeploys it from all connected gateways. Associated API keys and policies are not removed automatically; remove the access right from any keys scoped to this proxy, or delete those keys separately. + + +Deleting an MCP proxy also removes it from any versioning hierarchy it belongs to. If the deleted proxy was a versioned child, the version entry is removed from the base proxy's definition. + + diff --git a/ai-management/mcp-gateway/mcp-access-logs.mdx b/ai-management/mcp-gateway/mcp-access-logs.mdx new file mode 100644 index 0000000000..a75470dc2a --- /dev/null +++ b/ai-management/mcp-gateway/mcp-access-logs.mdx @@ -0,0 +1,129 @@ +--- +title: "MCP Gateway Access Logs" +sidebarTitle: "Access Logs" +description: "MCP-specific fields available in Tyk Gateway structured access logs, including JSON-RPC method, primitive type, primitive name, and error code." +keywords: "mcp, access logs, observability, logging, mcp_method, mcp_primitive_name, json-rpc" +--- + +When Tyk Gateway processes an MCP request, it adds four MCP-specific fields to the structured access log record for that request. These fields let you filter, aggregate, and analyze MCP traffic in your log management tooling using the same access log pipeline you use for REST APIs. + +For an overview of all MCP observability signals, see [MCP observability](/ai-management/mcp-gateway/mcp-observability). + +## Prerequisites + +Access logging must be enabled in your Tyk Gateway configuration. Set `access_logs.enabled` to `true` in `tyk.conf`: + +```json +{ + "access_logs": { + "enabled": true + } +} +``` + +## MCP fields + +The following fields are added to access log records for MCP requests. Each field is only included when it has a non-empty value; fields are omitted from the record entirely when not applicable, keeping log volume low on lifecycle calls such as `initialize` and `ping`. + +| Field | Type | Description | +|---|---|---| +| `api_type` | string | API protocol type. Always `mcp` for MCP requests. | +| `mcp_method` | string | JSON-RPC method invoked, for example `tools/call` or `resources/read`. Present on all MCP requests. | +| `mcp_primitive_type` | string | MCP primitive category: `tool`, `resource`, or `prompt`. Present only when a primitive was matched. | +| `mcp_primitive_name` | string | Name of the specific tool, resource, or prompt invoked, for example `get_current_weather`. Present only when a primitive was matched. | +| `mcp_error_code` | integer | Gateway-mapped JSON-RPC error code. Present only when the request failed at the gateway layer. | + +The `mcp_error_code` field reflects errors that occur within the gateway: authentication failures, rate limit rejections, and upstream errors. It does not capture error codes from within the upstream MCP server's JSON-RPC response body. + +| Error code | Meaning | +|---|---| +| `-32001` | Authentication required | +| `-32002` | Access denied | +| `-32003` | Rate limit exceeded | +| `-32004` | Upstream error (502/503/504) | +| `-32600` | Invalid request (400) | +| `-32603` | Internal error (500) | + +## Configuring which fields to include + +By default, when access logging is enabled, all available fields are included in each log record. To restrict the fields logged, set a `template` array in `access_logs`: + +```json +{ + "access_logs": { + "enabled": true, + "template": [ + "client_ip", + "api_id", + "api_name", + "api_type", + "method", + "path", + "status", + "latency_total", + "mcp_method", + "mcp_primitive_type", + "mcp_primitive_name", + "mcp_error_code" + ] + } +} +``` + +When a `template` is configured, only the listed fields appear in each log record. Fields in the template that have no value for a given request are omitted. + +## Example log records + +A successful `tools/call` request produces a record similar to the following: + +```json +{ + "api_id": "my-weather-mcp", + "api_name": "Weather MCP Proxy", + "api_type": "mcp", + "client_ip": "10.0.0.42", + "latency_total": 312, + "method": "POST", + "mcp_method": "tools/call", + "mcp_primitive_name": "get_current_weather", + "mcp_primitive_type": "tool", + "path": "/mcp", + "prefix": "access-log", + "status": 200 +} +``` + +A request that fails at the gateway due to a missing or invalid key produces a record with `mcp_error_code` set and no primitive fields (the primitive was never reached): + +```json +{ + "api_id": "my-weather-mcp", + "api_type": "mcp", + "client_ip": "10.0.0.42", + "latency_total": 4, + "method": "POST", + "mcp_error_code": -32001, + "mcp_method": "tools/call", + "path": "/mcp", + "prefix": "access-log", + "status": 401 +} +``` + +An `initialize` lifecycle call produces a record with `mcp_method` set but no primitive fields: + +```json +{ + "api_id": "my-weather-mcp", + "api_type": "mcp", + "client_ip": "10.0.0.42", + "latency_total": 18, + "method": "POST", + "mcp_method": "initialize", + "path": "/mcp", + "prefix": "access-log", + "status": 200 +} +``` + + diff --git a/ai-management/mcp-gateway/mcp-analytics.mdx b/ai-management/mcp-gateway/mcp-analytics.mdx new file mode 100644 index 0000000000..1a6c7ac1ed --- /dev/null +++ b/ai-management/mcp-gateway/mcp-analytics.mdx @@ -0,0 +1,84 @@ +--- +title: "MCP Analytics" +sidebarTitle: "MCP Analytics" +description: "Using Tyk Dashboard's Activity by MCP page to monitor traffic and errors across your MCP proxies and their tools, resources, and prompts." +keywords: "mcp, analytics, dashboard, activity by mcp, primitives, tools, resources, prompts" +--- + +MCP analytics gives you visibility into how your MCP proxies and their primitives are being used, directly in Tyk Dashboard. Analytics are organized at two levels: **proxy-level charts** compare traffic and errors across your MCP proxies, giving you a fleet-wide view; **primitive-level charts** break the data down by individual tool, resource, or prompt, showing exactly what agents are calling and where failures are concentrated. Use the filter bar to scope the view to a specific proxy, primitive type, or time window. + +This page is populated by [Tyk Pump](/api-management/tyk-pump#dashboard-analytics-pumps), through one of two paths depending on your deployment topology. In a combined control and data plane, the `mongo-mcp-aggregate`/`sql-mcp-aggregate` pump types feed this page directly; see the [Mongo MCP Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#mongo-mcp-aggregate-pump) and [SQL MCP Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-mcp-aggregate-pump) sections of Control Plane Pumps for installation and configuration. In a distributed deployment with Tyk MDCB, the Hybrid Pump has its own dedicated path; see [MCP Proxy Traffic](/api-management/dashboard-analytics/data-plane-pump#mcp-proxy-traffic) for how to configure it. Either way, the `mongo-mcp`/`sql-mcp` pumps store MCP traffic logs for your own downstream querying, but don't feed this page. + + +To access the MCP analytics page, your Tyk Dashboard user account must have both `analytics` and `mcp` permissions. + + +## Navigating to MCP Analytics + +In the Tyk Dashboard, go to **Monitoring** in the sidebar and select **Activity by MCP**. + +![Activity by MCP](/img/ai-management/activity-by-mcp.png) + +## Filters + +The filter bar at the top of the page applies to all charts simultaneously. Changing a filter refreshes every chart on the page. + +![Filters](/img/ai-management/filters.png) + +| Filter | Description | +|---|---| +| **MCP Proxy** | Restricts all charts to a single MCP proxy. Defaults to all proxies. | +| **Primitive Type** | Filters by primitive type: **Tools**, **Resources**, or **Prompts**. | +| **Primitive Name** | Filters by a specific primitive name within the selected type. The field is searchable. | +| **Resolution** | Sets the time bucket for chart data: **Hourly**, **Daily**, or **Monthly**. | +| **Date range** | Sets the time window for all chart data, in `dd/MM/yyyy` format. | + +When **Primitive Type** is changed, the **Primitive Name** filter resets to show all primitives of the new type. + +## Proxy-Level Charts + +The charts in this section aggregate activity at the proxy level. Use them to compare traffic and error rates across your MCP proxies and identify which ones need attention. + +### Activity per MCP + +A line chart showing hit count over time, with one line per MCP proxy. Use this chart to compare traffic volumes across proxies and identify usage trends. A proxy with disproportionately high traffic is a good candidate for tighter rate limits; a sudden spike may indicate a misbehaving client. + +![Activity per MCP](/img/ai-management/activity-per-mcp.png) + +### Errors by MCP + +A stacked bar chart showing error count over time, with bars stacked by MCP proxy. Use this chart to identify which proxies are generating errors and whether spikes correlate with specific time periods. Persistent errors on one proxy suggest a configuration or upstream issue; a brief, time-bounded burst may indicate a client retry loop or a transient upstream outage. + +![Errors by MCP](/img/ai-management/errors-per-mcp.png) + +## Primitive-Level Charts + +The charts in this section break down activity to the individual primitive level: tools, resources, and prompts. Use them to move from knowing that a proxy has a problem to knowing exactly which tool, resource, or prompt is the cause. + +### Most Used Primitives + +A horizontal stacked bar chart showing total hits ranked by primitive, highest first. Use this chart to identify which tools, resources, or prompts agents call most frequently. High-volume tools are the best candidates for per-tool rate limits and circuit breaker configuration. + +![Most Used Primitives](/img/ai-management/most-used-primitive.png) + +### Most Failing Primitives + +A horizontal stacked bar chart showing total errors ranked by primitive. Use this chart to pinpoint which primitives have the highest failure rates and may require investigation or circuit breaker configuration. + +![Most Failing Primitives](/img/ai-management/most-failing-primitive.png) + +### Slowest Primitives + +A horizontal stacked bar chart showing average latency in seconds, ranked by primitive. Use this chart to identify performance bottlenecks in your upstream MCP server. Consistently slow tools are good candidates for per-tool timeouts, which prevent a single slow tool from stalling an entire agent session. + +![Slowest Primitives](/img/ai-management/slowest-primitive.png) + +### Error Status Codes by Primitive + +A stacked bar chart breaking down HTTP error status codes by primitive. Use this chart to diagnose the type of errors occurring at the primitive level and determine whether failures are originating at the gateway or the upstream MCP server. + +![Error Status Codes by Primitive](/img/ai-management/error-status-code-by-primitive.png) + + +Analytics data is only recorded for MCP proxies that have analytics recording enabled. If charts show no data for a proxy, verify that analytics recording is configured correctly for that proxy. + diff --git a/ai-management/mcp-gateway/mcp-api-extensions.mdx b/ai-management/mcp-gateway/mcp-api-extensions.mdx new file mode 100644 index 0000000000..90a8d51271 --- /dev/null +++ b/ai-management/mcp-gateway/mcp-api-extensions.mdx @@ -0,0 +1,742 @@ +--- +title: "MCP API extensions" +description: "Reference for the MCP proxy management endpoints added to the Tyk Gateway API and Tyk Dashboard API. Covers all CRUD operations, versioning parameters, response shapes, validation behavior, and the differences between the two APIs." +keywords: "MCP, Model Context Protocol, Gateway API, Dashboard API, tyk-apis, MCP proxy, REST API reference, x-tyk-authorization, API extensions" +sidebarTitle: "Gateway & Dashboard API Reference" +--- + +Tyk extends both the Tyk Gateway API and the Tyk Dashboard API with a dedicated set of endpoints for managing MCP OAS definitions. These endpoints are separate from the standard OAS API endpoints. MCP proxies are stored and managed through their own resource paths and are excluded from the standard `/tyk/apis` and `/api/apis` listings. This page documents every endpoint in both APIs: their parameters, request and response shapes, validation behaviour, and the differences between the two interfaces. + + +For the full OpenAPI specifications of the Gateway API and Dashboard API, see [Tyk APIs](/tyk-apis). + + +--- + +## Tyk Gateway API extensions + +The Tyk Gateway API is the admin interface exposed directly by Tyk Gateway. MCP management endpoints are accessible at `/tyk/mcps` and require the gateway's admin secret in the `X-Tyk-Authorization` header. + +Changes made via the Gateway API take effect only after a gateway reload. The MCP endpoints write the proxy definition to disk and return immediately; they do not trigger an automatic reload. + +**Authentication**: `X-Tyk-Authorization: {gateway-secret}` +**Base URL**: `{gateway-host}` (typically `http://localhost:8080`) + +--- + +### List MCP proxies + +Returns all MCP OAS definitions loaded on this gateway instance. Standard Tyk OAS APIs are excluded from this response. + +| Property | Value | +|---|---| +| Method | `GET` | +| URL | `/tyk/mcps` | +| Auth | `X-Tyk-Authorization` | +| Request body | None | +| Query parameters | None | + +**Response**: `200 OK` + +```json +[ + { + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "paths": { ... }, + "x-tyk-api-gateway": { ... } + } +] +``` + +An empty array is returned if no MCP proxies are configured. + +--- + +### Get an MCP proxy + +Returns the full Tyk OAS API definition for a single MCP proxy. + +| Property | Value | +|---|---| +| Method | `GET` | +| URL | `/tyk/mcps/{apiID}` | +| Auth | `X-Tyk-Authorization` | +| Request body | None | +| Query parameters | None | + +**Path parameters** + +| Parameter | Description | +|---|---| +| `apiID` | The API ID of the MCP proxy to retrieve. | + +**Response headers** + +| Header | Description | +|---|---| +| `X-Tyk-Base-API-ID` | Present only when the retrieved proxy is a versioned child. Contains the API ID of the base (parent) proxy. | + +**Response**: `200 OK` + +```json +{ + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "paths": { ... }, + "x-tyk-api-gateway": { + "info": { "id": "weather-mcp-api", "name": "Weather MCP proxy", "state": { "active": true } }, + "server": { "listenPath": { "value": "/weather-mcp/", "strip": true } }, + "upstream": { "url": "https://weather-mcp.example.com" } + } +} +``` + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | The `apiID` value fails path component validation (for example, contains path traversal sequences). | +| `404 Not Found` | No API exists with the given ID, or the API exists but is not an MCP proxy. | + +--- + +### Create an MCP proxy + +Creates a new MCP proxy from a Tyk OAS API definition. The request body must be a valid OpenAPI 3.0.3 document containing an `x-tyk-api-gateway` extension. + +| Property | Value | +|---|---| +| Method | `POST` | +| URL | `/tyk/mcps` | +| Auth | `X-Tyk-Authorization` | +| Content-Type | `application/json` | +| Request body | Tyk OAS MCP definition | + +**Query parameters** + +| Parameter | Required | Description | +|---|---|---| +| `base_api_id` | No | The API ID of an existing MCP proxy to create this definition as a version of. If provided, `version_name` is also required. | +| `version_name` | No | The version identifier for the new proxy (for example, `v2`). Required when `base_api_id` is specified. | +| `set_default` | No | Set to `true` to make the new version the default version of the base proxy. Only applies when `base_api_id` is specified. | + +**Request body**: Minimum viable MCP proxy definition + +```json +{ + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "paths": { + "/mcp": { + "post": { "operationId": "mcpTransportPost", "responses": { "200": { "description": "JSON-RPC response" } } }, + "get": { "operationId": "mcpSSEGet", "responses": { "200": { "description": "SSE stream" } } } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "Weather MCP proxy", + "state": { "active": true } + }, + "server": { + "listenPath": { "value": "/weather-mcp/", "strip": true }, + "authentication": { "enabled": true } + }, + "upstream": { + "url": "https://weather-mcp.example.com" + } + } +} +``` + +If no `id` is provided in `x-tyk-api-gateway.info`, the gateway generates one. + +**Response**: `200 OK` + +```json +{ + "key": "weather-mcp-api", + "status": "ok", + "action": "added" +} +``` + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | Request body is not valid JSON, `x-tyk-api-gateway` extension is missing, the definition fails MCP schema validation, or OAS structure validation fails. | +| `422 Unprocessable Entity` | Versioning parameters are invalid: for example, `base_api_id` refers to an API that does not exist or is not an MCP proxy. | + +--- + +### Update an MCP proxy + +Replaces the definition of an existing MCP proxy in full. Partial updates are not supported; provide the complete Tyk OAS API definition in the request body. + +| Property | Value | +|---|---| +| Method | `PUT` | +| URL | `/tyk/mcps/{apiID}` | +| Auth | `X-Tyk-Authorization` | +| Content-Type | `application/json` | +| Request body | Complete Tyk OAS MCP definition | + +The `x-tyk-api-gateway.info.id` field in the request body must match the `{apiID}` path parameter. A mismatch returns `400 Bad Request`. + +**Response**: `200 OK` + +```json +{ + "key": "weather-mcp-api", + "status": "ok", + "action": "modified" +} +``` + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | Invalid API ID, malformed request body, API ID mismatch between URL and body, the API is not an MCP proxy, or validation fails. | +| `404 Not Found` | No API exists with the given ID. | + +--- + +### Delete an MCP proxy + +Removes an MCP proxy definition from the gateway. + +| Property | Value | +|---|---| +| Method | `DELETE` | +| URL | `/tyk/mcps/{apiID}` | +| Auth | `X-Tyk-Authorization` | +| Request body | None | + +If the deleted proxy is a versioned child, its entry is removed from the base proxy's version map. If it is a base proxy with existing versions, those children remain but lose their parent reference. + +**Response**: `200 OK` + +```json +{ + "key": "weather-mcp-api", + "status": "ok", + "action": "deleted" +} +``` + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | Invalid API ID, or the API exists but is not an MCP proxy. | +| `404 Not Found` | No API exists with the given ID. | +| `500 Internal Server Error` | The definition files could not be deleted from disk. | + +--- + +### Hot reload + +Changes made via the Gateway API are written to disk but are not applied to live traffic until the gateway reloads. After any create, update, or delete operation, issue a reload: + +```bash +# Reload all gateway instances in the group +GET /tyk/reload/group + +# Reload this gateway instance only +GET /tyk/reload +``` + +Both endpoints require the `X-Tyk-Authorization` header. + +--- + +## Tyk Dashboard API extensions + +The Tyk Dashboard API is the management interface exposed by Tyk Dashboard. It proxies operations to connected gateway instances and triggers reloads automatically, so you don't need to issue a manual reload after Dashboard API operations. + +The Dashboard API accepts both JSON and YAML request bodies for create and update operations. + +**Authentication**: Dashboard user API key (retrieved from your user profile in the Dashboard) +**Base URL**: `{dashboard-host}` (typically `http://localhost:3000`) + +--- + +### List MCP proxies + +Returns all MCP proxies managed by this Dashboard instance, with pagination metadata. + +| Property | Value | +|---|---| +| Method | `GET` | +| URL | `/api/mcps` | +| Auth | `Authorization: {dashboard-api-key}` | +| Request body | None | +| Query parameters | None | + +**Response**: `200 OK` + +```json +{ + "mcps": [ + { + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", ... }, + "x-tyk-api-gateway": { ... } + } + ], + "pages": 1 +} +``` + +The `pages` field contains the total number of pages available. + +--- + +### Get an MCP proxy + +Returns the full Tyk OAS API definition for a single MCP proxy. + +| Property | Value | +|---|---| +| Method | `GET` | +| URL | `/api/mcps/{apiId}` | +| Auth | `Authorization: {dashboard-api-key}` | +| Request body | None | + +**Response**: `200 OK` + +Returns the full Tyk OAS API definition. See [Get an MCP proxy: Gateway API](#get-an-mcp-proxy) for the response shape. + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | The API exists but is not an MCP proxy. | +| `404 Not Found` | No API exists with the given ID. | + +--- + +### Create an MCP proxy + +Creates a new MCP proxy from a Tyk OAS API definition. The Dashboard API accepts both JSON and YAML. + +| Property | Value | +|---|---| +| Method | `POST` | +| URL | `/api/mcps` | +| Auth | `Authorization: {dashboard-api-key}` | +| Content-Type | `application/json` or `application/x-yaml` | +| Request body | Tyk OAS MCP definition | + +**Query parameters** + +| Parameter | Required | Description | +|---|---|---| +| `base_api_id` | No | The API ID of an existing MCP proxy to create this definition as a version of. The base API must exist and must itself be an MCP proxy. | + +**Example request** + +```bash +curl -X POST ${DASH_URL}/api/mcps \ + -H "Authorization: ${DASH_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "paths": { + "/mcp": { + "post": { "operationId": "mcpTransportPost", "responses": { "200": { "description": "JSON-RPC response" } } }, + "get": { "operationId": "mcpSSEGet", "responses": { "200": { "description": "SSE stream" } } } + } + }, + "x-tyk-api-gateway": { + "info": { "name": "Weather MCP proxy", "state": { "active": true } }, + "server": { + "listenPath": { "value": "/weather-mcp/", "strip": true }, + "authentication": { "enabled": true } + }, + "upstream": { "url": "https://weather-mcp.example.com" } + } + }' +``` + +**Response**: `200 OK` + +```json +{ + "Status": "OK", + "Message": "API created", + "Meta": "weather-mcp-api-id" +} +``` + +The Dashboard triggers a gateway reload automatically. The proxy is active as soon as the response is returned. + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | Validation failed, `base_api_id` refers to a non-existent or non-MCP proxy, or the definition is structurally invalid. | +| `500 Internal Server Error` | An internal processing error occurred. | + +--- + +### Update an MCP proxy + +Replaces the definition of an existing MCP proxy in full. + +| Property | Value | +|---|---| +| Method | `PUT` | +| URL | `/api/mcps/{apiId}` | +| Auth | `Authorization: {dashboard-api-key}` | +| Content-Type | `application/json` or `application/x-yaml` | +| Request body | Complete Tyk OAS MCP definition | + +**Example request** + +```bash +curl -X PUT ${DASH_URL}/api/mcps/{apiID} \ + -H "Authorization: ${DASH_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "paths": { ... }, + "x-tyk-api-gateway": { + "info": { "id": "{apiID}", "name": "Weather MCP proxy", "state": { "active": true } }, + "server": { + "listenPath": { "value": "/weather-mcp/", "strip": true }, + "authentication": { "enabled": true } + }, + "upstream": { "url": "https://weather-mcp-v2.example.com" } + } + }' +``` + +**Response**: `200 OK` + +```json +{ + "Status": "OK", + "Message": "API updated", + "Meta": "{apiID}" +} +``` + +The Dashboard triggers a gateway reload automatically. + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | The API is not an MCP proxy, or validation fails. | +| `404 Not Found` | No API exists with the given ID. | + +--- + +### Delete an MCP proxy + +Removes an MCP proxy and triggers a gateway reload. + +| Property | Value | +|---|---| +| Method | `DELETE` | +| URL | `/api/mcps/{apiId}` | +| Auth | `Authorization: {dashboard-api-key}` | +| Request body | None | + +**Example request** + +```bash +curl -X DELETE ${DASH_URL}/api/mcps/{apiID} \ + -H "Authorization: ${DASH_KEY}" +``` + +**Response**: `200 OK` + +```json +{ + "Status": "OK", + "Message": "API deleted", + "Meta": "{apiID}" +} +``` + +The Dashboard triggers a gateway reload automatically. + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | The API exists but is not an MCP proxy. | +| `404 Not Found` | No API exists with the given ID. | + +--- + +### List versions of an MCP proxy + +Returns all versioned children of a base MCP proxy. + +| Property | Value | +|---|---| +| Method | `GET` | +| URL | `/api/mcps/{apiId}/versions` | +| Auth | `Authorization: {dashboard-api-key}` | +| Request body | None | + +The `{apiId}` must refer to a base proxy, not a versioned child. Calling this endpoint on a child proxy returns `422 Unprocessable Entity`. + +**Response**: `200 OK` + +```json +{ + "apis": [ + { "apiId": "weather-mcp-v2", "name": "Weather MCP proxy v2" } + ], + "pages": 1 +} +``` + +**Error responses** + +| Status | Condition | +|---|---| +| `400 Bad Request` | The API exists but is not an MCP proxy. | +| `422 Unprocessable Entity` | The API does not exist, is not in OAS format, or is itself a versioned child rather than a base proxy. | + +--- + +### Get the MCP definition schema + +Returns the JSON Schema that Tyk uses to validate MCP OAS definitions. Use this to validate definitions client-side before submitting them to the API. + +| Property | Value | +|---|---| +| Method | `GET` | +| URL | `/api/schemas/apidefs/mcp` | +| Auth | `Authorization: {dashboard-api-key}` | +| Request body | None | + +**Query parameters** + +| Parameter | Required | Description | +|---|---|---| +| `mcpVersion` | No | OAS version to return the schema for. Accepts `3.0` or `3.1`. Defaults to `3.0`. | +| `pretty` | No | Set to `true` to return the schema with indented formatting. | + +**Response**: `200 OK` + +Returns a JSON Schema document describing the valid structure of a Tyk OAS MCP definition. + +--- + +## Policy API extensions + +MCP policies are managed through the standard Tyk policy endpoints; there are no dedicated MCP policy routes. The existing endpoints accept and validate MCP-specific fields inside each `access_rights` entry. Tyk validates that these fields are only used on policies whose access rights target MCP proxies. + +For a conceptual explanation of what the policy fields do and how to configure access tiers, see [MCP proxy policies](/ai-management/mcp-gateway/policies). + +--- + +### Gateway API + +**Authentication**: `X-Tyk-Authorization: {gateway-secret}` + +| Operation | Method | URL | +|---|---|---| +| List policies | `GET` | `/policies` | +| Get a policy | `GET` | `/policies/{polID}` | +| Create a policy | `POST` | `/policies` | +| Update a policy | `PUT` | `/policies/{polID}` | +| Delete a policy | `DELETE` | `/policies/{polID}` | + +**Success responses** + +| Operation | Status | Body | +|---|---|---| +| List / Get | `200 OK` | Policy object or array | +| Create | `200 OK` | `{"key": "{polID}", "status": "ok", "action": "added"}` | +| Update | `200 OK` | `{"key": "{polID}", "status": "ok", "action": "modified"}` | +| Delete | `200 OK` | `{"key": "{polID}", "status": "ok", "action": "deleted"}` | + +--- + +### Dashboard API + +**Authentication**: `Authorization: {dashboard-api-key}` + +| Operation | Method | URL | +|---|---|---| +| List policies | `GET` | `/api/portal/policies` | +| Get a policy | `GET` | `/api/portal/policies/{polID}` | +| Create a policy | `POST` | `/api/portal/policies` | +| Update a policy | `PUT` | `/api/portal/policies/{polID}` | +| Delete a policy | `DELETE` | `/api/portal/policies/{polID}` | + +The Dashboard API triggers a gateway reload automatically on create, update, and delete. The Gateway API requires a manual reload via `/tyk/reload` or `/tyk/reload/group`. + +--- + +### MCP-specific fields in access rights + +The MCP-specific fields sit inside each entry in the `access_rights` object, alongside the standard `limit` and `versions` fields. + +| Field | Type | Description | +|---|---|---| +| `mcp_primitives` | array | Per-primitive rate limits. Each entry targets one named tool, resource, or prompt. | +| `mcp_access_rights` | object | Primitive-level allow/block lists for tools, resources, and prompts. Values support Go regular expressions. | +| `json_rpc_methods` | array | Per-method rate limits. Each entry targets one JSON-RPC method name such as `tools/call`. | +| `json_rpc_methods_access_rights` | object | Method-level allow/block list. Controls which JSON-RPC protocol methods the consumer may use. | + +#### `mcp_primitives` entry + +| Field | Type | Description | +|---|---|---| +| `type` | string | Primitive type: `tool`, `resource`, or `prompt`. | +| `name` | string | Name of the primitive as exposed by the MCP server. Case-sensitive. | +| `limit.rate` | integer | Maximum calls allowed per time window. | +| `limit.per` | integer | Time window in seconds. | + +#### `mcp_access_rights` object + +| Field | Type | Description | +|---|---|---| +| `tools.allowed` | array of strings | Allowlist of tool names. If non-empty, only listed tools are accessible. | +| `tools.blocked` | array of strings | Tools to block. Applied after `allowed`. | +| `resources.allowed` | array of strings | Allowlist of resource URIs. | +| `resources.blocked` | array of strings | Resource URIs to block. | +| `prompts.allowed` | array of strings | Allowlist of prompt names. | +| `prompts.blocked` | array of strings | Prompt names to block. | + +#### `json_rpc_methods` entry + +| Field | Type | Description | +|---|---|---| +| `name` | string | The JSON-RPC method name, for example `tools/call` or `resources/read`. | +| `limit.rate` | integer | Maximum calls using this method per time window. | +| `limit.per` | integer | Time window in seconds. | + +#### `json_rpc_methods_access_rights` object + +| Field | Type | Description | +|---|---|---| +| `allowed` | array of strings | If non-empty, only these methods are permitted. | +| `blocked` | array of strings | Methods to block. Applied after `allowed`. | + +--- + +### Example policy request + +The following creates a policy that restricts a consumer to read-only JSON-RPC methods, limits them to two specific tools, and applies a per-primitive rate limit: + +```json +{ + "name": "Weather Agent — Standard Tier", + "state": "active", + "rate": 1000, + "per": 60, + "quota_max": 50000, + "quota_renewal_rate": 86400, + "access_rights": { + "{mcp-proxy-api-id}": { + "api_id": "{mcp-proxy-api-id}", + "api_name": "Weather MCP Proxy", + "versions": ["Default"], + "limit": { "rate": 200, "per": 60 }, + "json_rpc_methods_access_rights": { + "allowed": ["tools/call", "tools/list", "resources/list", "resources/read"] + }, + "mcp_access_rights": { + "tools": { "allowed": ["get_forecast", "search_weather"] }, + "resources": { "blocked": ["internal://.*"] }, + "prompts": {} + }, + "json_rpc_methods": [ + { "name": "tools/call", "limit": { "rate": 100, "per": 60 } } + ], + "mcp_primitives": [ + { "type": "tool", "name": "get_forecast", "limit": { "rate": 20, "per": 60 } }, + { "type": "resource", "name": "weather://current", "limit": { "rate": 10, "per": 60 } } + ] + } + } +} +``` + +--- + +### Policy validation for MCP + +When a policy is created or updated, Tyk validates the MCP-specific fields: + +- **MCP fields on non-MCP APIs**: if `mcp_primitives`, `mcp_access_rights`, `json_rpc_methods`, or `json_rpc_methods_access_rights` are set against an API ID that is not an MCP proxy, the request is rejected with `400 Bad Request`. +- **REST fields on MCP proxies**: fields specific to REST APIs (`allowed_urls`, `endpoints`, `field_access_rights`) are rejected when set against an MCP proxy ID. +- **Primitive type values**: the `type` field in each `mcp_primitives` entry must be `tool`, `resource`, or `prompt`. Any other value is rejected. + +--- + +## MCP proxy isolation + +MCP proxies are managed separately from standard Tyk OAS APIs throughout the API surface: + +- `GET /tyk/apis` and `GET /api/apis` do not include MCP proxies in their responses. +- `GET /tyk/mcps` and `GET /api/mcps` only return MCP proxies; standard OAS APIs do not appear. +- Create and update operations on `/tyk/mcps` or `/api/mcps` reject definitions that are not valid MCP proxies. +- Get and delete operations on `/tyk/mcps/{id}` or `/api/mcps/{id}` return `404` or `400` if the API ID refers to a non-MCP definition. + +This separation ensures that MCP proxy configuration and standard API configuration cannot be accidentally cross-applied. + +--- + +## Validation + +Both APIs apply the same validation rules when creating or updating an MCP proxy. + +### Required structure + +Every MCP proxy definition must be a valid OpenAPI 3.0.3 document containing an `x-tyk-api-gateway` vendor extension at the root level. Definitions without this extension are rejected with `400 Bad Request`. + +### MCP schema validation + +The definition is validated against Tyk's MCP JSON schema. This schema enforces the structure of the `x-tyk-api-gateway` extension for MCP proxies (including the `mcpTools`, `mcpResources`, and `mcpPrompts` middleware maps) before any further processing. + +### Authentication metadata + +If `x-tyk-api-gateway.server.authentication.securitySchemes[name].oauth2.protectedResourceMetadata` is present and enabled, Tyk validates the PRM configuration. For MCP proxies specifically, at least one entry in `authorizationServers` is required. + +### Middleware restrictions + +Some middleware options are silently ignored when configured on MCP primitives (entries in `mcpTools`, `mcpResources`, or `mcpPrompts`). The schema accepts these fields for forward compatibility, but Tyk does not apply them at runtime: + +| Middleware | Reason | +|---|---| +| `transformRequestMethod` | Changing the HTTP method is not applicable to MCP's fixed POST/GET transport. | +| `transformResponseBody` | MCP responses must be returned unmodified to preserve JSON-RPC 2.0 protocol compliance. | +| `urlRewrite` | URL rewriting conflicts with MCP's single-endpoint transport (`/mcp`). | +| `cache` | Cache keys are derived from the URL path, which is identical for all MCP requests (`/mcp`). | +| `mockResponse` | Mock responses are incompatible with MCP's streaming JSON-RPC transport. | + +Global middleware settings that are also not supported for MCP OAS definitions: + +| Setting | Reason | +|---|---| +| `server.batchProcessing` | Batch HTTP processing is incompatible with the MCP transport model. | +| `middleware.global.cache` | Caching is not supported for MCP APIs. Neither global nor per-primitive `cache` middleware has any effect on MCP traffic. | + +--- + +## Gateway API vs Dashboard API + +| Aspect | Gateway API (`/tyk/mcps`) | Dashboard API (`/api/mcps`) | +|---|---|---| +| **Authentication** | `X-Tyk-Authorization: {secret}` header | `Authorization: {api-key}` header | +| **Content types accepted** | `application/json` | `application/json`, `application/x-yaml` | +| **List response format** | JSON array of OAS definitions | `{"mcps": [...], "pages": N}` | +| **Reload on change** | Manual: must call `/tyk/reload` or `/tyk/reload/group` | Automatic | +| **Versioning parameters** | `base_api_id`, `version_name`, `set_default` | `base_api_id` | +| **Version listing** | Not available | `GET /api/mcps/{apiId}/versions` | +| **Schema endpoint** | Not available | `GET /api/schemas/apidefs/mcp` | +| **Multi-gateway propagation** | Applies to this gateway instance only | Propagates to all connected gateways | diff --git a/ai-management/mcp-gateway/mcp-metrics.mdx b/ai-management/mcp-gateway/mcp-metrics.mdx new file mode 100644 index 0000000000..de22fedacf --- /dev/null +++ b/ai-management/mcp-gateway/mcp-metrics.mdx @@ -0,0 +1,251 @@ +--- +title: "MCP Gateway Metrics" +sidebarTitle: "Metrics" +description: "Monitor MCP proxy traffic with OpenTelemetry custom metrics. Includes MCP-specific dimensions and worked examples for common monitoring use cases." +keywords: "mcp, metrics, opentelemetry, dimensions, tools, primitives, latency, errors" +--- + +When Tyk Gateway proxies MCP traffic, it makes four MCP-specific fields available as `metadata` dimension sources in custom OTel metric instruments. Use these dimensions to monitor tool call volumes, track latency per primitive, classify gateway errors, and correlate usage to individual sessions, all through the same observability infrastructure you use for your REST APIs. + +For an overview of all MCP observability signals, see [MCP observability](/ai-management/mcp-gateway/mcp-observability). + +## MCP fields reference + +The following fields are derived from the JSON-RPC payload and available as `metadata` dimension sources in custom metrics. + +| Field | Description | Example values | +|---|---|---| +| `mcp_method` | JSON-RPC method invoked | `tools/call`, `initialize`, `resources/read`, `prompts/get` | +| `mcp_primitive_type` | MCP primitive category | `tool`, `resource`, `prompt` | +| `mcp_primitive_name` | Name of the specific tool, resource, or prompt | `get_current_weather`, `search_documents` | +| `mcp_error_code` | Gateway-mapped JSON-RPC error code; empty string on success | `-32001` (auth required), `-32002` (access denied), `-32003` (rate limit exceeded), `-32004` (upstream error) | + +All four fields are populated only for MCP APIs. For non-MCP requests they are empty strings, so existing metric instruments are unaffected. + + +`mcp_error_code` reflects errors mapped by the gateway layer (authentication failures, rate limit rejections, and upstream errors), not error codes in the upstream MCP server's JSON-RPC response body. See [MCP access logs](/ai-management/mcp-gateway/mcp-access-logs) for the full list of gateway error codes. + + +## Use cases + +The examples below use Tyk's [custom metrics](/api-management/metrics/custom-metrics) system. Each instrument is a JSON object in the `opentelemetry.metrics.api_metrics` array in `tyk.conf`. + +### MCP traffic volume by method + +Track how many requests each JSON-RPC method receives across all MCP APIs. This shows the distribution of `tools/call`, `initialize`, `resources/read`, and other operations. + +```json +{ + "name": "tyk.mcp.requests.by_method", + "type": "counter", + "description": "Request count broken down by MCP JSON-RPC method", + "dimensions": [ + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] +} +``` + +### Top tools by call volume + +Identify the most frequently invoked tools, useful for cost attribution and optimization. Add `api_id` to compare usage across multiple MCP backends. + +```json +{ + "name": "tyk.mcp.tool_calls.total", + "type": "counter", + "description": "MCP tool invocations by tool name and primitive type", + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_type", "label": "primitive_type", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ], + "filters": { "methods": ["POST"] } +} +``` + +### Tool execution latency: upstream + +Measure how long the upstream MCP server takes to respond per tool. Use this to identify slow tools and performance regressions; this measurement excludes gateway overhead. + +```json +{ + "name": "tyk.mcp.upstream.duration", + "type": "histogram", + "description": "Upstream latency per MCP tool", + "histogram_source": "upstream", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ], + "filters": { "methods": ["POST"] } +} +``` + +### Total request duration with MCP labels + +Measure end-to-end latency (client → gateway → upstream → client) with MCP labels attached. Compare this with upstream latency to quantify gateway overhead. + +```json +{ + "name": "tyk.mcp.request.duration", + "type": "histogram", + "description": "End-to-end latency per MCP tool", + "histogram_source": "total", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] +} +``` + + +The `histogram_source` field accepts three values: `"upstream"` (upstream server latency only), `"total"` (end-to-end including gateway), and `"gateway"` (gateway processing time only, excluding upstream). Use `"gateway"` to isolate gateway overhead. + + +### JSON-RPC error classification + +Count MCP errors by their gateway-mapped JSON-RPC error code. Unlike HTTP errors, MCP errors are typically returned with HTTP 200; `mcp_error_code` is the only reliable way to detect gateway-layer failures. + +```json +{ + "name": "tyk.mcp.errors.by_code", + "type": "counter", + "description": "MCP JSON-RPC errors by error code and tool", + "dimensions": [ + { "source": "metadata", "key": "mcp_error_code", "label": "error_code", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ], + "filters": { "methods": ["POST"] } +} +``` + + +Filter this instrument in your metrics backend to exclude the empty `error_code` value (successful requests), or add a `status_codes` filter if your upstream signals errors via HTTP status. + + +### Per-session tool usage + +Correlate tool call volume to individual MCP sessions using the authenticated session alias. Use this to identify heavy users, debug specific sessions, or allocate costs to teams. + +```json +{ + "name": "tyk.mcp.tool_calls.by_session", + "type": "counter", + "description": "Tool calls per session and tool", + "dimensions": [ + { "source": "session", "key": "alias", "label": "session", "default": "unknown" }, + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ], + "filters": { "methods": ["POST"] } +} +``` + + +`alias` is the human-readable key alias set on the API key or OAuth token. If your sessions are identified differently (for example, via a JWT claim), use the `context` source with the appropriate `jwt_claims_` key instead. + + +### Multi-API MCP backend comparison + +Compare performance across multiple MCP backends by including `api_id` alongside tool dimensions. Use this to determine whether the same tool performs differently on different upstream MCP servers. + +```json +{ + "name": "tyk.mcp.backend.upstream.duration", + "type": "histogram", + "description": "Upstream latency per tool broken down by MCP backend", + "histogram_source": "upstream", + "histogram_buckets": [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] +} +``` + +### Session efficiency + +Measure the ratio of productive `tools/call` operations to session lifecycle calls (`initialize`). A low ratio may indicate clients that open sessions but make few tool calls, useful for detecting misconfigured AI clients or idle connections. + +```json +{ + "name": "tyk.mcp.method.distribution", + "type": "counter", + "description": "Distribution of MCP method types for session efficiency analysis", + "dimensions": [ + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] +} +``` + +To compute efficiency, query your metrics backend for the ratio of `tools/call` to `initialize` counts: + +```promql +# PromQL: ratio of tool calls to session initializations per API +sum by (api_id) (rate(tyk_mcp_method_distribution_total{mcp_method="tools/call"}[5m])) +/ +sum by (api_id) (rate(tyk_mcp_method_distribution_total{mcp_method="initialize"}[5m])) +``` + +## Complete configuration example + +Add the following to `tyk.conf`. This covers all the instruments needed for the [Grafana](https://grafana.com/) dashboard panels: + +```json +{ + "opentelemetry": { + "metrics": { + "enabled": true, + "api_metrics": [ + { + "name": "tyk.mcp.requests.total", + "type": "counter", + "description": "MCP request count by method, tool, API, and session", + "dimensions": [ + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_type", "label": "primitive_type", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_error_code", "label": "error_code", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" }, + { "source": "session", "key": "alias", "label": "session", "default": "unknown" } + ] + }, + { + "name": "tyk.mcp.upstream.duration", + "type": "histogram", + "description": "Upstream MCP server latency per tool and API", + "histogram_source": "upstream", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] + }, + { + "name": "tyk.mcp.request.duration", + "type": "histogram", + "description": "End-to-end MCP request latency per tool and API", + "histogram_source": "total", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_method", "label": "mcp_method", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ] + } + ] + } + } +} +``` + + +The `tyk.mcp.requests.total` counter uses 6 dimensions. Keep the total dimension count at 10 or fewer per instrument to stay on the OTel SDK fast path. See [Performance Considerations](/api-management/logs/access-logs#performance-considerations) for guidance. + + diff --git a/ai-management/mcp-gateway/mcp-middleware.mdx b/ai-management/mcp-gateway/mcp-middleware.mdx new file mode 100644 index 0000000000..e1e983d897 --- /dev/null +++ b/ai-management/mcp-gateway/mcp-middleware.mdx @@ -0,0 +1,566 @@ +--- +title: "MCP middleware" +description: "Apply per-primitive middleware to MCP tools, resources, and prompts in Tyk Gateway. Covers access control, traffic management, request transformation, circuit breakers, timeouts, and observability for MCP traffic." +keywords: "MCP, Model Context Protocol, MCP middleware, MCP tools, MCP resources, MCP prompts, allowlist, rate limiting, circuit breaker, virtual endpoint, JSON-RPC, Tyk OAS" +sidebarTitle: "MCP Middleware" +--- + +Tyk lets you apply middleware to individual MCP primitives (specific tools, resources, and prompts), giving you the same granular control over AI agent traffic that you have over conventional API endpoints. You can rate limit an expensive tool, block a sensitive prompt, set a circuit breaker on an unreliable resource, or apply a custom plugin to inspect tool call arguments before they reach the upstream. This page describes each middleware capability available for MCP proxies, when to use it, and how to configure it. + +> For an explanation of how middleware fits into the overall MCP API definition structure (the three levels, their evaluation order, and how they interact), see [MCP definitions](/ai-management/mcp-gateway/mcp-proxy-definitions). + +--- + +## Configuration paths + +Middleware can be configured in three ways, depending on the level at which it applies and whether you are using the Dashboard UI or editing the definition directly. + +### Dashboard: Settings tab (proxy level) + +The **Settings** tab on the MCP Designer exposes middleware that applies to all requests through the proxy, regardless of which primitive is invoked. These options map to `x-tyk-api-gateway.middleware.global` in the definition: + +| Middleware | Maps to | +|---|---| +| CORS | `middleware.global.cors` | +| Transform Request Headers | `middleware.global.transformRequestHeaders` | +| Transform Response Headers | `middleware.global.transformResponseHeaders` | +| Context Variables | `middleware.global.contextVariables` | +| Traffic Logs | `middleware.global.trafficLogs` | +| Plugin Config / Bundle | `middleware.global.pluginConfig` | + +### Dashboard: Primitives tab (primitive level) + +The **Primitives** tab lets you add middleware to individual tools, resources, and prompts. Click a primitive and then **Add Middleware**. These options map to the primitive's entry in `mcpTools`, `mcpResources`, or `mcpPrompts`. See [Managing MCP proxies using the Dashboard](/ai-management/mcp-gateway/managing-proxies#primitives-tab) for the full UI walkthrough. + +| Middleware | Maps to | +|---|---| +| Allow, Block | `allow`, `block` | +| Ignore Authentication | `ignoreAuthentication` | +| Rate Limit | `rateLimit` | +| Request Size Limit | `requestSizeLimit` | +| Circuit Breaker | `circuitBreaker` | +| Transform Request Headers | `transformRequestHeaders` | +| Transform Response Headers | `transformResponseHeaders` | +| Virtual Endpoint | `virtualEndpoint` | +| Scope check | `scopeCheck` | +| Token exchange | `exchange` | +| Track Endpoint, Do Not Track Endpoint | `trackEndpoint`, `doNotTrackEndpoint` | +| Post Plugins | `postPlugins` | + +### MCP definition (all levels) + +All middleware options (including those not exposed in the Dashboard) can be configured by editing the proxy definition directly. Open the definition editor via **Actions → View MCP Proxy Definition** in the Dashboard, or submit the definition via the API. The following options are only available this way: + +- `transformRequestBody`: Request body transformation (supported) +- `urlRewrite`, `transformRequestMethod`: Accepted by the schema but silently ignored at runtime; they have no effect on MCP primitives. See the warnings in the [request transformation](#request-transformation) section below. +- `middleware.operations`: Method-level middleware applying to all calls of a given JSON-RPC method (for example, all `tools/call` requests), evaluated before primitive-level middleware + + +Caching and mock responses are not supported for MCP primitives. + + +The sections below document every option with definition examples. All definition examples apply to the `mcpTools`, `mcpResources`, or `mcpPrompts` maps unless otherwise noted. + +--- + +## How primitive middleware is resolved + +Middleware for MCP primitives is configured in `x-tyk-api-gateway.middleware` inside one of three maps: + +- `mcpTools`: Keyed by tool name (the `params.name` value in a `tools/call` request) +- `mcpResources`: Keyed by resource URI or URI wildcard pattern (the `params.uri` value in a `resources/read` request) +- `mcpPrompts`: Keyed by prompt name (the `params.name` value in a `prompts/get` request) + +When Tyk receives a `tools/call`, `resources/read`, or `prompts/get` request, it extracts the primitive identifier from the JSON-RPC body, finds the matching entry in the relevant map, and executes the configured middleware before proxying to the upstream. + +The `middleware.operations` map applies middleware at the JSON-RPC method level rather than the individual primitive level. Method-level middleware evaluates before primitive-level middleware. + +--- + +## Access control + +Access control middleware determines which primitives a client is allowed to invoke. It is the most commonly configured middleware for MCP proxies, because MCP servers typically expose more capabilities than you want to make available through the gateway. + +### allow + +The `allow` middleware adds a primitive to an explicit allowlist. When any primitive within a category has `allow` enabled, Tyk switches that entire category into **allowlist mode**: only the explicitly listed primitives are accessible, and all others are rejected with a JSON-RPC error. + +```json +{ + "middleware": { + "mcpTools": { + "get-weather": { + "allow": { "enabled": true } + }, + "get-forecast": { + "allow": { "enabled": true } + } + } + } +} +``` + +In this example, only `get-weather` and `get-forecast` are accessible. A request to any other tool name returns an error without reaching the upstream. Resources and prompts are unaffected; the three categories are evaluated independently, so allowlisting tools does not restrict resource or prompt access. + +Allowlist mode is the recommended approach when you have a known set of primitives to expose. It ensures that new tools added to the upstream MCP server are not automatically accessible through the gateway; you must explicitly add them to the allowlist. + +### block + +The `block` middleware explicitly denies access to a primitive. Tyk returns a JSON-RPC error without forwarding the request to the upstream. Use `block` when all primitives should be accessible by default except a specific subset. + +```json +{ + "middleware": { + "mcpTools": { + "admin-reset": { + "block": { "enabled": true } + } + } + } +} +``` + +If no `allow` rules exist in the category, all primitives are accessible by default. Block rules apply on top of this open default. If `allow` rules are also present, block rules take precedence. + +### ignoreAuthentication + +The `ignoreAuthentication` middleware exempts a primitive from the API's authentication checks, allowing unauthenticated access to that specific primitive while the rest of the API remains protected. + +```json +{ + "middleware": { + "mcpTools": { + "server-status": { + "ignoreAuthentication": { "enabled": true } + } + } + } +} +``` + +Use this for primitives that need to be publicly accessible (health checks, capability discovery, or public reference data) without requiring a separate unauthenticated API definition. + +### scopeCheck + +The `scopeCheck` middleware enforces OAuth 2.0 scope requirements for a specific primitive. When enabled, Tyk extracts the scopes from the inbound token and checks them against the `security:` declarations for that primitive. Requests where the token does not carry the required scopes are rejected with a `403 Forbidden` and an RFC 6750 `WWW-Authenticate` header describing the missing scopes. + +```json +{ + "middleware": { + "mcpTools": { + "read-customer": { + "scopeCheck": { "enabled": true } + } + } + } +} +``` + +Scope enforcement for the primitive is governed by two things working together: + +- The `security:` field on the primitive in the [Tyk vendor extension](/api-management/gateway-config-tyk-oas) — this names which scopes the token must carry. MCP primitives have no OAS path entry, so `security:` is declared in `x-tyk-api-gateway.middleware.mcpTools` (or `mcpResources` / `mcpPrompts`) alongside the other per-primitive middleware: + + ```json + { + "middleware": { + "mcpTools": { + "read-customer": { + "security": [{ "idpAuth": ["tools:read"] }], + "scopeCheck": { "enabled": true } + } + } + } + } + ``` + +- The API-level `oauth2.scopeCheck` configuration — `claimNames`, `scopeSource`, and `separator` control how Tyk reads scopes out of the inbound token. + +Setting `enabled: true` activates scope enforcement for this primitive only. Other primitives are unaffected unless they also declare `scopeCheck: { "enabled": true }`. Omitting the block or setting `enabled: false` means this primitive's own `security:` declaration does not contribute to scope enforcement. The API-level root `security:` array may still apply depending on `scopeSource` — when `scopeSource` is `"union"` (the default) or `"global"`, root security requirements are enforced regardless. + +`scopeCheck` requires the `oauth2` security scheme to be configured on the API. It has no effect when `externalOAuthServer` or other authentication schemes are in use. For scheme setup and scope configuration details, see [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication). + +--- + +## Upstream authentication + +### exchange + +The `exchange` middleware overrides the token exchange provider's defaults for a specific primitive. When active, Tyk requests a new exchange token scoped to this primitive's `audience` and `scopes`, rather than using the API-level provider's `defaultTarget`. + +Token exchange is Enterprise Edition only and requires a provider to be configured at the API level under `oauth2.tokenExchange.providers`. For provider setup and caching options, see [Token exchange](/api-management/authentication/token-exchange). + +```json +{ + "middleware": { + "mcpTools": { + "write-record": { + "exchange": { + "enabled": true, + "audience": "https://storage.example.com", + "scopes": ["storage.write"] + } + } + } + } +} +``` + +- **`enabled`**: Must be explicitly `true` to activate the per-primitive override. Omitting this field or setting it to `false` leaves the API-level provider's `defaultTarget` in effect for this primitive. +- **`audience`**: The resource or audience parameter sent to the token endpoint, overriding `provider.defaultTarget` for this primitive. +- **`scopes`**: The scopes to request in the exchange. When omitted or empty while `enabled` is `true`, Tyk uses the scopes listed in the primitive's `security:` field in the [Tyk vendor extension](/api-management/gateway-config-tyk-oas) — the same field used by `scopeCheck` — as the scopes to request from the token endpoint. This avoids repeating the scope list in two places. + +When no `exchange` block is present on a primitive, the provider's `defaultTarget` and `scopes` apply uniformly to all primitives. + +--- + +## Traffic management + +Traffic management middleware protects your upstream MCP server from overload and controls the quality of service for individual primitives. + +### rateLimit + +The `rateLimit` middleware applies a rate limit to a specific primitive, counted separately from any method-level or MCP server-level rate limits. Use it when different tools have meaningfully different costs: for example, a resource-intensive tool should have a tighter limit than one that reads static metadata. + +```json +{ + "middleware": { + "mcpTools": { + "execute-query": { + "rateLimit": { + "enabled": true, + "rate": 10, + "per": 60 + } + }, + "list-tables": { + "rateLimit": { + "enabled": true, + "rate": 200, + "per": 60 + } + } + } + } +} +``` + +The `rate` field is the maximum number of requests allowed in the time window specified by `per` (in seconds, or as a shorthand string such as `"1m"` or `"30s"`). Primitive-level rate limits are additive with method-level limits; a request must pass both. + +Rate limits configured in `mcpTools`, `mcpResources`, and `mcpPrompts` apply to all consumers of the proxy; they are shared ceilings. For per-consumer primitive rate limits that track each consumer key independently, use the `mcp_primitives` field in a security policy. Both can be active simultaneously: the middleware limit protects the upstream from aggregate overload; the policy limit enforces each consumer's individual entitlement. See [MCP proxy policies](/ai-management/mcp-gateway/policies). + +### requestSizeLimit + +The `requestSizeLimit` middleware restricts the maximum size of the JSON-RPC request body for a specific primitive. Use it for tools that accept large argument payloads, where oversized requests could cause performance problems upstream. + +```json +{ + "middleware": { + "mcpTools": { + "process-document": { + "requestSizeLimit": { + "enabled": true, + "value": 65536 + } + } + } + } +} +``` + +The `value` field is in bytes. Requests that exceed the limit are rejected before reaching the upstream. + +### circuitBreaker + +The `circuitBreaker` middleware monitors the failure rate for a specific primitive and temporarily stops forwarding requests when the error rate exceeds a threshold. This protects the upstream from cascading failures and gives it time to recover. + +```json +{ + "middleware": { + "mcpTools": { + "search-index": { + "circuitBreaker": { + "enabled": true, + "threshold": 0.5, + "sampleSize": 20, + "coolDownPeriod": 30, + "halfOpenStateEnabled": true + } + } + } + } +} +``` + +- **`threshold`**: The ratio of failed requests (0.0 to 1.0) that trips the breaker. `0.5` means the breaker trips when more than 50% of recent requests fail. +- **`sampleSize`**: The number of requests in the evaluation window. The threshold is checked after each window. +- **`coolDownPeriod`**: Seconds to wait with the breaker open before attempting recovery. +- **`halfOpenStateEnabled`**: When `true`, allows a small number of probe requests through during the cool-down period to test whether the upstream has recovered, rather than waiting for the full period to elapse before reopening. + +Circuit breakers are most valuable for tools that call external services or perform expensive operations where failure is likely to persist, rather than being transient. + +### cache + + +The `cache` field is not supported for MCP primitives. Setting it has no effect. See the capability matrix at the end of this page. + + +The `cache` field is accepted in the configuration schema but does not activate caching for MCP primitives. Any `cache` configuration you set on an entry in `mcpTools`, `mcpResources`, or `mcpPrompts` is silently ignored at runtime. The limitation is architectural: Tyk's cache middleware derives cache keys from the HTTP URL path, which is the same for every MCP request (`/mcp`). It does not inspect the JSON-RPC request body, so it cannot distinguish a `resources/read` call for `weather://current` from a `tools/call` to `get_forecast`; they resolve to the same cache key. Caching MCP traffic meaningfully would require cache key derivation from the JSON-RPC method and primitive identifier, which is not currently implemented. + +--- + +## Request transformation + +Request transformation middleware modifies requests before they reach the upstream MCP server. You can add or remove headers, rewrite the upstream URL, or replace the request body. + +### transformRequestHeaders + +The `transformRequestHeaders` middleware adds, removes, or modifies HTTP headers on the request forwarded to the upstream. + +```json +{ + "middleware": { + "mcpTools": { + "query-database": { + "transformRequestHeaders": { + "enabled": true, + "add": [ + { "name": "X-Caller-Id", "value": "$tyk_context.request_id" }, + { "name": "X-Tool-Name", "value": "query-database" } + ], + "remove": ["X-Internal-Debug"] + } + } + } + } +} +``` + +Use this to inject authentication credentials your upstream expects, add tracing identifiers, or strip headers that should not reach the upstream. + +### transformRequestBody + +The `transformRequestBody` middleware transforms the JSON-RPC request body before it is forwarded to the upstream, using a Go template. This allows you to reshape the request, for example to translate between different argument schemas or to inject values from the gateway context. + +```json +{ + "middleware": { + "mcpTools": { + "search": { + "transformRequestBody": { + "enabled": true, + "format": "json", + "body": "" + } + } + } + } +} +``` + +The template receives the full JSON-RPC request as input and must produce a valid JSON-RPC 2.0 request as output to maintain protocol compliance with the upstream. + +### urlRewrite + + +The `urlRewrite` field is not supported for MCP primitives. Setting it has no effect. The field is accepted by the configuration schema for forward compatibility but is silently ignored at runtime. URL rewriting is incompatible with MCP's single-endpoint transport; all traffic flows through `/mcp` regardless of which primitive is invoked. + + +### transformRequestMethod + + +The `transformRequestMethod` field is not supported for MCP primitives. Setting it has no effect. The field is accepted by the configuration schema for forward compatibility but is silently ignored at runtime. MCP always uses `POST` for JSON-RPC messages and `GET` for SSE streams; the HTTP method cannot be changed at the primitive level. + + +--- + +## Response transformation + +### transformResponseHeaders + +The `transformResponseHeaders` middleware adds, removes, or modifies HTTP headers on the response returned to the client. + +```json +{ + "middleware": { + "mcpResources": { + "public://data/*": { + "transformResponseHeaders": { + "enabled": true, + "add": [ + { "name": "Cache-Control", "value": "public, max-age=300" } + ] + } + } + } + } +} +``` + + +Response body transformation (`transformResponseBody`) is not available for MCP primitives. MCP responses are JSON-RPC 2.0 messages and must be returned to the client unmodified to maintain protocol compliance. Response body transformation remains available for entries in `middleware.operations` if you need to transform at the method level, but the same protocol compliance constraint applies, so use with care. + + +--- + +## Testing and development + +### mockResponse + + +The `mockResponse` field is not supported for MCP primitives. Setting it has no effect. Mock responses require Tyk to construct a complete HTTP response body before it reaches the client, which is incompatible with the streaming JSON-RPC transport MCP uses. See the capability matrix at the end of this page. + + +The `mockResponse` field is accepted in the configuration schema but does not intercept or replace responses for MCP primitives. + +### virtualEndpoint + +The `virtualEndpoint` middleware executes a JavaScript function in place of the upstream proxy. Use it to implement simple primitives directly in the gateway, for example a tool that aggregates data from a Tyk context variable, performs a simple calculation, or returns a dynamically constructed response without needing a dedicated upstream service. + +```json +{ + "middleware": { + "mcpTools": { + "echo": { + "virtualEndpoint": { + "enabled": true, + "functionName": "echoTool", + "body": "", + "proxyOnError": false + } + } + } + } +} +``` + +The JavaScript function receives the request object and must return a response object in JSON-RPC 2.0 format. Set `proxyOnError` to `true` to fall back to the upstream if the virtual endpoint function throws an error. + +--- + +## Observability + +### trackEndpoint and doNotTrackEndpoint + +By default, Tyk records analytics for all requests. These two middleware options let you override tracking behavior at the primitive level. + +`trackEndpoint` enables detailed analytics for a primitive that might otherwise be excluded: + +```json +{ + "middleware": { + "mcpTools": { + "execute-query": { + "trackEndpoint": { "enabled": true } + } + } + } +} +``` + +`doNotTrackEndpoint` excludes a primitive from analytics logs and dashboards. Use this for high-volume or sensitive primitives where capturing every request creates noise or a compliance concern: + +```json +{ + "middleware": { + "mcpTools": { + "health-check": { + "doNotTrackEndpoint": { "enabled": true } + } + } + } +} +``` + +--- + +## Plugins + +### postPlugins + +The `postPlugins` field executes one or more custom plugin functions after the main middleware chain completes, immediately before the request is proxied to the upstream. Use custom plugins when the built-in middleware capabilities are insufficient, for example to validate request arguments against an external schema, to enrich the request with data from a third-party service, or to implement custom access control logic. + +```json +{ + "middleware": { + "mcpTools": { + "execute-query": { + "postPlugins": [ + { + "enabled": true, + "functionName": "validateQuerySchema", + "path": "/opt/tyk/plugins/query-validator.so" + } + ] + } + } + } +} +``` + +Plugins are loaded from the path specified or from a bundle configured in the global `pluginConfig` section. See [Custom plugins](/api-management/plugins/overview) for the full plugin development guide. + +--- + +## Resource URI matching + +Resources in the `mcpResources` map are matched against the `params.uri` value in incoming `resources/read` requests. Tyk resolves the match in this order: + +1. **Exact match**: If the request URI matches a key exactly, that entry's middleware is applied. +2. **Wildcard match**: If no exact match is found, Tyk checks for entries containing `*`. When multiple patterns match, the longest prefix wins. +3. **No match**: If neither an exact nor wildcard match is found, the request is handled by the default middleware chain (all primitives are accessible unless the category is in allowlist mode). + +For example, given these entries: + +```json +{ + "mcpResources": { + "file:///config/database.json": { + "cache": { "enabled": true, "timeout": 3600 } + }, + "file:///config/*": { + "cache": { "enabled": true, "timeout": 300 } + }, + "file:///*": { + "allow": { "enabled": true } + } + } +} +``` + +A request for `file:///config/database.json` matches the exact entry and gets a 1-hour cache. A request for `file:///config/settings.json` matches the `file:///config/*` pattern and gets a 5-minute cache. A request for `file:///logs/app.log` matches the `file:///*` fallback and is allowed but not cached. + +--- + +## Middleware capability reference + +The following table summarises every middleware capability available for MCP proxies and where it can be configured. + +| Middleware | Field | Dashboard: Settings tab | Dashboard: Primitives tab | Definition | +|---|---|---|---|---| +| Allowlist | `allow` | — | Yes | Yes | +| Blocklist | `block` | — | Yes | Yes | +| Ignore authentication | `ignoreAuthentication` | — | Yes | Yes | +| Scope check | `scopeCheck` | — | Yes | Yes | +| Token exchange (per-primitive) | `exchange` | — | Yes | Yes | +| Rate limiting | `rateLimit` | — | Yes | Yes | +| Request size limit | `requestSizeLimit` | — | Yes | Yes | +| Circuit breaker | `circuitBreaker` | — | Yes | Yes | +| Cache | `cache` | — | — | No, disabled for protocol compliance | +| Transform request headers (global) | `middleware.global.transformRequestHeaders` | Yes | — | Yes | +| Transform request headers (primitive) | `transformRequestHeaders` | — | Yes | Yes | +| Transform request body | `transformRequestBody` | — | — | Yes | +| URL rewrite | `urlRewrite` | — | — | No, silently ignored for protocol compliance | +| Transform request method | `transformRequestMethod` | — | — | No, silently ignored for protocol compliance | +| Transform response headers (global) | `middleware.global.transformResponseHeaders` | Yes | — | Yes | +| Transform response headers (primitive) | `transformResponseHeaders` | — | Yes | Yes | +| Transform response body | `transformResponseBody` | — | — | No, disabled for protocol compliance | +| Mock response | `mockResponse` | — | — | No, disabled for protocol compliance | +| Virtual endpoint | `virtualEndpoint` | — | Yes | Yes | +| Track endpoint | `trackEndpoint` | — | Yes | Yes | +| Do not track | `doNotTrackEndpoint` | — | Yes | Yes | +| Post plugins | `postPlugins` | — | Yes | Yes | +| CORS | `middleware.global.cors` | Yes | — | Yes | +| Context variables | `middleware.global.contextVariables` | Yes | — | Yes | +| Traffic logs | `middleware.global.trafficLogs` | Yes | — | Yes | +| Plugin config / bundle | `middleware.global.pluginConfig` | Yes | — | Yes | + diff --git a/ai-management/mcp-gateway/mcp-observability.mdx b/ai-management/mcp-gateway/mcp-observability.mdx new file mode 100644 index 0000000000..82057d0810 --- /dev/null +++ b/ai-management/mcp-gateway/mcp-observability.mdx @@ -0,0 +1,42 @@ +--- +title: "MCP Gateway Observability Overview" +sidebarTitle: "Overview" +description: "An overview of the observability signals Tyk Gateway emits for MCP traffic: metrics dimensions, structured access log fields, and the Dashboard analytics page." +keywords: "mcp, observability, opentelemetry, metrics, access logs, analytics, monitoring" +--- + +Tyk Gateway emits two categories of observability signal for MCP traffic: custom metrics dimensions and structured access log fields. Both signals are enriched with the same set of MCP-specific fields, letting you monitor tool call volumes, track latency per primitive, classify errors, and correlate usage across sessions, through the same observability infrastructure you use for your REST APIs. + +## Prerequisites + +OpenTelemetry must be enabled on your Tyk Gateway. See [OpenTelemetry configuration](/api-management/traces) for setup instructions. + +## MCP fields + +The following fields are derived from the JSON-RPC payload on each MCP request. They appear across both signal types. + +| Field | Description | Example values | +|---|---|---| +| `mcp_method` | JSON-RPC method invoked | `tools/call`, `initialize`, `resources/read`, `prompts/get` | +| `mcp_primitive_type` | MCP primitive category | `tool`, `resource`, `prompt` | +| `mcp_primitive_name` | Name of the specific tool, resource, or prompt | `get_current_weather`, `search_documents` | +| `mcp_error_code` | Gateway-mapped JSON-RPC error code on failure; absent or empty on success | `-32001`, `-32002`, `-32003` | + +All four fields are populated only for MCP requests. For non-MCP requests the fields are empty or absent, so existing metric instruments and log templates are unaffected. + +## Observability Signals + +| Signal | What it covers | Doc | +|---|---|---| +| **Metrics** | Custom OTel metric instruments with MCP dimensions for counters and histograms | [MCP metrics](/ai-management/mcp-gateway/mcp-metrics) | +| **Access logs** | Structured per-request log records with MCP fields included when non-empty | [MCP access logs](/ai-management/mcp-gateway/mcp-access-logs) | + + +Distributed tracing is not currently implemented for MCP traffic. The `TRACING_ENABLED` configuration flag exists in the gateway but does not generate spans for MCP requests. Use the metrics and access log signals above to correlate and diagnose MCP traffic. + + +## Dashboard Analytics + +Alongside these OpenTelemetry signals, Tyk Dashboard has a dedicated **Activity by MCP** analytics page, covering proxy-level and primitive-level traffic and error charts. See [MCP Analytics](/ai-management/mcp-gateway/mcp-analytics). + + diff --git a/ai-management/mcp-gateway/mcp-proxy-definitions.mdx b/ai-management/mcp-gateway/mcp-proxy-definitions.mdx new file mode 100644 index 0000000000..dc7c63141b --- /dev/null +++ b/ai-management/mcp-gateway/mcp-proxy-definitions.mdx @@ -0,0 +1,507 @@ +--- +title: "MCP OAS definition" +description: "How Tyk represents MCP servers as OpenAPI definitions: the OpenAPI specification structure, MCP primitives and JSON-RPC methods, and the x-tyk-api-gateway extensions that configure gateway behavior for MCP traffic." +keywords: "MCP, Model Context Protocol, MCP API definition, x-tyk-api-gateway, JSON-RPC, primitives, tools, resources, prompts, operations, Tyk OAS, Streamable HTTP" +sidebarTitle: "MCP Proxy Definition" +--- + +An MCP proxy definition is the configuration object that tells Tyk how to proxy an MCP server. It is built on the **[Tyk OAS API definition](/api-management/gateway-config-tyk-oas)** format (an OpenAPI 3.0 document extended with the `x-tyk-api-gateway` vendor extension) and adds the MCP-specific structures that allow Tyk to inspect JSON-RPC traffic and apply middleware at the method and primitive level. This page explains the structure of an MCP API definition, how MCP concepts (primitives, methods, operations) map to it, and which parts of the extension are specific to MCP. + +> If you are not familiar with Tyk OAS API definitions, read [Tyk OAS](/api-management/gateway-config-tyk-oas) first. This page focuses on the MCP-specific aspects and assumes knowledge of the base format. + +--- + +## Structure overview + +An MCP API definition has two parts that work together: + +- **The OpenAPI specification**: Describes the MCP server's transport endpoints and, optionally, each JSON-RPC method as a documented operation. Tyk uses this to understand the API's shape and to present it in the Developer Portal. +- **The `x-tyk-api-gateway` extension**: Contains all gateway configuration: the listen path, upstream URL, authentication, and the middleware maps that govern individual tools, resources, and prompts. + +The two parts share the same file or API object: + +```json +{ + "openapi": "3.0.3", + "info": { + "title": "Weather MCP proxy", + "version": "2025-11-25" + }, + "paths": { ... }, + "x-tyk-api-gateway": { + "info": { ... }, + "server": { ... }, + "upstream": { ... }, + "middleware": { ... } + } +} +``` + +The sections below explain each part and the MCP-specific patterns within them. + +--- + +## MCP concepts in the definition + +Three core MCP concepts shape how you write an MCP API definition: primitives, JSON-RPC methods, and transport endpoints. Understanding them before reading the definition structure makes the configuration decisions much clearer. + +### Primitives + +Primitives are the capabilities an MCP server exposes. The MCP specification defines three categories: + +- **Tools**: Actions an AI agent can invoke. Each tool has a name, an input schema, and returns a result. For example, a `get-weather` tool that accepts a location and returns a forecast. +- **Resources**: Data an AI agent can read. Each resource is identified by a URI (for example, `weather://stations/london`). Resources support URI wildcard patterns for template-based access (for example, `weather://stations/*`). +- **Prompts**: Pre-written instruction templates that shape LLM behavior. Each prompt has a name and accepts arguments that customise the generated content. + +Primitives are the level at which Tyk applies fine-grained middleware. You can rate limit a specific tool, cache a specific resource, or block a specific prompt, independently of every other primitive on the same server. In the `x-tyk-api-gateway` extension, each primitive category has its own middleware map: `mcpTools`, `mcpResources`, and `mcpPrompts`. + +### JSON-RPC methods + +MCP clients communicate with servers by sending JSON-RPC 2.0 requests. Every request carries a `method` field that identifies the operation: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "get-weather", + "arguments": { "location": "London" } + } +} +``` + +Methods are organised into namespaces that correspond to MCP capabilities: + +| Namespace | Methods | Purpose | +|---|---|---| +| Lifecycle | `initialize`, `ping` | Session establishment and health checks | +| Tools | `tools/list`, `tools/call` | Tool discovery and invocation | +| Resources | `resources/list`, `resources/read`, `resources/templates/list`, `resources/subscribe`, `resources/unsubscribe` | Resource discovery, reading, and subscriptions | +| Prompts | `prompts/list`, `prompts/get` | Prompt discovery and retrieval | +| Completions | `completion/complete` | Argument autocompletion | +| Logging | `logging/setLevel` | Server log level control | + +In the `x-tyk-api-gateway` extension, you can configure middleware at the method level (applying a rule to every `tools/call` request regardless of which tool is named) using the `middleware.operations` map. + +### Transport endpoints + +All MCP traffic flows through a single path (`/mcp`) that supports two HTTP methods: + +- **`POST /mcp`**: Clients send JSON-RPC messages. The server responds with either a single JSON object or a Server-Sent Events stream. +- **`GET /mcp`**: Clients open a persistent SSE connection for server-initiated messages. + +These two transport endpoints are the only real HTTP endpoints your MCP server needs to expose. In the OpenAPI specification portion of the definition, they are documented as `POST /mcp` and `GET /mcp`. Tyk proxies both. + +--- + +## The OpenAPI specification portion + +The OpenAPI specification in an MCP API definition documents the server's HTTP interface. For MCP, this has a standard structure that you can treat as a template. + +### Transport paths + +At minimum, the `paths` object documents the two transport endpoints: + +```json +{ + "paths": { + "/mcp": { + "post": { + "summary": "Send a JSON-RPC message", + "operationId": "mcpTransportPost", + "parameters": [ + { + "name": "MCP-Protocol-Version", + "in": "header", + "required": true, + "schema": { "type": "string", "example": "2025-11-25" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/JSONRPCRequest" } + } + } + }, + "responses": { + "200": { + "description": "JSON-RPC response or SSE stream", + "content": { + "application/json": { "schema": { "$ref": "#/components/schemas/JSONRPCResponse" } }, + "text/event-stream": { "schema": { "type": "string" } } + } + }, + "202": { "description": "Accepted (notification, no response body)" } + } + }, + "get": { + "summary": "Open an SSE stream", + "operationId": "mcpSSEGet", + "responses": { + "200": { + "description": "Server-sent events stream", + "content": { + "text/event-stream": { "schema": { "type": "string" } } + } + } + } + } + } + } +} +``` + +The `operationId` values (`mcpTransportPost`, `mcpSSEGet`) are referenced internally by Tyk for the transport endpoints. These values are fixed; do not change them. + +### Method paths + +In addition to the transport paths, you can document each JSON-RPC method as a separate path. This is optional for gateway operation but makes the API browsable in the Tyk Developer Portal: + +```json +{ + "paths": { + "/mcp/tools/call": { + "post": { + "summary": "Invoke a tool", + "operationId": "tools/callPOST", + "x-mcp-method": "tools/call", + ... + } + }, + "/mcp/resources/read": { + "post": { + "summary": "Read a resource", + "operationId": "resources/readPOST", + "x-mcp-method": "resources/read", + ... + } + } + } +} +``` + +These paths are a documentation and governance interface. They present each JSON-RPC method as a distinct, typed operation, making MCP proxies discoverable alongside REST and GraphQL APIs in your Developer Portal. The path structure (`/mcp/{namespace}/{action}`) and the `operationId` convention (`{method}POST`) are what tie the OpenAPI spec to the `middleware.operations` map in the `x-tyk-api-gateway` extension. + + +The method paths do not correspond to real HTTP endpoints. All traffic still flows through `POST /mcp`. The method paths exist solely for documentation, schema validation, and middleware configuration purposes. + + +--- + +## The x-tyk-api-gateway extension + +The `x-tyk-api-gateway` extension contains all gateway configuration. For MCP proxies, it has the same four top-level sections as any Tyk OAS API definition, with MCP-specific content in the `middleware` section. + +### info, server, and upstream + +The `info`, `server`, and `upstream` sections work identically to a standard Tyk OAS API definition. They configure the API's identity, its client-facing interface, and its upstream connectivity: + +```json +{ + "x-tyk-api-gateway": { + "info": { + "name": "Weather MCP proxy", + "state": { "active": true } + }, + "server": { + "listenPath": { "value": "/weather/", "strip": true }, + "authentication": { + "enabled": true, + "securitySchemes": { + "bearerAuth": { "enabled": true } + } + } + }, + "upstream": { + "url": "https://weather-mcp.example.com" + } + } +} +``` + +There are no MCP-specific fields in these three sections. You configure authentication, TLS, load balancing, upstream rate limits, and all other standard gateway capabilities exactly as you would for a REST API. See [Tyk OAS](/api-management/gateway-config-tyk-oas) for the full field reference for these sections. + +The example above uses `bearerAuth` as the security scheme — a simple bearer token check. For full OAuth 2.1 compliance (token validation, scope enforcement, Protected Resource Metadata, and token exchange), use the `oauth2` scheme instead. See [MCP OAuth 2.1](/ai-management/mcp-gateway/oauth-2-1). + +### The middleware section + +The `middleware` section is where MCP OAS definitions diverge from standard Tyk OAS API definitions. It contains the same `global` block for API-wide middleware, but adds two new concepts: an `operations` map keyed by JSON-RPC method, and three primitive maps: `mcpTools`, `mcpResources`, and `mcpPrompts`. + +```json +{ + "middleware": { + "global": { ... }, + "operations": { ... }, + "mcpTools": { ... }, + "mcpResources": { ... }, + "mcpPrompts": { ... } + } +} +``` + +Each is explained below. + +#### global + +Global middleware applies to every request on the API. It is configured identically to a standard Tyk OAS API (CORS, traffic logs, header transformations, custom plugins, and so on). There is nothing MCP-specific here. + +#### operations: method-level middleware + +The `operations` map lets you configure middleware that applies to every invocation of a JSON-RPC method, regardless of which specific primitive is targeted. It is keyed by the operation ID of the method path in the OpenAPI spec, which follows the convention `{json-rpc-method}{HTTP-method}`: + +| JSON-RPC method | Operation ID key | +|---|---| +| `tools/call` | `tools/callPOST` | +| `tools/list` | `tools/listPOST` | +| `resources/read` | `resources/readPOST` | +| `resources/list` | `resources/listPOST` | +| `prompts/get` | `prompts/getPOST` | +| `prompts/list` | `prompts/listPOST` | +| `initialize` | `initializePOST` | + +For example, to rate limit all tool calls at the method level: + +```json +{ + "middleware": { + "operations": { + "tools/callPOST": { + "rateLimit": { + "enabled": true, + "rate": 500, + "per": 60 + } + } + } + } +} +``` + +This rate limit applies to every `tools/call` request, whatever tool name appears in `params.name`. Method-level middleware evaluates before primitive-level middleware. + +#### mcpTools: per-tool middleware + +The `mcpTools` map configures middleware for individual tools. Each key is the tool name as it appears in the `params.name` field of a `tools/call` request: + +```json +{ + "middleware": { + "mcpTools": { + "get-weather": { + "allow": { "enabled": true }, + "rateLimit": { "enabled": true, "rate": 100, "per": 60 } + }, + "execute-query": { + "allow": { "enabled": true }, + "requestSizeLimit": { "enabled": true, "value": 8192 } + } + } + } +} +``` + +When any tool in `mcpTools` has `allow` enabled, the entire tools category enters allowlist mode: only the explicitly listed tools are accessible, and all other tool names are rejected. Tools, resources, and prompts are evaluated independently; allowlisting tools does not affect access to resources or prompts. + +#### mcpResources: per-resource middleware + +The `mcpResources` map configures middleware for individual resources or URI patterns. Each key is matched against the `params.uri` field of a `resources/read` request. Keys can be exact URIs or wildcard patterns using `*`: + +```json +{ + "middleware": { + "mcpResources": { + "weather://stations/london": { + "allow": { "enabled": true }, + "cache": { "enabled": true, "timeout": 300 } + }, + "weather://stations/*": { + "allow": { "enabled": true }, + "cache": { "enabled": true, "timeout": 60 } + } + } + } +} +``` + +Tyk resolves matches in priority order: exact matches take precedence over wildcard matches. When multiple wildcard patterns match, the longest matching prefix wins. + +#### mcpPrompts: per-prompt middleware + +The `mcpPrompts` map configures middleware for individual prompts. Each key is the prompt name as it appears in the `params.name` field of a `prompts/get` request: + +```json +{ + "middleware": { + "mcpPrompts": { + "weather-summary": { + "allow": { "enabled": true } + }, + "weather-alert": { + "allow": { "enabled": true }, + "transformRequestHeaders": { + "enabled": true, + "add": [{ "name": "X-Prompt-Tier", "value": "premium" }] + } + } + } + } +} +``` + +--- + +## Middleware precedence + +When a request arrives, Tyk evaluates middleware in this order: + +1. **Global middleware**: Applies to all requests on the API. +2. **Operation middleware** (`operations`): Applies to all requests for the matched JSON-RPC method. +3. **Primitive middleware** (`mcpTools`, `mcpResources`, or `mcpPrompts`): Applies to the specific named tool, resource, or prompt. Scope check (`scopeCheck`) and token exchange (`exchange`) run at this level — scope check validates the inbound token's scopes against the primitive's `security:` requirements, and token exchange replaces the `Authorization` header before the request reaches the upstream. +4. **Upstream proxy**: The modified request is forwarded to the upstream MCP server. + +All three middleware levels can be active simultaneously. A `tools/call` request to `execute-query` must pass global middleware, then the `tools/callPOST` operation middleware, then the `execute-query` primitive middleware, in that order. If any level rejects the request, processing stops and Tyk returns a JSON-RPC error to the client. + +All three middleware levels apply to all consumers of the proxy. For per-consumer control (different rate limits or tool access for different API keys), use security policies. Policies introduce a five-level rate limit hierarchy (including per-consumer primitive rate limits) and primitive allow/block lists that are evaluated independently for each consumer key. See [MCP proxy policies](/ai-management/mcp-gateway/policies) for details. + +--- + +## A complete example + +The following definition configures a weather MCP proxy with bearer token authentication, method-level and tool-level rate limiting, resource caching, and a prompt allowlist. + +```json +{ + "openapi": "3.0.3", + "info": { + "title": "Weather MCP proxy", + "version": "2025-11-25" + }, + "paths": { + "/mcp": { + "post": { + "operationId": "mcpTransportPost", + "summary": "Send a JSON-RPC message", + "parameters": [ + { + "name": "MCP-Protocol-Version", + "in": "header", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "required": true, + "content": { "application/json": { "schema": { "type": "object" } } } + }, + "responses": { + "200": { "description": "JSON-RPC response or SSE stream" } + } + }, + "get": { + "operationId": "mcpSSEGet", + "summary": "Open an SSE stream", + "responses": { + "200": { "description": "Server-sent events stream" } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "Weather MCP proxy", + "state": { "active": true } + }, + "server": { + "listenPath": { "value": "/weather/", "strip": true }, + "authentication": { + "enabled": true, + "securitySchemes": { + "bearerAuth": { "enabled": true } + } + } + }, + "upstream": { + "url": "https://weather-mcp.example.com" + }, + "middleware": { + "global": { + "trafficLogs": { "enabled": true } + }, + "operations": { + "tools/callPOST": { + "rateLimit": { "enabled": true, "rate": 500, "per": 60 } + } + }, + "mcpTools": { + "get-weather": { + "allow": { "enabled": true }, + "rateLimit": { "enabled": true, "rate": 100, "per": 60 } + }, + "get-forecast": { + "allow": { "enabled": true }, + "rateLimit": { "enabled": true, "rate": 50, "per": 60 } + } + }, + "mcpResources": { + "weather://stations/*": { + "allow": { "enabled": true } + } + }, + "mcpPrompts": { + "weather-summary": { + "allow": { "enabled": true } + } + } + } + } +} +``` + +What this definition does: + +- The API listens on `/weather/` and proxies to `https://weather-mcp.example.com`. Clients connect to `{gateway_host}/weather/mcp`. +- Bearer token authentication is required on all requests. +- All `tools/call` requests are rate limited to 500 per minute at the method level. +- Only two tools are accessible (`get-weather` and `get-forecast`). Any other tool name is rejected. Each tool has its own tighter rate limit. +- Resources matching `weather://stations/*` are accessible. +- Only the `weather-summary` prompt is accessible. +- Traffic logs are enabled for all requests. + +This example uses `bearerAuth` for simplicity. For full OAuth 2.1 compliance — including token validation against an external IdP, per-primitive scope enforcement, Protected Resource Metadata, and RFC 8693 token exchange — replace `bearerAuth` with the `oauth2` scheme. See [MCP OAuth 2.1](/ai-management/mcp-gateway/oauth-2-1) for a complete worked example. + +--- + +## Supported MCP spec features + +The following table documents which MCP protocol capabilities Tyk currently implements and how each maps to the proxy definition. + +| Capability | MCP spec version introduced | Tyk support | Notes | +|---|---|---|---| +| **Tools** (`tools/list`, `tools/call`) | Pre-2025-03-26 | ✅ Full | Per-tool middleware via `mcpTools`. Tool-level access control, rate limiting, timeouts, circuit breakers. | +| **Resources** (`resources/list`, `resources/read`) | Pre-2025-03-26 | ✅ Full | Per-resource middleware via `mcpResources`. URI wildcard patterns supported. | +| **Prompts** (`prompts/list`, `prompts/get`) | Pre-2025-03-26 | ✅ Full | Per-prompt middleware via `mcpPrompts`. | +| **Streamable HTTP transport** (`POST /mcp`) | 2025-03-26 | ✅ Full | Primary transport. JSON-RPC messages with single-response or SSE-streaming responses. | +| **SSE transport** (`GET /mcp`) | Pre-2025-03-26 | ✅ Full | Server-initiated messages. Tyk maintains the long-lived SSE connection. | +| **Sampling** (`sampling/createMessage`) | Pre-2025-03-26 | ✅ Pass-through | Tyk proxies sampling messages. Method-level middleware via `operations` applies; primitive-level middleware does not (sampling is client-side). | +| **Roots** (`roots/list`) | Pre-2025-03-26 | ✅ Pass-through | Tyk proxies roots messages unchanged. | +| **Elicitation** (`elicitation/create`) | 2025-03-26 | ✅ Pass-through | Tyk proxies elicitation messages unchanged. | +| **Protected Resource Metadata (PRM)** | 2025-03-26 | ✅ Full | Tyk serves `/.well-known/oauth-protected-resource` automatically. Configured under `authentication.securitySchemes[name].oauth2.protectedResourceMetadata` from Tyk 5.14.0. See [OAuth 2.1 authentication](/ai-management/mcp-gateway/oauth-2-1). | +| **stdio transport** | Pre-2025-03-26 | ❌ Not supported | Tyk is a network-based gateway. Use a stdio-to-HTTP bridge for local MCP servers. | + +**MCP specification version:** Tyk implements the `2025-11-25` revision of the MCP specification. + +--- + +## Summary + +| Concept | Where it lives in the definition | +|---|---| +| MCP primitives (tools, resources, prompts) | `middleware.mcpTools`, `middleware.mcpResources`, `middleware.mcpPrompts` | +| JSON-RPC method middleware | `middleware.operations`: keyed by `{method}POST` | +| API-wide middleware | `middleware.global` | +| Transport endpoints (`POST /mcp`, `GET /mcp`) | `paths./mcp.post`, `paths./mcp.get` in the OpenAPI spec | +| Listen path, authentication, upstream URL | `x-tyk-api-gateway.server`, `x-tyk-api-gateway.upstream` | + diff --git a/ai-management/mcp-gateway/oauth-2-1.mdx b/ai-management/mcp-gateway/oauth-2-1.mdx new file mode 100644 index 0000000000..252e4ab97d --- /dev/null +++ b/ai-management/mcp-gateway/oauth-2-1.mdx @@ -0,0 +1,520 @@ +--- +title: "MCP Gateway: OAuth 2.1 Authentication" +description: "How Tyk Gateway implements the OAuth 2.1 authorization model defined by the MCP specification: Protected Resource Metadata discovery, inbound Bearer token authentication via the oauth2 security scheme, RFC 8693 token exchange, and upstream OAuth for accessing OAuth-protected MCP servers." +keywords: "MCP, Model Context Protocol, OAuth 2.1, Protected Resource Metadata, PRM, RFC 9728, bearer token, client credentials, upstream authentication, well-known, authorization server, MCP authentication" +sidebarTitle: "OAuth 2.1 Authentication" +--- + +This page explains how Tyk Gateway implements the OAuth 2.1 authorization model defined by the MCP specification. It covers inbound Bearer token authentication via the `oauth2` security scheme, Protected Resource Metadata (PRM) discovery, RFC 8693 token exchange, and upstream OAuth. After reading this page you'll be able to configure end-to-end OAuth 2.1 for any MCP proxy. + +--- + +## MCP Auth Model + +MCP authorization operates on two distinct planes. + +**Inbound authorization** governs how MCP clients (AI agents, LLM frameworks, and applications) authenticate to Tyk Gateway. Tyk validates the credential on every request before it reaches your upstream MCP server. All [authentication methods](/api-management/client-authentication) supported by Tyk apply here: Bearer tokens, API keys, JWT, and mutual TLS. + + +For OAuth 2.1 compliance, configure the `oauth2` security scheme. It publishes the Protected Resource Metadata discovery document, enforces per-operation scopes, and enables RFC 8693 token exchange — all from a single scheme declaration. See [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication). + + +**Upstream authorization** governs how Tyk authenticates to your upstream MCP server when that server requires an OAuth token. Tyk obtains the token from the authorization server using the client credentials flow and injects it into every proxied request. Your upstream receives a properly authorized request without any involvement from the original caller. + +The two planes are configured independently — Bearer tokens on the inbound side can be combined with client credentials on the upstream side. + +--- + +## Protected Resource Metadata + +**Protected Resource Metadata (PRM)** is the standardized discovery mechanism defined in [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728). It gives OAuth clients a machine-readable document describing a protected resource: which authorization servers can issue tokens for it, and which OAuth scopes it supports. + +The MCP specification recommends that every MCP server expose a PRM document so that clients can discover the correct authorization server before making their first request. Without it, clients must be pre-configured with authorization server URLs — an approach that becomes fragile as deployments grow and authorization infrastructure changes. + +Tyk serves the PRM document natively. When PRM is enabled on an [MCP OAS definition](/ai-management/mcp-gateway/mcp-proxy-definitions), Tyk intercepts GET requests to the well-known path and serves the metadata document. The endpoint is unauthenticated by design — clients need it before they have a token. + +### The discovery flow + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant A as Authorization Server + participant U as Upstream MCP Server + + C->>T: POST /mcp (no token) + T-->>C: 401 Unauthorized + Note over T,C: WWW-Authenticate: Bearer resource_metadata=https://gateway.example.com/weather-mcp/.well-known/oauth-protected-resource + + C->>T: GET /.well-known/oauth-protected-resource + T-->>C: 200 OK — PRM document + Note over T,C: resource · authorization_servers: [auth.example.com] · scopes_supported + + C->>A: POST /oauth/token (client credentials grant) + A-->>C: 200 OK · access_token · token_type: Bearer + + C->>T: POST /mcp · Authorization: Bearer access_token + Note over T: Validate Bearer token · apply middleware + T->>U: POST /mcp · Authorization: Bearer upstream-token + U-->>T: Response + T-->>C: Response + Note over C,U: Request authorised and proxied +``` + +The `WWW-Authenticate` header Tyk sends on authentication failure is a standard Bearer challenge extended with the `resource_metadata` parameter defined in RFC 9728. Any OAuth 2.1-compliant client library handles this automatically. + +### Configuring PRM + +PRM is configured within the `oauth2` security scheme block in the Tyk Vendor Extension. The scheme name (`idpAuth` in the example below) must match the scheme declared with `type: oauth2` in the OAS `components.securitySchemes` section — see [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication) for the full scheme setup. + +```json +{ + "x-tyk-api-gateway": { + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "idpAuth": { + "enabled": true, + "protectedResourceMetadata": { + "enabled": true, + "resource": "https://gateway.example.com/weather-mcp/", + "authorizationServers": ["https://auth.example.com"], + "autoDeriveScopes": true + } + } + } + } + } + } +} +``` + +| Field | Required | Description | +|---|---|---| +| `enabled` | Yes | Activates the PRM endpoint. When `true`, Tyk serves the metadata document and includes the `WWW-Authenticate: Bearer resource_metadata=...` header on authentication failures. | +| `resource` | Yes | The resource identifier for this API, typically the URL at which Tyk exposes the MCP proxy. Accepts `$tyk_context.*` variables for dynamic values. | +| `authorizationServers` | Yes | One or more authorization server URLs that can issue tokens for this resource. Tyk validates that at least one entry is present for Tyk OAS API definitions. | +| `wellKnownPath` | No | Overrides the default well-known path. Defaults to `.well-known/oauth-protected-resource`. Relative to the API's listen path. | +| `autoDeriveScopes` | No | When `true` (default), the `scopes_supported` field in the PRM document is populated from the `flows.scopes` catalog in your OAS `components.securitySchemes` declaration and every `security:` array on the API. When `false`, only the `flows.scopes` catalog is used. | + +The PRM endpoint is served at `{listen-path}/{wellKnownPath}`. With the default path and a listen path of `/weather-mcp/`, the endpoint is available at `/weather-mcp/.well-known/oauth-protected-resource`. + + +If you have an existing PRM configuration at `authentication.protectedResourceMetadata`, Tyk Dashboard migrates it automatically to `authentication.securitySchemes[name].protectedResourceMetadata` on startup. No manual action is required. APIs that already have a scheme-level PRM block are not modified. + + +--- + +## Scope enforcement + +Scope enforcement checks that the bearer token carries the scopes required by the matched operation or MCP primitive — translating what the authorization server granted into access decisions at the gateway. + +Configure it under `scopeCheck` in the `oauth2` scheme block: + +```json +{ + "x-tyk-api-gateway": { + "server": { + "authentication": { + "securitySchemes": { + "idpAuth": { + "enabled": true, + "scopeCheck": { + "enabled": true, + "claimNames": ["scope", "scp"], + "separator": " ", + "scopeSource": "union" + } + } + } + } + } + } +} +``` + +The required scopes for each operation come from the `security:` array in your OAS definition. For MCP primitives, which have no OAS path entry, declare scopes in the Tyk Vendor Extension under `middleware.mcpTools`, `middleware.mcpResources`, or `middleware.mcpPrompts`: + +```json +{ + "x-tyk-api-gateway": { + "middleware": { + "mcpTools": { + "create-report": { + "security": [{ "idpAuth": ["tools:write"] }] + } + } + } + } +} +``` + +| Field | Required | Description | +|---|---|---| +| `enabled` | Yes | Activates scope enforcement for this scheme. | +| `claimNames` | No | JWT claim names to read scopes from. Defaults to `["scope", "scp"]`. All listed claims present on the token are merged into a single scope set. | +| `separator` | No | Character used to split string-valued scope claims. Defaults to `" "` (space). Set to `","` for comma-separated IdPs. | +| `scopeSource` | No | Which `security:` declarations drive enforcement: `"union"` (default) merges root and per-operation alternatives; `"operation"` applies only the matched operation's declaration; `"global"` applies only the root-level declaration. | + +When a request fails scope enforcement, Tyk returns `403 Forbidden` with `WWW-Authenticate: Bearer error="insufficient_scope"`. Scope enforcement runs after JWT signature validation — if the token is invalid, the request is rejected at the JWT auth step before scope check is reached. + +For per-operation and per-primitive exemptions (`scopeCheck.enabled: false` on individual operations or MCP primitives) and the full field reference, see [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication#scope-enforcement). + +--- + +## Upstream OAuth + +When the upstream MCP server requires OAuth, Tyk handles token acquisition transparently. It acts as an OAuth client — obtaining a token from the upstream's authorization server using the client credentials flow and attaching it to every proxied request. The original MCP client never needs to supply upstream credentials. + +Tyk caches acquired tokens and refreshes them before they expire, so the upstream sees a consistent stream of valid credentials without a token request on every MCP call. + + +Upstream OAuth is available in Tyk Enterprise Edition only. + + +### Client credentials flow + +Client credentials is the OAuth flow for machine-to-machine communication — no user is involved, Tyk acts as the client. OAuth 2.1 retains this flow specifically for server-to-server scenarios. + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant A as Authorization Server + participant U as Upstream MCP Server + + C->>T: POST /mcp · Authorization: Bearer inbound-token + Note over T: Validate inbound token · check token cache + + alt Token not cached or expired + T->>A: POST /oauth/token + Note over T,A: grant_type: client_credentials · client_id · client_secret · scopes + A-->>T: 200 OK · access_token · expires_in: 3600 + Note over T: Cache upstream token + else Token cached + Note over T: Use cached upstream token + end + + T->>U: POST /mcp · Authorization: Bearer upstream-token + U-->>T: Response + T-->>C: Response + Note over C,U: Request processed upstream +``` + +### Configuring upstream OAuth + +Upstream OAuth is configured in the `upstream.authentication` section of the API definition: + +```json +{ + "x-tyk-api-gateway": { + "upstream": { + "url": "https://weather-mcp.example.com", + "authentication": { + "enabled": true, + "oauth": { + "enabled": true, + "allowedAuthorizeTypes": ["clientCredentials"], + "clientCredentials": { + "clientId": "tyk-gateway-client", + "clientSecret": "your-client-secret", + "tokenUrl": "https://auth.example.com/oauth/token", + "scopes": ["mcp:read", "mcp:write"] + } + } + } + } + } +} +``` + +| Field | Required | Description | +|---|---|---| +| `clientId` | Yes | The OAuth client ID issued by the authorization server for this gateway instance. | +| `clientSecret` | Yes | The client secret associated with the client ID. | +| `tokenUrl` | Yes | The token endpoint of the upstream's authorization server. | +| `scopes` | No | The scopes to request when obtaining the token. The authorization server grants only the scopes it recognises and the client is permitted. | +| `extraMetadata` | No | Keys to extract from the token response and pass to the upstream as additional context. | + +--- + +## Token exchange + +The [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#access-token-privilege-restriction) requires that an MCP server **MUST NOT** pass through the token it received from the MCP client when making requests to upstream APIs. The upstream token must be a separate token issued by the upstream authorization server. + +**Token exchange** ([RFC 8693](https://www.rfc-editor.org/rfc/rfc8693)) is how Tyk satisfies this requirement while preserving the caller's identity. Tyk presents the validated inbound token to an authorization server and receives a backend-scoped token in return. The inbound agent token never reaches the upstream. + +This matters for MCP because: + +- **Spec compliance** — The MCP spec explicitly forbids passing through the inbound token. Token exchange satisfies this requirement while maintaining a traceable delegation chain. +- **Audience compliance** — MCP servers must validate that tokens were issued specifically for them as the intended audience ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)). Token exchange produces a token audienced to the upstream service without requiring clients to request multiple tokens. +- **Separation of trust** — The AI agent's credential stays scoped to the gateway trust domain; the upstream receives a narrowly-scoped credential appropriate to its own. + +```mermaid +sequenceDiagram + autonumber + participant C as MCP Client + participant T as Tyk Gateway + participant I as Authorization Server + participant U as Upstream MCP Server + + C->>T: POST /mcp · Authorization: Bearer agent-token + Note over T: Validate inbound token · enforce scopes + + T->>I: POST /token (RFC 8693 exchange) + Note over T,I: subject_token=agent-token · audience=upstream-service + I-->>T: 200 OK · access_token (upstream-scoped) + + T->>U: POST /mcp · Authorization: Bearer upstream-token + U-->>T: Response + T-->>C: Response + Note over C,U: Inbound token never forwarded to upstream +``` + +Token exchange is Enterprise Edition only and is configured within the `oauth2` security scheme. For full configuration details — including provider setup, caching, and per-primitive overrides — see [Token exchange](/api-management/authentication/token-exchange). + + +Do not configure `upstream.authentication.oauth` alongside token exchange. They serve the same purpose — authenticating Tyk to the upstream — and configuring both produces a conflict. Use token exchange when you want to propagate a delegated credential derived from the inbound token. Use `upstream.authentication.oauth` when Tyk should authenticate with its own static client credentials independent of who the original caller was. + + +--- + +## Composing features + +Each feature on this page is independent. The example below uses all of them, but you can enable any subset depending on your requirements. + +**JWT authentication** validates the inbound token's signature. In Tyk 5.14.0 this is required alongside the `oauth2` scheme because the `oauth2` scheme reads token claims but does not verify the signature itself. It is the baseline for everything else. + +**PRM** enables dynamic discovery — clients that arrive without a token learn where to obtain one. Enable it if your MCP clients are OAuth 2.1-compliant and should discover the authorization server automatically. Omit it if your clients are pre-configured with the authorization server URL. + +**Scope enforcement** (`scopeCheck`) checks that the token carries the scopes required by the matched operation or MCP primitive. Use it when the IdP owns access decisions — the authorization server grants specific scopes and Tyk enforces them at the gateway. If you prefer Tyk's policy engine to control primitive access (a platform-owned model where keys are issued with policy-level permissions), omit `scopeCheck` and rely on policies instead. The two approaches are mutually exclusive per primitive: using both creates conflicting ownership of access decisions. + +**Token exchange** replaces the inbound agent token with an upstream-scoped token before forwarding. The MCP specification requires that the upstream never receives the original token — token exchange is how you satisfy that requirement while preserving the delegation chain. If you are using [Upstream OAuth](#upstream-oauth) with static client credentials instead, token exchange can be omitted. + +**PRM and scope enforcement are particularly complementary.** PRM advertises which scopes clients need to request; `scopeCheck` enforces at runtime that the presented token actually carries them. When `autoDeriveScopes` is enabled, both are driven by the same `security:` declarations in your OAS definition — so the scopes a client is told to request and the scopes Tyk enforces are derived from the same source and can't drift out of sync. Enabling PRM without `scopeCheck` means scopes are advertised but not enforced at the gateway. Enabling `scopeCheck` without PRM means enforcement works, but clients need to know the required scopes upfront rather than discovering them dynamically. + +Common configurations: + +| Goal | Features to enable | +|---|---| +| Validate tokens; use Tyk policies for primitive access | JWT auth | +| Add client-side discovery | JWT auth + PRM | +| Add IdP-owned scope enforcement | JWT auth + PRM + scopeCheck | +| Full MCP spec compliance with delegated upstream identity | JWT auth + PRM + scopeCheck + token exchange | + +--- + +## The complete OAuth 2.1 flow + +The end-to-end flow has two phases. The authorization server appears in both — first when the MCP client obtains its agent token, then when Tyk exchanges it for an upstream-scoped token. + +**Phase 1 — Discovery** + +1. The MCP client makes a request to the MCP endpoint without a token. +2. Tyk returns `401 Unauthorized` with a `WWW-Authenticate` header pointing to the PRM well-known URL. +3. The client fetches the PRM document and learns which authorization server to use and which scopes are supported. +4. The client authenticates to the authorization server (authorization code flow, device flow, etc.) and receives an agent-scoped access token. + +**Phase 2 — Authenticated request** + +5. The client sends the request with `Authorization: Bearer `. +6. Tyk validates the token signature (JWT auth) and enforces scopes (`oauth2` scheme). +7. Tyk POSTs an RFC 8693 token exchange to the authorization server, presenting the agent token as `subject_token` and requesting a token audienced to the upstream MCP server. +8. The authorization server returns an upstream-scoped access token. +9. Tyk replaces the `Authorization` header with the exchanged token and forwards the request to the upstream MCP server. +10. The upstream responds; Tyk returns the response to the client. + +The agent's original token never reaches the upstream MCP server. The MCP client and the upstream each receive a token issued specifically for their trust boundary. + +--- + +## A complete configuration example + +The following configuration for a weather MCP proxy enables all four features — PRM discovery, JWT signature validation, scope enforcement, and RFC 8693 token exchange: + +```json expandable +{ + "openapi": "3.0.3", + "info": { "title": "Weather MCP proxy", "version": "2025-11-25" }, + "components": { + "securitySchemes": { + "jwtAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }, + "idpAuth": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://auth.example.com/authorize", + "tokenUrl": "https://auth.example.com/token", + "scopes": { + "tools:read": "Read access to MCP tools", + "tools:write": "Write access to MCP tools" + } + } + } + } + } + }, + "security": [ + { "jwtAuth": [], "idpAuth": ["tools:read"] } + ], + "paths": { + "/mcp": { + "post": { "operationId": "mcpTransportPost", "responses": { "200": { "description": "JSON-RPC response" } } }, + "get": { "operationId": "mcpSSEGet", "responses": { "200": { "description": "SSE stream" } } } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "Weather MCP proxy", + "state": { "active": true } + }, + "server": { + "listenPath": { "value": "/weather-mcp/", "strip": true }, + "authentication": { + "enabled": true, + "securitySchemes": { + "jwtAuth": { + "enabled": true, + "signingMethod": "rsa", + "jwksURIs": [{ "url": "https://auth.example.com/.well-known/jwks.json" }], + "identityBaseField": "sub" + }, + "idpAuth": { + "enabled": true, + "scopeCheck": { + "enabled": true, + "claimNames": ["scope", "scp"], + "separator": " ", + "scopeSource": "union" + }, + "protectedResourceMetadata": { + "enabled": true, + "resource": "https://gateway.example.com/weather-mcp/", + "authorizationServers": ["https://auth.example.com"], + "autoDeriveScopes": true + }, + "tokenExchange": { + "enabled": true, + "providers": [ + { + "name": "idp-prod", + "issuers": ["https://auth.example.com"], + "tokenEndpoint": "https://auth.example.com/token", + "clientAuth": { + "method": "client_secret_basic", + "clientId": "tyk-gateway", + "clientSecret": "env://EXCHANGE_CLIENT_SECRET" + }, + "defaultTarget": { + "audience": "https://weather-mcp.example.com", + "scopes": ["mcp:read", "mcp:write"] + } + } + ] + } + } + } + } + }, + "upstream": { + "url": "https://weather-mcp.example.com" + } + } +} +``` + +In this configuration: +- MCP clients that arrive without a token receive a `401` with a `WWW-Authenticate` header pointing to the PRM document. +- Clients that follow the discovery flow obtain a token from `https://auth.example.com` using the `authorizationCode` flow and present it as a Bearer token. +- Tyk validates the token signature via the `jwtAuth` scheme (JWKS endpoint), then `scopeCheck` enforces that the token carries `tools:read` before the request proceeds. +- Tyk exchanges the validated agent token for an upstream-scoped token audienced to `https://weather-mcp.example.com`. The original agent token never reaches the upstream MCP server. + + +In Tyk 5.14.0, the `oauth2` scheme does not validate JWT signatures itself. The `jwtAuth` scheme in this example handles signature validation — configure JWT authentication on the API alongside the `oauth2` scheme so that inbound tokens are verified before scope enforcement runs. + + +For the static client credentials variant — where Tyk authenticates to the upstream with its own credentials independent of the inbound token — see [Upstream OAuth](#upstream-oauth). + +--- + +## Per-primitive configuration + +The complete example above configures each feature at the API level, applying uniformly to all primitives. Both scope enforcement and token exchange support per-primitive overrides when individual tools need different access requirements or different upstream credentials. + +### Multiple exchange providers + +The `providers` array accepts multiple entries. Tyk selects the matching provider at request time by comparing the inbound token's `iss` claim against each provider's `issuers` list. This lets you handle tokens from different authorization servers — for example, your own IdP and a partner's — routing each to the appropriate token endpoint and default target: + +```json +"tokenExchange": { + "enabled": true, + "providers": [ + { + "name": "idp-prod", + "issuers": ["https://auth.example.com"], + "tokenEndpoint": "https://auth.example.com/token", + "clientAuth": { + "method": "client_secret_basic", + "clientId": "tyk-gateway", + "clientSecret": "env://EXCHANGE_CLIENT_SECRET" + }, + "defaultTarget": { + "audience": "https://weather-mcp.example.com", + "scopes": ["mcp:read", "mcp:write"] + } + }, + { + "name": "partner-idp", + "issuers": ["https://auth.partner.example"], + "tokenEndpoint": "https://auth.partner.example/token", + "clientAuth": { + "method": "client_secret_post", + "clientId": "tyk-gateway-partner", + "clientSecret": "env://PARTNER_EXCHANGE_SECRET" + }, + "defaultTarget": { + "audience": "https://weather-mcp.example.com", + "scopes": ["mcp:read"] + } + } + ] +} +``` + +If no provider matches the inbound token's issuer, the exchange step fails and Tyk returns an error before the request reaches the upstream. + +### Per-primitive scope and exchange overrides + +The `defaultTarget` in each provider applies uniformly to every primitive. When individual tools need a different audience, a narrower set of scopes, or stricter scope enforcement than the API-level default, configure overrides directly on the primitive in the `middleware.mcpTools` map: + +```json +"middleware": { + "mcpTools": { + "get-forecast": { + "security": [{ "idpAuth": ["tools:read"] }], + "scopeCheck": { "enabled": true } + }, + "delete-station": { + "security": [{ "idpAuth": ["tools:write", "admin:stations"] }], + "scopeCheck": { "enabled": true }, + "exchange": { + "enabled": true, + "audience": "https://station-admin.example.com", + "scopes": ["admin:stations"] + } + } + } +} +``` + +- `get-forecast` requires `tools:read`. Scope enforcement rejects tokens that don't carry that claim. The exchange uses the provider's `defaultTarget` audience and scopes unchanged. +- `delete-station` requires both `tools:write` and `admin:stations`. The `exchange` override requests a token audienced specifically to `https://station-admin.example.com` — a narrower credential than the standard weather MCP token — carrying only the `admin:stations` scope. + +For the full `scopeCheck` and `exchange` field reference, see [MCP middleware](/ai-management/mcp-gateway/mcp-middleware). diff --git a/ai-management/mcp-gateway/overview.mdx b/ai-management/mcp-gateway/overview.mdx new file mode 100644 index 0000000000..752f84e2f4 --- /dev/null +++ b/ai-management/mcp-gateway/overview.mdx @@ -0,0 +1,159 @@ +--- +title: "MCP Gateway" +description: "Tyk Gateway proxies and governs remote MCP servers, applying authentication, rate limiting, access control, key management, and observability to AI agent traffic at the gateway level." +keywords: "MCP, Model Context Protocol, MCP Gateway, AI agent, remote MCP server, JSON-RPC, authentication, rate limiting, observability, policy enforcement" +sidebarTitle: "Overview" +--- + +![MCP Gateway](/img/ai-management/mcp-gateway-header.png) + +The Model Context Protocol (MCP) is the open standard for connecting AI applications to external tools, data sources, and workflows. + +As MCP servers move into shared cloud infrastructure, they face the same operational challenges REST APIs faced a decade ago: who can call what, how often, with what credentials, and with what visibility. + +Tyk Gateway addresses these challenges natively, sitting between MCP clients and your remote MCP servers to enforce authentication, access policies, rate limits, and traffic governance on every JSON-RPC request. + +--- + +## What is MCP? + +MCP uses a client-server model: an **MCP client** (an AI agent or framework) connects to an **MCP server** that exposes tools, resources, and prompts through JSON-RPC 2.0 messages over HTTP. + +For the full protocol reference, see [MCP Gateway: Core Concepts](/ai-management/mcp-gateway/core-concepts) or the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25). + +--- + +## The problem with ungoverned MCP + +Remote MCP servers are HTTP services. Like any HTTP service, they need authentication, rate limiting, access control, and observability to be operated reliably at scale. Without a gateway layer, each individual server must address these concerns on its own (if at all), leading to: + +**Inconsistent security.** Each MCP server implements its own authentication, or none at all. No central place exists to enforce who can access which tools, rotate credentials, or revoke access. + +**No visibility.** No standard way exists to see which AI agents are calling which tools, how often, and whether calls are succeeding. Troubleshooting failures or planning capacity requires digging into individual server logs. + +**Ungoverned proliferation.** MCP servers can appear across teams without oversight. No registry exists of what is available, no approval process, and no way to apply organization-wide policies consistently. + +**Fragile direct connections.** AI agents connecting directly to MCP servers have no protection if a server is slow or unavailable. Rate limits, circuit breakers, and timeouts must be rebuilt on every server independently. + +--- + +## Where Tyk fits + +Tyk Gateway sits in front of your remote MCP servers and proxies all MCP traffic through a centrally managed gateway layer. AI clients connect to Tyk at a configured listen path; Tyk authenticates the request, applies the configured middleware chain, and forwards the request to the upstream MCP server. The Tyk Dashboard serves as the registry layer, the central catalog of every MCP server in your organization, with the access policies and observability that govern how each one is used. + +![Where Tyk MCP Gateway fits](/img/ai-management/where-tyk-mcp-gateway-fits.png) + +Tyk understands the MCP protocol. It parses JSON-RPC 2.0 request bodies to identify the method being called and the specific tool, resource, or prompt being accessed. This means you can apply policies at the level of individual MCP primitives (rate limiting a particular tool, blocking access to a specific resource, or caching a prompt response) rather than treating all MCP traffic as an opaque HTTP stream. + +Tyk handles both MCP transport methods: + +- **`POST /mcp`**: JSON-RPC messages from client to server. Tyk applies the middleware chain and proxies the request, streaming SSE responses through if the upstream returns them. +- **`GET /mcp`**: Long-lived SSE connections for server-initiated messages. Tyk maintains the connection and passes events through transparently. + +--- + +## How Tyk represents MCP proxies + +Tyk models each remote MCP server as a **[Tyk OAS API definition](/ai-management/mcp-gateway/mcp-proxy-definitions)**, an OpenAPI 3.0 document extended with the `x-tyk-api-gateway` vendor extension. This is the same format used for REST APIs, so MCP proxies share the same configuration model, tooling, and management APIs as the rest of your Tyk estate. + +The MCP proxy definition configures: + +- The **listen path**: the URL prefix where Tyk exposes the MCP proxy to clients +- The **upstream URL**: the address of your remote MCP server +- **Authentication**: which method to use and how to validate credentials +- **Middleware**: per-primitive access control, rate limits, caching, transformations, and more + +You create and manage MCP proxy definitions through the Tyk Dashboard or the Gateway API. No changes to your upstream MCP server are required. + + +MCP proxy definitions require Tyk OAS format. Tyk Classic API definitions do not support MCP. + + +--- + +## What Tyk MCP Gateway provides + +### Authentication and key management + +Tyk authenticates every MCP request before it reaches your upstream server. All authentication methods supported for REST APIs work identically for MCP: bearer tokens, API keys, JWT, OAuth 2.0, and mTLS. You issue and manage API keys through the Tyk Dashboard or API, and every key is associated with a security policy that defines what it can access and at what rate. + +This means your MCP servers don't need to implement their own authentication. Tyk handles credential verification, token validation, and key lifecycle centrally: rotation, expiration, and revocation. + +The [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) mandates OAuth 2.1 as the authorization framework and requires MCP servers to implement **Protected Resource Metadata** (PRM), a discovery document that tells OAuth-aware clients which authorization server to use and which scopes the resource supports. Tyk implements PRM natively: it serves the `/.well-known/oauth-protected-resource` endpoint automatically and returns the correct `WWW-Authenticate` challenge on unauthenticated requests, so compliant MCP clients can self-configure without any pre-configuration. See [OAuth 2.1 authentication](/ai-management/mcp-gateway/oauth-2-1) for the full implementation details. + +### Access control and policy enforcement + +Security policies give you granular, per-consumer control over what MCP consumers can see and access, and how often. Rather than configuring each key individually, you define a policy once and issue keys that inherit its rules automatically. + +For MCP proxies, policies go significantly further than the rate limits and quotas available for standard REST APIs. A policy can control which proxies a consumer key can reach, which JSON-RPC methods it is permitted to use, which individual tools, resources, and prompts it can invoke or discover, and what rate limits and quotas apply at each level, independently for each consumer. + +This is what makes it practical to serve many different AI agents from a single MCP proxy. A read-only analyst agent and a privileged administrator agent can share the same upstream server but operate within completely different entitlements, each enforced at the gateway without any changes to the upstream. + +Tyk also filters discovery responses per consumer. When an agent calls `tools/list`, `resources/list`, or `prompts/list`, Tyk intercepts the upstream response and returns only the primitives that consumer is permitted to see. **Each agent's view of the server's capabilities is scoped to its own entitlements from the moment it connects.** + +### Service registry + +The Tyk Dashboard's MCP section serves as a central registry of all MCP servers in your organization. Each MCP proxy is a registry entry that records the upstream server's address, its listen path, the tools and resources it exposes, and the access policies that govern it. + +This gives teams a single authoritative source of truth for what MCP capabilities are available, who can access them, and under what conditions. When a new MCP server is onboarded, registering it as a proxy in the Dashboard makes it discoverable to authorized agents and invisible to everyone else, without changes to the upstream server or the agents themselves. + +See [Managing MCP proxies](/ai-management/mcp-gateway/managing-proxies). + +### Traffic management + +Tyk applies the same traffic management capabilities to MCP traffic that it provides for REST and GraphQL: + +- **Rate limiting** at every level of granularity: across all consumers on a proxy, per consumer key, per JSON-RPC method, and down to the individual named tool, resource, or prompt. Limits at each level are tracked independently, so a consumer exhausting their budget on one tool does not affect their access to others. +- **Throttling** to queue requests that exceed the rate limit rather than rejecting them immediately. +- **Timeouts** per primitive, so a slow tool response does not stall the entire MCP session. +- **Circuit breakers** that detect failure patterns on individual tools and temporarily stop forwarding requests, giving the upstream time to recover. +- **Request size limits** to protect against oversized argument payloads on tools that accept large inputs. +- **Upstream rate limits** that protect your MCP server from being overwhelmed regardless of how many consumers are configured. + +### Analytics + +Tyk records analytics for every MCP request and exposes them in the Tyk Dashboard under **Monitoring → Activity by MCP**. Analytics are captured at two levels. + +**Proxy-level charts** show total request volume, error counts, and HTTP error code distribution across all your MCP proxies. Use these to compare traffic and error rates between proxies and identify trends over time. + +**Primitive-level charts** break the data down by individual tool, resource, or prompt: call volumes over time, most frequently called primitives, highest error rates, and slowest average latency. These charts show exactly which tools AI agents are calling, which are failing, and which are your performance bottlenecks, without any additional instrumentation. + +Both levels of data appear alongside your REST and GraphQL API analytics, giving you a unified view of your entire API estate. See [MCP observability](/ai-management/mcp-gateway/mcp-observability). + +### Observability + +Beyond the Dashboard analytics page, Tyk emits MCP-specific observability signals that integrate with your existing monitoring infrastructure. + +**Structured access logs** include four MCP-specific fields on every logged request: the JSON-RPC method invoked (`mcp_method`), the primitive type (`mcp_primitive_type`), the primitive name (`mcp_primitive_name`), and a gateway-mapped error code when the request fails at the gateway layer (`mcp_error_code`). These fields let you filter and aggregate MCP traffic in your log management tooling using the same pipeline you use for REST APIs. See [MCP access logs](/ai-management/mcp-gateway/mcp-access-logs). + +**OpenTelemetry metrics**: when OTel is enabled on the gateway, Tyk emits four MCP-specific metric instruments covering request counts (with dimensions for method, primitive type, tool name, and error code), method distribution, upstream latency per tool, and end-to-end request latency. These metrics can be scraped by [Prometheus](https://prometheus.io/) and used to build dashboards in [Grafana](https://grafana.com/) or your preferred metrics platform. See [MCP metrics](/ai-management/mcp-gateway/mcp-metrics) and [How to build a Grafana dashboard for MCP traffic](/ai-management/mcp-gateway/how-to-grafana-mcp-dashboard). + +--- + +## Requirements and limitations + +### Requirements + +| Requirement | Detail | +|---|---| +| Tyk Gateway version | v5.13 or later | +| API definition format | Tyk OAS only. Tyk Classic API definitions do not support MCP. | +| API definition extension | The `x-tyk-api-gateway` vendor extension is required in every MCP proxy definition. | + +### Supported transports + +Tyk MCP Gateway supports the **Streamable HTTP** transport defined in the MCP `2025-11-25` specification: + +- **`POST /mcp`**: JSON-RPC messages from client to server, with optional SSE streaming responses. +- **`GET /mcp`**: Long-lived SSE connection for server-initiated messages. + +The **stdio** transport is not supported. Tyk is a network-based API gateway; stdio is designed for local, in-process MCP servers. If your upstream MCP server uses stdio, a stdio-to-HTTP bridge (such as the one provided by the MCP SDK) is required between it and Tyk. + +### HTTP-to-MCP translation + +When using Tyk to convert an existing REST API into MCP tools (via [AI Studio](/ai-management/ai-studio/overview)), only **OpenAPI REST APIs** are supported as the source. GraphQL APIs cannot be converted to MCP tools. + +### MCP specification version + +Tyk supports the MCP **`2025-11-25`** specification. For the full protocol reference, see the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25). + diff --git a/ai-management/mcp-gateway/policies.mdx b/ai-management/mcp-gateway/policies.mdx new file mode 100644 index 0000000000..d7a8d043b6 --- /dev/null +++ b/ai-management/mcp-gateway/policies.mdx @@ -0,0 +1,217 @@ +--- +title: "MCP Gateway Policies" +description: "How Tyk security policies govern MCP proxy access: which proxies a consumer can reach, which tools and resources they can invoke, and what rate limits and quotas apply, all configured in one place and applied consistently across every key." +keywords: "MCP, Model Context Protocol, MCP policies, access control, rate limiting, mcp_primitives, mcp_access_rights, json_rpc_methods_access_rights, security policy, MCP proxy" +sidebarTitle: "MCP Policies" +--- + +A Tyk security policy defines the access rules and usage limits for a consumer. For MCP proxies, policies go beyond the standard rate limit and quota controls available for REST APIs. They let you control access and apply rate limits at the level of individual tools, resources, and prompts, and restrict which JSON-RPC protocol methods a consumer can use at all. This page explains what MCP policies control, how to configure them, and how Tyk merges multiple policies applied to the same key. + +--- + +## What policies are for + +A policy is a reusable template applied to one or more API keys. Rather than configuring limits and access rights on each key individually, you define them once in a policy and issue keys that inherit those rules automatically. When you update the policy, every key bound to it picks up the change. + +For MCP proxies, policies serve three purposes: + +**Controlling access**: a policy determines which MCP proxies a consumer key can reach. Without an entry in the policy's access rights, the consumer receives `403 Forbidden` regardless of what key they present. + +**Governing MCP capabilities**: beyond proxy-level access, policies let you restrict which JSON-RPC methods a consumer can use and which specific tools, resources, and prompts they can invoke. This lets you give different consumers different views of the same MCP proxy without creating separate proxy definitions. + +**Enforcing usage limits**: policies apply rate limits and quotas at multiple levels: across the whole policy, per MCP proxy, per JSON-RPC method, and per named primitive. Each consumer key tracks its own independent counters. + +--- + +## What MCP policies control + +### Access control + +Tyk evaluates access control at three levels, in order: + +**Proxy access**: the policy's access rights list determines which MCP proxies the key can reach. A key can only call a proxy listed in its access rights. + +**JSON-RPC method access** (`json_rpc_methods_access_rights`): controls which protocol-level operations the consumer may use. For example, you can allow `tools/call` and `tools/list` while blocking `sampling/createMessage`. Use an `allowed` list to restrict to specific methods, or a `blocked` list to exclude specific methods while permitting all others. + +**Primitive access** (`mcp_access_rights`): controls which specific tools, resources, and prompts the consumer can invoke. Each primitive type (tools, resources, prompts) has its own `allowed` and `blocked` list. Values are Go regular expressions, so `"internal_.*"` matches any tool whose name starts with `internal_`. A non-empty `allowed` list acts as an explicit allowlist; `blocked` entries are excluded from whatever is otherwise permitted. + +### Rate limiting + +Rate limits can be applied at four levels within a policy entry for an MCP proxy: + +**Policy global**: the top-level `rate` and `per` fields on the policy apply across all APIs the key accesses. + +**Per MCP proxy**: the `limit` field inside an access rights entry applies a rate limit specific to calls to that proxy, independent of the consumer's overall policy rate. + +**Per JSON-RPC method** (`json_rpc_methods`): sets a rate limit for calls using a specific method name, such as `tools/call`. The counter applies across all tools invoked via that method. + +**Per primitive** (`mcp_primitives`): sets a rate limit scoped to a specific named tool, resource, or prompt. A consumer who exhausts their limit on `generate_report` is blocked from calling that tool while remaining free to call others. + +All applicable limits are checked independently on every call. Whichever is exhausted first blocks the request. + +### Quotas + +A quota sets a maximum total number of calls over a renewal period (daily, weekly, or monthly). A consumer who exhausts their quota receives `429 Too Many Requests` until the period resets. Quotas are configured at the policy level using `quota_max` and `quota_renewal_rate`. See [Quotas](/api-management/request-quotas) for details. + +--- + +## Configuring MCP policies + +### Using the Tyk Dashboard + +1. In the Tyk Dashboard sidebar, click **Policies** then **Add Policy**. + + ![MCP policy list](/img/ai-management/mcp-policy-list.png) + +2. On the **Access Rights** tab, find your MCP proxy in the list and click it to add it to the policy. +3. Expand the proxy's access rights block. You will see sections for primitive rate limits and access control. +4. To configure **primitive rate limits**, click **Add Rate Limit**. Set the **Rate** and **Per** (in seconds) values, then click **Add Primitive** to associate a named tool, resource, or prompt with that limit. Add multiple primitives to the same group if they share a limit; create separate groups for different limits. +5. To configure **access control** for methods and primitives, use the access control tabs within the access rights block. Add tool, resource, prompt, or method names and toggle them between allowed and blocked. +6. On the **Configurations** tab, set the policy name, key expiry, and global rate limit. +7. Click **Create Policy**. + +### Using the Dashboard API + +Create or update a policy via `POST /api/portal/policies` (create) or `PUT /api/portal/policies/{policy-id}` (update). The MCP-specific fields sit inside the `access_rights` entry for each MCP proxy. + +The following example creates a policy that grants access to a weather MCP proxy, restricts the consumer to read-only methods, limits them to specific tools, and applies per-primitive rate limits: + +```json expandable +{ + "name": "Weather Agent — Standard Tier", + "state": "active", + "rate": 1000, + "per": 60, + "quota_max": 50000, + "quota_renewal_rate": 86400, + "access_rights": { + "{mcp-proxy-api-id}": { + "api_id": "{mcp-proxy-api-id}", + "api_name": "Weather MCP Proxy", + "versions": ["Default"], + "limit": { + "rate": 200, + "per": 60 + }, + "json_rpc_methods_access_rights": { + "allowed": ["tools/call", "tools/list", "resources/list", "resources/read"] + }, + "mcp_access_rights": { + "tools": { + "allowed": ["get_forecast", "search_weather"] + }, + "resources": { + "blocked": ["internal://.*"] + }, + "prompts": {} + }, + "json_rpc_methods": [ + { + "name": "tools/call", + "limit": { "rate": 100, "per": 60 } + } + ], + "mcp_primitives": [ + { + "type": "tool", + "name": "get_forecast", + "limit": { "rate": 20, "per": 60 } + }, + { + "type": "resource", + "name": "weather://current", + "limit": { "rate": 10, "per": 60 } + } + ] + } + } +} +``` + +See [Policies](/api-management/policies) for more details. + +--- + +## Policy schema for MCP + +The MCP-specific fields appear inside each entry in the `access_rights` object, alongside the standard `limit` and `versions` fields. + +### Access rights entry + +| Field | Type | Description | +|---|---|---| +| `api_id` | string | The ID of the MCP proxy this entry applies to. | +| `api_name` | string | Display name of the proxy. | +| `versions` | array | Always `["Default"]` for MCP proxies. | +| `limit` | object | Per-proxy rate limit for this consumer. Contains `rate` (integer) and `per` (seconds). | +| `mcp_access_rights` | object | Primitive-level allow/block lists. See below. | +| `json_rpc_methods_access_rights` | object | Method-level allow/block list. Contains `allowed` (array of strings) and `blocked` (array of strings). | +| `mcp_primitives` | array | Per-primitive rate limits. Each entry targets one named tool, resource, or prompt. See below. | +| `json_rpc_methods` | array | Per-method rate limits. Each entry targets one JSON-RPC method name. See below. | + +### `mcp_access_rights` object + +Controls which primitives the consumer can invoke. Each of the three sub-objects follows the same structure. + +| Field | Type | Description | +|---|---|---| +| `tools.allowed` | array of strings | Explicit allowlist of tool names. If non-empty, only listed tools are accessible. Supports Go regular expressions. | +| `tools.blocked` | array of strings | Tools to block. Applied after `allowed`. Supports Go regular expressions. | +| `resources.allowed` | array of strings | Explicit allowlist of resource URIs. | +| `resources.blocked` | array of strings | Resource URIs to block. | +| `prompts.allowed` | array of strings | Explicit allowlist of prompt names. | +| `prompts.blocked` | array of strings | Prompt names to block. | + +Leave a sub-object empty (`{}`) to apply no restrictions for that primitive type. + +### `mcp_primitives` array + +Each entry defines a rate limit for one named primitive. + +| Field | Type | Required | Description | +|---|---|---|---| +| `type` | string | Yes | Primitive type. Accepted values: `tool`, `resource`, `prompt`. | +| `name` | string | Yes | Name of the primitive as exposed by the MCP server. Case-sensitive. | +| `limit.rate` | integer | Yes | Maximum calls allowed per time window. Set to `0` for unlimited. | +| `limit.per` | integer | Yes | Time window in seconds. Set to `0` for unlimited. | + +Entries are matched by `type` and `name` together. A tool named `weather` and a resource named `weather` are independent entries with independent counters. + +### `json_rpc_methods` array + +Each entry defines a rate limit for one JSON-RPC method. + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | Yes | The JSON-RPC method name, for example `tools/call` or `resources/read`. | +| `limit.rate` | integer | Yes | Maximum calls using this method per time window. | +| `limit.per` | integer | Yes | Time window in seconds. | + +### Top-level policy fields + +| Field | Type | Description | +|---|---|---| +| `rate` | integer | Global rate limit across all APIs in the policy. | +| `per` | integer | Time window for the global rate limit, in seconds. | +| `quota_max` | integer | Maximum total calls in the quota period. Set to `-1` for unlimited. | +| `quota_renewal_rate` | integer | Quota renewal period in seconds. Common values: `86400` (daily), `604800` (weekly). | +| `key_expires_in` | integer | Key lifetime in seconds from creation. Set to `0` for no expiry. | + +--- + +## When multiple policies apply + +A consumer key can have multiple policies applied to it. Tyk merges MCP-specific fields as follows: + +- **Rate limits**: the most permissive limit wins. If two policies define a limit for the same primitive, the higher `rate` value is used. +- **`mcp_access_rights` and `json_rpc_methods_access_rights`**: `allowed` lists are merged by union (the consumer gains access to the combined set). `blocked` lists are also unioned, so a primitive blocked in any policy remains blocked. +- **Proxy access**: the consumer gains access to the union of all proxies listed across all their policies. + +--- + +## Applying a policy to a consumer + +A policy takes effect when it is applied to an access key. In the Tyk Dashboard, go to **Keys**, click **Add Key**, select the policy from the **Apply Policy** dropdown, and generate the key. Issue the key to the consumer; they include it in the `Authorization` header of every MCP request. + +See [Policies](/api-management/policies) for full details. + diff --git a/ai-management/mcp-gateway/quickstart.mdx b/ai-management/mcp-gateway/quickstart.mdx new file mode 100644 index 0000000000..f14045c2a7 --- /dev/null +++ b/ai-management/mcp-gateway/quickstart.mdx @@ -0,0 +1,168 @@ +--- +title: "MCP Gateway quickstart" +description: "Create your first MCP proxy in Tyk Dashboard in under five minutes. Connect to the Mock MCP Server and verify your proxy is routing traffic correctly, with no authentication required." +keywords: "MCP, Model Context Protocol, MCP Gateway, quickstart, MCP proxy, JSON-RPC, Tyk Dashboard, MCP Inspector" +sidebarTitle: "Quickstart" +--- + +This guide gets you from zero to a working MCP proxy in minutes. An MCP proxy sits between an AI agent and a remote MCP server, routing requests, giving you visibility over every tool call, and letting you apply governance policies without touching the upstream server. + +You'll create a proxy to the [Tyk Mock MCP Server](https://github.com/TykTechnologies/tyk-mock-mcp-server), connect to it with [MCP Inspector](https://github.com/modelcontextprotocol/inspector), and verify that tool calls are routing correctly through Tyk. + +--- + +## Before you begin + +- A running Tyk Gateway (v5.13 or later) connected to your Tyk Dashboard. See [Self-managed](/getting-started/quick-start) +- A Dashboard user account with MCP write permissions +- Go 1.22 or later, or Docker (to run the Mock MCP Server) +- Node.js 18 or later (to run MCP Inspector). If you don't have it, download it from [nodejs.org](https://nodejs.org/en/download). + +--- + +## Instructions + +### Step 1: Start the Mock MCP Server + +The Mock MCP Server is the upstream your proxy will route traffic to. It exposes 15 tools across six categories (users, posts, products, analytics, utilities, and streaming) and requires no configuration or credentials. + +1. Start the Mock MCP Server using Go or Docker: + + + + ```bash + git clone https://github.com/TykTechnologies/tyk-mock-mcp-server.git + cd tyk-mock-mcp-server + go build -o tyk-mock-mcp-server . + ./tyk-mock-mcp-server + ``` + + + ```bash + docker run -p 7878:7878 ghcr.io/tyktechnologies/tyk-mock-mcp-server:latest + ``` + + + +2. Confirm the server is running on `http://localhost:7878`. Leave it running. + + +Your Tyk Gateway must be able to reach `localhost:7878`. If your gateway runs in Docker or on a remote host, replace `localhost` with the appropriate hostname or IP address. + + +--- + +### Step 2: Create the MCP proxy + +1. In the Tyk Dashboard sidebar, click **MCP**, then click **Add MCP Proxy**. + + ![Create MCP proxy](/img/ai-management/mcp-create-mcp.png) + + This opens the three-step creation wizard. + +2. **Name your proxy.** Enter `Mock MCP Server`. Tyk derives the listen path from the name automatically. Click **Continue**. + + ![Name your MCP proxy](/img/ai-management/mcp-quickstart-name-server.png) + +3. **Set the upstream URL.** Enter `http://localhost:7878/mcp`. Click **Continue**. + + ![Set the upstream URL](/img/ai-management/mcp-quickstart-server-url.png) + +4. **Connect gateways.** Select your gateway instances, or leave blank to deploy to all gateways. Click **Finish**, then click **Save MCP Proxy**. + + ![Deploy to gateways](/img/ai-management/mcp-quickstart-deploy-gateways.png) + + The Dashboard displays "MCP proxy successfully created". + +--- + +### Step 3: Find your MCP endpoint + +1. Click **Edit** to open the proxy designer. + +2. Find the **MCP Proxy URL** at the top of the page and append `/mcp` to get your MCP endpoint. + + ![MCP Proxy URL](/img/ai-management/mcp-quickstart-proxy-url.png) + + For example, if the Dashboard shows `https://my-gateway.example.com/mock-mcp-server`, your MCP endpoint is: + + ``` + https://my-gateway.example.com/mock-mcp-server/mcp + ``` + +3. Note this URL down; you'll enter it into MCP Inspector in the next step. + +--- + +### Step 4: Connect with MCP Inspector + +MCP Inspector is a browser-based tool for testing MCP servers. It handles the session handshake, lists available tools, and lets you call them interactively. + +1. Start MCP Inspector: + + ```bash + npx @modelcontextprotocol/inspector + ``` + + MCP Inspector downloads automatically on first run. + +2. Open the URL printed in your terminal. + +3. Set **Transport Type** to `Streamable HTTP`. + +4. Set **URL** to your MCP endpoint from Step 3. + +5. Click **Connect**. + + ![MCP Inspector connect](/img/ai-management/mcp-quickstart-mcp-inspector-connect.png) + +--- + +### Step 5: Call a tool + +1. Click the **Tools** tab. You'll see all 15 Mock MCP Server tools listed: Tyk has proxied the `tools/list` response from the upstream. + + {/* TODO: Add screenshot of MCP Inspector Tools tab showing the 15 tools listed */} + +2. Select **get_users** and click **Run**. The Mock MCP Server responds with a sample user list. + + {/* TODO: Add screenshot of the get_users tool response in MCP Inspector */} + +The request travelled from MCP Inspector → Tyk Gateway → Mock MCP Server → back through Tyk → MCP Inspector. Your proxy is working. + +--- + +### Step 6: View the call in analytics + +1. In the Tyk Dashboard sidebar, go to **Monitoring** → **Activity by MCP**. + +2. Check that the `tools/call` request appears under **Primitives Traffic** and **Most Used Primitives**, with `get_users` listed as the invoked tool. The `initialize` handshake from MCP Inspector appears separately with no primitive name, as expected for a session lifecycle call. + + {/* TODO: Add screenshot of the Activity by MCP analytics page showing the get_users call recorded under Primitives Traffic */} + + +Analytics data is written by Tyk Pump asynchronously. Allow a few seconds after making a call before checking the analytics page. If no data appears, verify that analytics recording is enabled and that Tyk Pump is running and connected to your storage backend. + + +--- + +## Troubleshooting + +**Connection refused in MCP Inspector**: Check that your Tyk Gateway is running and that the MCP endpoint URL is correct. Confirm the Mock MCP Server is running on port `7878`. + +**No tools listed**: The proxy connected but the upstream is not reachable. Confirm the Mock MCP Server is running. If your gateway runs in Docker, replace `localhost` in the upstream URL with `host.docker.internal`. + +--- + +## What's next + +You have a working MCP proxy routing traffic to the Mock MCP Server. The next step is to secure it, adding authentication so only authorized agents can connect. + +**[How to secure an MCP proxy →](/ai-management/mcp-gateway/how-to-proxy-remote-mcp)** + +After that, the series continues with: + +- **Restrict tool access**: Configure a tool allowlist so agents can only call the tools you have approved. See [Block an MCP Tool](/ai-management/mcp-gateway/how-to-block-tool). +- **Create access tiers**: Use policies to define different levels of access for different agents. See [MCP proxy policies](/ai-management/mcp-gateway/policies). +- **Set up token exchange**: Use RFC 8693 token exchange so the inbound agent token is replaced with a backend-scoped token before reaching the upstream MCP server. See [Token exchange](/api-management/authentication/token-exchange). +- **Understand the concepts**: See [MCP Gateway: Core Concepts](/ai-management/mcp-gateway/core-concepts) for the mental model behind sessions, middleware levels, and policies. diff --git a/ai-management/mcps/api-to-mcp.mdx b/ai-management/mcps/api-to-mcp.mdx new file mode 100644 index 0000000000..c9f311ed2e --- /dev/null +++ b/ai-management/mcps/api-to-mcp.mdx @@ -0,0 +1,238 @@ +--- +title: "Natural-language interaction with your APIs (API to MCP)" +description: "Enable AI assistants to safely and dynamically interact with your existing APIs using Tyk's API to MCP tooling." +keywords: "AI MCP, API-to-MCP, Tyk AI MCP" +sidebarTitle: "API to MCP" +--- + +## Overview + +**API to MCP** enables AI assistants to safely and dynamically interact with your existing APIs. It allows non-technical users to access API functionality through natural language, while developers retain full control over what endpoints are exposed and how they are accessed. + +This allows AI tools to interpret, invoke, and structure API operations without requiring any backend modifications. + +**Use this tool to:** +- Expose your APIs for AI interaction +- Allow AI assistants to understand and call API operations +- Configure basic access controls (e.g., filtering operations and setting headers) to manage how AI tools interact with your APIs + +If you're looking for quick setup, [jump to Quick Start](#quick-start). For deeper understanding, see [How It Works](#how-it-works) and [Use Cases](#use-cases). + +```mermaid +graph LR + A["Your OpenAPI"] --> B["Tyk API-to-MCP Tool"] + B --> C["Tyk MCP Server"] + D["AI Assistant"] <--> C + C <--> E["Your API"] + + style A fill:#ffffff,stroke:#D1D1E0,stroke-width:1px,font-size:18px + style B fill:#d5f5e3,stroke:#D1D1E0,stroke-width:1px,font-size:18px + style C fill:#d5f5e3,stroke:#D1D1E0,stroke-width:1px,font-size:18px + style D fill:#eeeeee,stroke:#D1D1E0,stroke-width:1px,font-size:18px + style E fill:#ffffff,stroke:#D1D1E0,stroke-width:1px,font-size:18px + + linkStyle default stroke-width:2px +``` + +## Key Features +- **Dynamic OpenAPI Loading:** Load specifications from local files or HTTP/HTTPS URLs +- **OpenAPI Overlay Support:** Apply overlays to customize specifications +- **Flexible Operation Filtering:** Include/exclude specific operations using glob patterns +- **Comprehensive Parameter Handling:** Preserves formats and includes metadata +- **Built-in Access Control & Security:** Control which endpoints are exposed, enforce authentication (API keys, OAuth, etc.), and add custom headers to all API requests, for secure AI access +- **Authentication Support:** Handles API keys, OAuth tokens, and other security schemes +- **MCP Extensions:** Support for custom x-mcp extensions to override tool names and descriptions +- **Multiple Integration Options:** Works with Claude Desktop, Cursor, Vercel AI SDK, and other MCP-compatible environments + +Check the [complete features list](https://github.com/TykTechnologies/api-to-mcp#features) is available in Tyk's *api-to-mcp* GitHub repository. + +## Quick Start + + +To get started quickly, the primary way to use it is by configuring your AI assistant to run it directly as an MCP tool. + +### Requirements +- [Node.js v18+](https://nodejs.org/en/download) installed +- An accessible OpenAPI specification, e.g. `https://petstore3.swagger.io/api/v3/openapi.json` (could be a local file as well) +- Claude Desktop (which we show in this example) or other MCP-compatible AI assistant that supports connecting to external MCP-compatible tool servers (e.g. Cursor, Vercel AI SDK, Cline extension in VS Code etc.) + +### Configure your AI Assistant + +To connect the tool with Claude Desktop or other MCP-compatible assistants, you need to register it as an MCP server. Most AI assistance share similar MCP server definition. This is the definition for *api-to-mcp* with petstore as the OpenAPI: + +```json +{ + "mcpServers": { + "api-tools": { + "command": "npx", + "args": [ + "-y", + "@tyk-technologies/api-to-mcp@latest", + "--spec", + "https://petstore3.swagger.io/api/v3/openapi.json" + ], + "enabled": true + } + } +} +``` + +**Step 1.** +To enable the tool, paste the above configuration into your AI assistant’s MCP config file + +- **Claude Desktop**: For MacOS, you need to update `~/Library/Application Support/Claude/claude_desktop_config.json`. See the [Claude Desktop setup instructions](https://github.com/TykTechnologies/api-to-mcp?tab=readme-ov-file#setting-up-in-claude-desktop) for Windows OS and more customization options. +- **Cursor**: See the [Cursor setup guide](https://github.com/TykTechnologies/api-to-mcp#cursor) for instruction on setting it with Cursor. + +**Step 2.** +Once connected, ask the AI to perform an operation (e.g., "List all pets" or "Create a new user"). + +## How It Works + +### User flow in API to MCP user flow + +1. **Input**: Your OpenAPI specification (required) and optional overlays +2. **Processing**: The API to MCP tool loads the spec, applies any overlays, and transforms API operations into MCP tools +3. **Runtime**: The MCP server exposes these tools to AI assistants, which are now discoverable to the AI assistant +4. **Execution Flow**: When you ask the AI assistant a question, it calls the tool (via the MCP server), which translates the request, forwards it to your API, and returns a formatted response. + + +```mermaid +flowchart LR + subgraph "Input" + A["OpenAPI Specification"] + B["Optional Overlays"] + end + + subgraph "API to MCP Tool" + C["1. Load & Parse
OpenAPI Spec"] + D["2. Apply Overlays
(if provided)"] + E["3. Transform API Operations
into MCP Tools"] + F["MCP Server"] + end + + subgraph "Runtime" + G["4. AI Assistant
Discovers Tools"] + H["AI Assistant
Calls Tools"] + I["Your API"] + end + + A -->|YAML/JSON| C + B -->|Optional| D + C --> D + D --> E + E -->|Register Tools| F + F -->|Expose Tools| G + G --> H + H -->|Request| F + F -->|Translate & Forward| I + I -->|Response| F + F -->|Format & Return| H + + classDef input fill:#f9f,stroke:#333,stroke-width:2px,font-size:18px; + classDef tool fill:#bbf,stroke:#333,stroke-width:2px,font-size:18px; + classDef runtime fill:#bfb,stroke:#333,stroke-width:2px,font-size:18px; + + class A,B input; + class C,D,E,F tool; + class G,H,I runtime; + +linkStyle default stroke-width:2px +``` + +### Request lifecycle: how an AI assistant invokes an API tool + +The following diagram illustrates the flow of a request through the system at runtime: + +```mermaid +sequenceDiagram + participant AI as "AI Client" + participant MCP as "MCP Server" + participant Tool as "Tool Handler" + participant API as "API Client" + participant Target as "Target API" + + AI->>MCP: Invoke Tool + MCP->>Tool: Execute Tool Handler + Tool->>Tool: Extract Parameters + Tool->>Tool: Validate Input + Tool->>API: executeApiCall() + API->>API: Apply Security + API->>API: Construct Request + API->>Target: Make HTTP Request + Target-->>API: HTTP Response + + alt Successful Response + API-->>Tool: Success Response + Tool->>MCP: Format MCP Result + MCP-->>AI: Tool Execution Success + else Error Response + API-->>Tool: Error Response + Tool->>MCP: Map to MCP Error + MCP-->>AI: Tool Execution Error + end + +``` + +API to MCP can be found in [api-to-mcp GitHub repository](https://github.com/TykTechnologies/api-to-mcp) + +## Use cases + + +Use **API to MCP** when you need to: + +- **Connect AI Assistants to Existing APIs** - Let AI tools understand and call your existing API operations using natural language — no code changes needed, just [configuration](https://github.com/TykTechnologies/api-to-mcp/#configuration). + +- **Create a Unified Interface for AI Systems** - Standardize how APIs are accessed by AI across your organization with a consistent protocol (MCP). + +- **Control API Access for AI** - Filter which operations are available to AI, apply authentication, and monitor usage securely. + +- **Improve API Discoverability** - Enable AI systems to automatically list available endpoints, input parameters, and expected responses. + +- **Test APIs Using AI** - Use AI assistants to generate test inputs, invoke endpoints, and validate responses in a conversational way. Natural-language test generation and feedback. + +- **Auto-docs & validation** - Use AI to test, describe, or troubleshoot APIs in a conversational way. Natural-language test generation and feedback. + +- **Workflow Automation** - Connect APIs and AI logic in real time to automate workflows and streamline processes. + +--- + +## Best Practices + +- **Start small**: Only expose safe, limited endpoints +- **Use filters**: allow list or block list endpoints as needed +- **Secure your APIs**: Pass tokens, headers, or keys securely +- **Track usage**: Monitor tool access and patterns +- **Version specs**: Maintain OpenAPI version control +- **Use env vars**: Don't hardcode secrets in CLI or config +- **Validate safely**: Test in staging before going live + +--- + +## Customize your own version of the api-to-mcp tool + +If you'd like to share your MCP with a predefined OpenAPI spec and configuration, you can customize this tool to fit your needs. Useful for sharing pre-configured setups with others. + +By creating a customized version, others can use the tool with minimal configuration --- no need to manually specify specs or overlays. + +Refer to the [customization and publishing guide](https://github.com/TykTechnologies/api-to-mcp?tab=readme-ov-file#customizing-and-publishing-your-own-version) in the *api-to-mcp* repository for step-by-step instructions. + +--- + +## FAQs + +**Does this work with GraphQL?** +Not currently — OpenAPI REST APIs only. + +**How do I secure my API requests?** +Use `--headers`, environment variables. Check the [configuration section](https://github.com/TykTechnologies/api-to-mcp/tree/main#configuration) for more details. + +**Can I hide or rename tools?** +Yes — use `x-mcp` extensions and filters. + +**What AI tools are supported?** +Any tool that supports MCP: Claude, Cursor, VS Code, and more. + + +## Summary + +API to MCP transforms OpenAPI specs into AI-compatible tools using the MCP standard. It enables your AI stack to dynamically understand, test, and invoke your existing APIs securely — without modifying your existing backend. diff --git a/ai-management/mcps/dashboard-api-to-mcp.mdx b/ai-management/mcps/dashboard-api-to-mcp.mdx new file mode 100644 index 0000000000..80edaada0b --- /dev/null +++ b/ai-management/mcps/dashboard-api-to-mcp.mdx @@ -0,0 +1,105 @@ +--- +title: "Natural-language interaction with Tyk Dashboard (API to MCP)" +description: "Talk to Tyk Dashboard like a person using AI tools" +keywords: "AI MCP, Dashboard API-to-MCP, Tyk Dashboard API MCP, Dashboard API, Talk to Tyk Dashboard, AI Management" +sidebarTitle: "Dashboard API to MCP" +--- + +## Overview + +Use `tyk-dashboard-mcp` to expose your **Tyk Dashboard API** to AI assistants like Claude, Cursor, or VS Code extensions — enabling natural-language interaction with your Tyk Dashboard. + +This tool is a preconfigured fork of [api-to-mcp GitHub repository](https://github.com/TykTechnologies/api-to-mcp), designed specifically for the *Tyk Dashboard* API. It comes bundled with a predefined OpenAPI spec and overlays, so you don’t need to configure much manually. + +Explore the core functionality in the [API to MCP guide](/ai-management/mcps/api-to-mcp). + + +## Use Cases + +Once connected, you, with your AI assistants, can perform helpful actions on your Tyk Dashboard using natural language. For example: +- **Explore your API landscape** - List APIs, describe endpoints in plain English, review policies +- **Query Dashboard settings for audits or support tasks** - List users and keys +- **Automate admin tasks** - Create or update API definitions (e.g., OAS-based APIs) through AI-driven flows, reducing manual clicks (please note that we haven't documented this just yet) +- **Power AI developer tools** - Use this as a backend for developer assistants like Claude, Cursor, or VS Code extensions to guide devs while on boarding and using Tyk Dashboard on daily basis. Ideal for internal use cases like AI-driven dashboards, documentation bots, or dev portals powered by LLMs. +- **Build internal chatbots** - Create internal tools that let team members ask questions like "What APIs are active?" or "What's the global rate limit defined API X?" + + +## Setup Instructions + +**Step 1.** Use the following MCP server config for Claude Desktop, Cursor, Cline or any other MCP-compatible tool: + +```json +{ + "mcpServers": { + "tyk-dashboard-api": { + "command": "npx", + "args": [ + "-y", + "@tyk-technologies/tyk-dashboard-mcp", + "--targetUrl", + "https://your-dashboard-domain.com", + "--headers", + "{\"Authorization\":\"{dashboard-API-key}\"}" + ], + "enabled": true + } + } +} +``` + +Refer to your assistant’s docs for where to place this config — e.g. +- `claude_desktop_config.json` for [Claude configuration](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server) +- `.cursor-config.json` for [Cursor configuration](https://docs.cursor.com/context/model-context-protocol#configuring-mcp-servers) +- `cline_mcp_settings.json` for [Cline configuration](https://docs.roocode.com/features/mcp/using-mcp-in-roo#configuring-mcp-servers) (as a VS Code extension). + +**Step 2.** +Once connected, ask your AI assistant to perform an operation (e.g., "List all apis" or "Create a new user"). + +## Examples + +Here you can see the response of asking the *Cline* in VS Code: + +1. Task: *Show me the Tyk dashboard api endpoint to create apis* + +Screenshot of the response to request of AI to create a new user + +
+ +2. Task: *Please create a new user in tyk dashboard* + +Screenshot of the response to request of AI to create a new user + +## Tips + +- You don’t need to manually define an OpenAPI spec — this tool includes the official Tyk Dashboard OpenAPI spec. +- You can fork or extend the tool if you want to include additional internal APIs alongside the dashboard. +- It's an open source and you can find it in [tyk-dashboard-mcp GitHub repository](https://github.com/TykTechnologies/tyk-dashboard-mcp) + +## FAQs + +**How is this different from `api-to-mcp`?** +`tyk-dashboard-mcp` is a customised version which is preconfigured for the Tyk Dashboard API. No need to specify your own spec. + +**Does this expose all dashboard functionality?** +Only the operations defined in the OpenAPI spec. You can customize the access list to show/hide more. In the following MCP server config the `--whitelist` to only allow access to getAPIs operation and to only allow to create Tyk OAS definitions: + +```json +{ + "mcpServers": { + "tyk-dashboard-api": { + "command": "npx", + "args": [ + "-y", + "@tyk-technologies/tyk-dashboard-mcp", + "--targetUrl", + "https://your-dashboard-domain.com", + "--headers", + "{\"Authorization\":\"{dashboard-API-key}\"}", + "--whitelist", + "getApis*,POST:/api/apis/oas", + ], + "enabled": true + } + } +} +``` diff --git a/ai-management/mcps/overview.mdx b/ai-management/mcps/overview.mdx new file mode 100644 index 0000000000..43cac4d77e --- /dev/null +++ b/ai-management/mcps/overview.mdx @@ -0,0 +1,53 @@ +--- +title: "Tyk MCP Servers" +description: "A comprehensive guide to Model Context Protocol (MCP) servers in Tyk and how they extend AI capabilities." +keywords: "AI MCP, MCPs in Tyk, Model Context Protocol" +sidebarTitle: "Overview" +--- + +## MCP capabilities + +[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) servers help AI systems securely interact with external services and tools. They establish structured, governed connections that integrate seamlessly with your Tyk environment. + +## What are MCPs? + +Model Context Protocol (MCP) servers extend AI systems by exposing external services, tools, and resources in a standardised way. They act as bridges between AI applications and external systems, securely managing authentication, access, and execution. + +With Tyk MCP Servers, your AI agents can: + +- Access external data sources and APIs +- Execute specialised tools and functions +- Interact with system resources +- Retrieve contextual information + +MCPs use a defined protocol to connect AI agents with external systems, expanding AI capabilities while maintaining governance and control. + +## Why standardisation matters + +The MCP specification standardises how AI agents discover and interact with external capabilities. This helps: + +- **Simplify integration** across diverse systems +- **Enhance security** through consistent architecture +- **Promote interoperability** with different vendor solutions +- **Improve governance** when managing AI systems at scale + +## MCP for Enterprise use + + +Tyk extends the MCP model for enterprise deployments with the following capabilities: + +- **Remote MCP catalogues and server support** – Expose internal APIs and tools to AI assistants securely without requiring local installations. +- **Secure local MCP server deployment** – Deploy MCP servers within controlled environments, integrated with Tyk AI Gateway for monitoring and governance. +- **Standardised protocols** – Maintain interoperability standards for seamless integration into existing workflows. + +These features enable enterprises to scale AI integrations securely while ensuring compliance and operational control. + +## Ready-to-use MCP options + +Tyk offers several ready-to-use MCP integrations: + +- **[API to MCP](/ai-management/mcps/api-to-mcp)** – Convert existing APIs (via OpenAPI/Swagger specs) into MCP-accessible tools. +- **[Dashboard API to MCP](/ai-management/mcps/dashboard-api-to-mcp)** – Expose the Tyk Dashboard API for management and monitoring. +- **[Tyk Docs MCP](/ai-management/mcps/tyk-docs-mcp)** – Provide AI access to searchable Tyk documentation. + +For more information on implementing MCPs, [contact the Tyk team](https://tyk.io/contact/) do discuss your specific use cases. diff --git a/ai-management/mcps/tyk-docs-mcp.mdx b/ai-management/mcps/tyk-docs-mcp.mdx new file mode 100644 index 0000000000..76c4f2162f --- /dev/null +++ b/ai-management/mcps/tyk-docs-mcp.mdx @@ -0,0 +1,93 @@ +--- +title: "Natural-language interaction with Tyk Docs (MCP)" +description: "Talk to Tyk documentation like a person using AI tools. Use Docs MCP to enable AI assistants to search and retrieve information from Tyk documentation." +keywords: "AI MCP, Tyk Docs, AI Documentation Search, Talk to Tyk Docs" +sidebarTitle: "Tyk Docs MCP" +--- + +## Overview + +Tyk Docs [MCP](https://modelcontextprotocol.io/introduction) exposes the Tyk documentation to AI assistants. Instead of searching manually, users can ask natural-language questions and get answers backed by Tyk docs. The tool makes AI-assisted support, troubleshooting, and documentation exploration fast and reliable. + + +Here you can see the AI assistant chooses to use Tyk Docs MCP (*Cline* in VS Code) while answering the query *How do I set a rate limit for a Tyk API?*: + +Screenshot of the response to request of AI to create a new user + + +Screenshot of the response to request of AI to create a new user + + +## Key Features + +- **Semantic search** — finds the most relevant content, not just keyword matches +- **Contextual results** — includes sections around your result for better understanding +- **Product filters** — limit results by product (Gateway, Dashboard, etc.) +- **Includes links** — jump straight into the relevant section of the docs +- **Answer snippets** — shows concise answers when possible +- **Always up to date** — syncs with the latest Tyk documentation + + +## Use Cases + + +Let AI do the digging — here’s how teams use Tyk Docs MCP: + +- **First-line support** - *How do I set a rate limit for a Tyk API?* +
AI can help answer questions about Tyk products, features, and usage cases. + +- **Feature implementation help** - *How do I enable JWT authentication in Tyk Gateway?* +
AI can help developers use Tyk's features by providing ad hoc step-by-step instructions and examples. + +- **Troubleshooting guidance** - *I'm seeing 'Auth field missing' error. What does that mean?* +
AI can help identify issues and provide guidance on how to resolve them. + +- **Fast API reference** - *What fields are in the /apis response?* +
AI can help developers quickly find the information they need to implement Tyk's features. + +- **Discover what’s possible** - *What analytics tools does Tyk include?* +
AI can help developers discover new ways to use Tyk's features and capabilities. + + +## Quick Start + +To get started, connect your AI assistant to the Tyk Docs MCP server, which is hosted remotely at `https://tyk.io/docs/mcp`. + +### Requirements +- [Node.js v18+](https://nodejs.org/en/download) installed (needed to run `npx`) +- Internet access to reach `tyk.io` +- An MCP-compatible AI assistant, such as Claude Code, Claude Desktop, Cursor, VS Code, or Codex + +### Configure your AI Assistant + +**Step 1.** +The easiest way to connect is the [`add-mcp`](https://github.com/neon-solutions/add-mcp) CLI. It detects the AI assistants installed on your machine and adds the server to whichever ones you choose: + +```bash +npx add-mcp https://tyk.io/docs/mcp +``` + +You can also click **Add MCP** in the menu at the top of any Tyk Docs page to get the same command, pre-filled for that page. + +Mintlify Context Menu Options + +If your assistant supports remote HTTP MCP servers but isn't detected automatically, add the server to its MCP configuration manually instead: + +```json +{ + "mcpServers": { + "tyk-docs": { + "type": "http", + "url": "https://tyk.io/docs/mcp" + } + } +} +``` + +**Step 2.** +Once connected, ask the AI to perform an operation as suggested in the [use cases above](#use-cases). + + +## How It Works Under the Hood + +Tyk Docs MCP is Mintlify's built-in MCP server for the Tyk Documentation site. It indexes the live, published documentation, so results always reflect the current content, with no local package to install or keep up to date. Aside from a `submit_feedback` tool, which lets a connected assistant report a documentation issue such as an incorrect, outdated, or confusing page directly to the Tyk docs team, the server is read-only. diff --git a/ai-management/overview.mdx b/ai-management/overview.mdx new file mode 100644 index 0000000000..8332c5835c --- /dev/null +++ b/ai-management/overview.mdx @@ -0,0 +1,102 @@ +--- +title: "AI management" +description: "An overview of Tyk's AI management solutions, including AI Studio for governance and deployment, and Model Context Protocol (MCP) servers for secure AI integrations." +keywords: "Tyk AI management, AI Studio, Tyk MCP Servers" +sidebarTitle: "Overview" +--- + +As artificial intelligence becomes increasingly integrated into enterprise systems, organizations need structured, secure, and governed approaches to manage AI capabilities effectively. Tyk's AI management solutions are designed to help enterprises integrate, control, and scale AI applications while maintaining compliance and security. + +## Secure AI for the enterprise + +Tyk's AI management solutions address key challenges in AI governance, security, and integration. They enable organizations to deploy AI capabilities while maintaining oversight, managing risks, and meeting enterprise standards. + +## AI integration architecture and its importance + +Integrating AI into existing systems requires a structured architecture that connects models, APIs, and specialised tools securely and efficiently. + +A managed AI integration architecture provides: + +- **Standardisation** to ensure interoperability across AI components +- **Security** across AI workflows and data interactions +- **Governance** to monitor and control AI usage and data +- **Scalability** for enterprise-wide deployment and increasing complexity +- **Interoperability** across vendors and services + +Without a structured approach, organizations risk fragmented solutions, security gaps, and unmanaged AI usage ("shadow AI"). By integrating AI into existing systems, enterprises can achieve a more secure and efficient approach to AI management. + +## Tyk’s AI management capabilities + +Tyk provides three key solutions for AI management: + +### [AI Studio](/ai-management/ai-studio/overview) + +Tyk AI Studio is a platform for managing and deploying AI applications securely and at scale. It provides: + +- **Centralised governance** with role-based access control and compliance tracking +- **Cost management** through usage monitoring and budgeting tools +- **Security features** including unified access controls and credential management +- **Developer enablement** via curated AI service catalogues +- **Collaboration tools** through intuitive AI interfaces + +AI Studio supports enterprises in reducing unauthorised AI usage by providing central management across all AI interactions. + +[Explore AI Studio](/ai-management/ai-studio/overview) + +### [MCP Gateway](/ai-management/mcp-gateway/overview) + +Tyk MCP Gateway puts Tyk's API governance layer directly in front of remote MCP servers — from GitHub Copilot and Slack to internal tools built by your own teams. It provides: + +- **Authentication and key management** with full OAuth 2.1 compliance, including Protected Resource Metadata discovery so MCP clients can self-configure +- **Access control** at the individual tool, resource, and prompt level — allowlist exactly which MCP primitives each agent can call +- **Traffic management** including per-tool rate limiting, timeouts, circuit breakers, and request size limits to protect upstream MCP servers +- **Unified credential management** — agents authenticate to Tyk once; Tyk handles per-vendor upstream credentials centrally +- **Observability** with per-tool analytics surfaced alongside the rest of your API traffic in the Tyk Dashboard + +MCP Gateway addresses a practical governance gap: as AI agents begin calling remote MCP servers, direct connections bypass every organisational control. Routing through Tyk makes every agent connection managed, auditable, and policy-enforced. + +[Explore MCP Gateway](/ai-management/mcp-gateway/overview) + +### [Tyk MCP Servers](/ai-management/mcps/overview) + +The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) provides a standardised method for AI components to interact with external resources. + +With MCPs, organizations can: + +- **Integrate securely** with external AI providers and services +- **Build custom tools** for AI assistants and workflows +- **Access resources** such as files, APIs, and databases +- **Enhance AI workflows** with contextual information + +MCPs help expand AI system functionality by enabling secure, standardised interactions between services. + +[Explore Tyk MCP Servers](/ai-management/mcps/overview) + +### How they work together + +Tyk's three AI management capabilities are complementary: + +- **AI Studio** offers governance, monitoring, and development tooling for managing AI applications and LLM access. +- **MCP Gateway** governs AI agent traffic to remote MCP servers — applying authentication, access control, and observability at the gateway layer. +- **Tyk MCP Servers** provide secure, standardised connections from AI systems to external services and APIs. + +Together, they create a flexible, governed framework for managing AI applications and agent traffic at scale. + +## Next steps + +To start using Tyk's AI management capabilities: + +1. Explore the [AI Studio documentation](/ai-management/ai-studio/overview) +2. Learn how [MCP Gateway](/ai-management/mcp-gateway/overview) governs AI agent traffic to remote MCP servers +3. Review [Tyk MCP Servers](/ai-management/mcps/overview) and how they extend AI systems +4. [Request a demo](https://tyk.io/ai-demo/) to see the platform in action. + +## Key outcomes + +Tyk's AI management solutions are designed to: + +- **Reduce risk** through centralised access and monitoring +- **Improve efficiency** across AI development workflows +- **Enhance cost control** with usage and budgeting insights +- **Support compliance** with data protection and security standards +- **Enable scalable architectures** based on open protocols diff --git a/api-management/access-control/overview.mdx b/api-management/access-control/overview.mdx new file mode 100644 index 0000000000..7147b5cee7 --- /dev/null +++ b/api-management/access-control/overview.mdx @@ -0,0 +1,92 @@ +--- +title: "Access Control Overview" +description: "Understand how Tyk manages API access, limits consumption, and scales using Sessions, Keys, and Policies." +keywords: "Access Control, Sessions, Keys, Policies, Rate Limits, Quotas, Plans, Products" +sidebarTitle: "Overview" +--- + +## Introduction + +When managing APIs, one of the most critical tasks is controlling what access your API clients have to your services. You need to ensure that clients can only access the APIs, paths, and HTTP methods they are authorized for, while also enforcing consumption limits like rate limits and quotas to protect your upstream services. + +In Tyk, access control is built on three core concepts: **Sessions**, **Keys**, and **Policies**. Understanding how these three elements interact is the key to managing API access securely and at scale. + +## The Foundation: Session + +At the heart of Tyk's access control is the **Session**. + +Every authenticated client connection in Tyk is represented by a Session, which is typically stored in the Data Plane's Redis database. This object is the ultimate source of truth for a client's current state. It contains: +- **Access Rights:** Which APIs, versions, paths, and methods the client is allowed to access. +- **Consumption Limits:** The client's rate limit (requests per second) and quota (requests per month/week/etc.). +- **Current Usage:** Real-time tracking of how much of their quota they have consumed. +- **Metadata:** Custom data associated with the client that can be used for routing, transformations, or analytics. + +When a request arrives, Tyk evaluates the Session to determine if the request should be allowed through to your upstream service. + +## Identifying the Client: Keys and Authentication + +If the Session dictates *what* a client can do, how does Tyk know *who* the client is? This is where **Keys** and Authentication come in. + +A "Key" is simply the mechanism used to resolve the Session. While Tyk supports many different authentication methods, they all fall into one of three categories based on how the Session is created: + +### 1. Pre-Registered Sessions + +For most authentication methods, the client must be pre-registered with Tyk. A Session is created and stored in Redis at the time of registration. When a request arrives, Tyk uses the provided credentials as a lookup key to retrieve this existing session. + +This category includes: +- [**Auth Tokens**](/api-management/authentication/bearer-token): The token itself is the lookup key. +- [**Basic Authentication**](/api-management/authentication/basic-authentication): The username acts as the lookup key. +- [**HMAC Signatures**](/basic-config-and-security/security/authentication-authorization/hmac-signatures) The Key ID provided in the request is the lookup key. +- [**Certificate Auth**](/api-management/authentication/certificate-auth) A hash of the client's TLS certificate acts as the lookup key. + +### 2. Dynamically Generated Sessions + +For [**JWT Auth**](/api-management/authentication/jwt-authorization), Tyk does not require a pre-existing Session in its database prior to the first request. Instead, Tyk validates the token and dynamically generates the Session on the fly based on the token's claims. This Session is then persisted to Redis, with the [identity](/api-management/authentication/jwt-authorization#identifying-the-session-owner) as the lookup key. + +Regardless of the authentication method you choose, the end result is always a Session that Tyk uses to enforce access control. + +### 3. OAuth 2.0: Client-Delegated Sessions + +For [**OAuth 2.0**](/api-management/authentication/oauth-2), the process is a hybrid. You first register an **OAuth Client** and assign it a Policy. When the client successfully completes an OAuth flow (such as Client Credentials or Authorization Code), Tyk dynamically generates an access token and a corresponding Session based on the Client's Policy. This Session is then persisted to Redis, and for subsequent API requests, the access token acts as a standard lookup key. + +### Multiple Authentication + +When more than one method is required to authenticate the client request, the Session that Gateway should use to determine access rights and to control and track usage is determined according to [these rules](/basic-config-and-security/security/authentication-authorization/multiple-auth#understanding-authentication-modes). + +## Managing at Scale: Policies + +While you could configure access rights and limits directly on individual Sessions, this becomes unmanageable at scale. If you have 10,000 clients and you want to grant them access to a newly published API, updating 10,000 individual Sessions in Redis is not practical. + +This is where **Policies** come in. A Policy is a templated set of rules that defines access rights and consumption limits. + +Instead of writing these rules directly into the Session, the Session simply references a **Policy ID**. When a client makes a request, Tyk loads the Session, sees the Policy ID, and applies the rules defined in that Policy. If you need to update access for all 10,000 clients, you simply update the Policy once, and the changes take effect whenever a Key using that Policy is presented. + +### Partitioned Policies vs. Monolithic Policies + +Tyk supports two approaches to policies: +- **Monolithic Policies:** A single policy that defines both access rights and consumption limits. +- **Partitioned Policies:** Multiple policies applied to a single session, where one policy might define access rights (e.g., "Product A") and another might define consumption limits (e.g., "Gold Plan"). + +**We strongly encourage the use of Partitioned Policies.** They offer much greater flexibility and reusability. + +If you are using the **Tyk Developer Portal**, you will see this concept in action. The Portal uses the terminology [**Products**](/portal/api-products) (policies that define API access rights) and [**Plans**](/portal/api-plans) (policies that define rate limits and quotas). Under the hood, these are simply partitioned policies working together to control the client's session. + + + There is currently a limitation when using the Dashboard UI to configure partitioned policies that an API must be selected even when the **Enforce Access Rights** option is not selected. The policy that is created will not grant access to that API, so any API can be selected. + + +## Turning Access On and Off + +Managing the lifecycle of client access is straightforward with Tyk: + +- **Key Revocation:** You can instantly revoke a client's access by deleting their Session from Redis. +- **Key Expiry:** Sessions can be configured with a Time-To-Live (TTL), after which they automatically expire and are removed. +- **Policy Disabling:** You can disable a Policy, which will immediately block access for all clients whose sessions rely on that Policy. + +## Next Steps + +Now that you understand the core concepts, you can dive deeper into how to implement them: + +- [Understanding Sessions](/api-management/access-control/sessions-and-keys/understanding-sessions) +- [Managing Session Lifecycle](/api-management/access-control/sessions-and-keys/session-lifecycle) +- [Creating and Managing Policies](/api-management/access-control/policies/managing-policies) \ No newline at end of file diff --git a/api-management/access-control/policies/applying-policies.mdx b/api-management/access-control/policies/applying-policies.mdx new file mode 100644 index 0000000000..48ad421422 --- /dev/null +++ b/api-management/access-control/policies/applying-policies.mdx @@ -0,0 +1,191 @@ +--- +title: "Applying Policies" +description: "Learn how Tyk links Policies to Sessions, how they are dynamically applied during a request, and the permitted combinations of different Policy types." +keywords: "Policies, Apply Policies, Dynamic Application, Partitioned Policies, Monolithic Policies" +sidebarTitle: "Applying Policies" +--- + +## Introduction + +Once you have created a Policy, the next step is to apply it to your clients' Sessions. This page explains exactly how Policies are linked to Sessions under the hood, how the Tyk Gateway dynamically applies them during a request, and the rules for combining different types of policies. + +## Policy IDs + +Policy objects have two separate identifiers: + +- **Policy ID (`id`)**: this is the identifier that should be used in all operations via the Gateway API, Dashboard API, in the Session's `apply_policies` array, and in the API definition's scope-to-policy mapping. This can be set by the user (though is not currently manually configurable in the Dashboard UI). +- **Database ID (`_id`)**: this is the internal reference for the Policy within Tyk Dashboard's database and cannot be modified by the user. + +Prior to Tyk Developer Portal v1.17.1 the Portal would incorrectly use `_id`, so the Dashboard API expected this in its Policy management endpoints. From Tyk 5.12.0 onwards there is full support for the `id` across the Tyk Dashboard API and users are recommended to use this exclusively. + +To mitigate against the risk of unexpected side effects, from Tyk 5.13.0 onwards the Policy ID can only contain the following characters: `a-z`, `A-Z`, `0-9`, `.`, `_`, `-`, `~`. If you need to bypass this restriction due to existing non-compliant Policy IDs, you can set the `allow_unsafe_policy_ids` configuration flag in your [Gateway](/tyk-oss-gateway/configuration#allow_unsafe_policy_ids) and [Dashboard](/tyk-dashboard/configuration#allow_unsafe_policy_ids) configuration. + +## Linking Policies to Sessions + +At the data level, a Policy is linked to a Session via the Session object's configuration. + +When you inspect a [Session object](/api-management/access-control/sessions-and-keys/understanding-sessions), you will see two fields related to policies: +- `apply_policies`: An array of strings containing the IDs of the Policies linked to this Session. This is the recommended way to link one or more policies. +- `apply_policy_id` *(deprecated)*: A single string that can contain a Policy ID. This is a legacy field from older versions of Tyk that only supported a single Policy per session and is now only provided as a fallback that is checked if the `apply_policies` array is empty. + +You can manage the association of Policies with Sessions from various touchpoints depending on your Tyk deployment: + +- **Developer Portal:** When [managing access requests](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/approve-requests), Tyk will automatically manage the linking of Policies relating to API Products and Plans to the client's Session. +- **Dashboard UI:** When creating or editing a "Key" (Session) in the Dashboard, you can select one or more Policies from the **Apply Policies** dropdown. +- **Tyk Operator:** Use the `pol_id` from the `SecurityPolicy` CRD's status when creating the Session. +- **Dashboard API:** When creating or updating a Session via the API, you append the Policy IDs to the `apply_policies` array in the Session object. +- **Gateway API (Open Source):** When creating or updating a Session via the API, you append the Policy IDs to the `apply_policies` array in the Session object. +- **OAuth Clients:** When creating an OAuth Client (via the Dashboard or API), you must associate it with a Policy. Whenever Tyk issues an OAuth access token for that client, it dynamically generates a Session based on that Policy and links the token to it. + +## Dynamic Application During a Request + +A common misconception is that when you link a Policy to a Session, Tyk permanently copies the Policy's rules into the Session object in the database. **This is not the case.** + +Instead, Tyk applies Policies **dynamically in memory** during the lifecycle of a request. Here is exactly what happens when a client makes a request: + +1. **Session Retrieval:** The Gateway extracts the client's [Key](/api-management/access-control/overview#identifying-the-client-keys-and-authentication) from the request and uses this to retrieve their Session object from the local cache or the Redis database. +2. **Session Cloning:** The Gateway creates a temporary, in-memory clone of the Session object. +3. **Policy Lookup:** The Gateway reads the `apply_policies` array from the cloned Session and retrieves the corresponding Policy definitions from its internal store. +4. **Dynamic Overlay:** The Gateway iterates through the linked Policies and overlays their rules (rate limits, quotas, access rights, etc.) onto the cloned Session object. +5. **Request Processing:** The Gateway uses this fully populated, temporary Session object to authorize and rate-limit the current request. +6. **Session Persistence:** After the request completes, if the client consumes quota, the Gateway updates the quota counters in the temporary Session before it is saved back to Redis to persist the values. See the [Request Quotas](/api-management/request-quotas#how-tyk-implements-quotas) section for more details. (Note: Rate limit counters are tracked in separate Redis keys and do not modify the Session object). + +Because this process happens dynamically on every request, any changes you make to a Policy are immediately reflected across all linked Sessions without requiring you to update the Sessions themselves. + +### Worked Example: Dynamic Application Flow + +Consider a scenario where a user has a base "Standard Tier" Policy and has just purchased a "Premium Reporting Add-on" Policy. Both Policy IDs are stored in their Session's apply_policies array. + +```mermaid +sequenceDiagram +    participant Client +    participant Gateway +    participant Redis as "Redis (Session Store)" +    participant PolicyStore as Policy Store + participant Upstream as Upstream Service + +    Client->>Gateway: Request with Auth Token +    Gateway->>Redis: Fetch Session +    Redis-->>Gateway: Session {apply_policies: ["standard_tier", "premium_addon"]} +    Gateway->>Gateway: Clone Session (In-Memory) +    Gateway->>PolicyStore: Fetch Policies +    PolicyStore-->>Gateway: Policy Definitions +    Gateway->>Gateway: Overlay "standard_tier" rules onto Clone +    Gateway->>Gateway: Overlay "premium_addon" rules onto Clone +    Gateway->>Gateway: Validate Request against Cloned Session +    alt Request Allowed + Gateway->>Upstream: Request is proxied + Upstream->>Gateway: 200 OK +        Gateway-->>Client: 200 OK + else Rate Limit Exceeded +        Gateway-->>Client: 429 Too Many Requests +    end +``` + +## Applying a Single Policy + +When a single Policy is linked to a Session, the Gateway dynamically overlays the Policy's configuration onto the Session during a request. + +**Which fields are updated?** +A Policy can update or replace the following sections of a Session: +- **Global Limits:** Rate limits (`rate`, `per`), quotas (`quota_max`, `quota_renewal_rate`), and GraphQL complexity (`max_query_depth`). +- **Access Rights:** The APIs the client can access, including allowed endpoints, restricted GraphQL types, and granular API-level rate limits and quotas. +- **Metadata and Tags:** Custom key-value pairs and tags used for analytics. +- **Security Settings:** HMAC and HTTP Signature validation flags. +- **Session Lifecycle:** Expiry settings and post-expiry actions. + +**What happens to existing Session values?** +If the Session object already has values configured in fields that are contained in the Policy, the Gateway handles them in three different ways: +1. **Overwritten:** Global limits (rate, quota, complexity), Access Rights, and Session Lifecycle settings are completely overwritten by the Policy. The Gateway explicitly clears these values from the Session before applying the Policy. +2. **Merged:** Metadata and Tags are merged. The Policy's tags are appended to the Session's existing tags, and metadata keys are combined. +3. **Preserved:** If the Policy does not define a specific section (for example, it declares no Access Rights), the Session's original values for that section are preserved. + +## Applying Multiple Policies + +Tyk allows you to link multiple Policies to a single Session, applying each Policy in turn to result in a final Session state that will be used for the request. This is where the concept of **Partitioned Policies** becomes powerful. + +### Partitioned Policies + +Instead of creating *monolithic* policies that define everything, you can create partial or *partitioned* policies that only update specific sections of a Session. + +To declare a Policy as partitioned, you configure the `partitions` object within the Policy definition. This object contains boolean flags that explicitly enable specific segments of the Policy: + +```json +{ + "partitions": { + "acl": false, + "rate_limit": true, + "quota": true, + "complexity": false, + "per_api": false + } +} +``` + +- **ACL (`acl`):** Only applies Access Rights (allowed APIs and endpoints). +- **Rate Limit (`rate_limit`):** Only applies rate limits and throttling settings. +- **Quota (`quota`):** Only applies quota maximums and renewal rates. +- **Complexity (`complexity`):** Only applies GraphQL query depth limits. +- **Per-API (`per_api`):** A special type of Policy where limits are tracked individually for each API rather than globally. + +#### Multiple Partitions on a Single Policy + +It is entirely possible (and common) for a single Policy to have **multiple partitions enabled simultaneously**. For example, you could create a Policy with both `rate_limit: true` and `quota: true` to handle all limits, while leaving `acl` disabled so access rights are inherited from elsewhere. + +### How Multiple Policies are Merged + +When multiple policies update the same section of a Session, Tyk merges them to be the **most permissive**: +- **Rate Limits:** Tyk calculates the duration between requests (`per / rate`). The Policy that allows the highest request rate (shortest duration between requests) wins. +- **Quotas and Complexity:** Tyk takes the largest `quota_max`, `quota_renewal_rate`, and `max_query_depth` across all applied policies. +- **Access Rights (ACLs):** Tyk takes the **union** of all allowed URLs and methods. If Policy A allows `GET /users` and Policy B allows `POST /reports`, the Session can access both. +- **GraphQL Restrictions:** Tyk takes the **union** of restricted types. A field is restricted if any applied policy restricts it. + + + Because `quota_renewal_rate` is the duration in seconds before the quota resets, taking the maximum value results in the most infrequent renewal period so be careful when combining Policies that set different quota limits. The resultant quota parameters may be taken from different policies so the effective quota may not match any of the applied policies. + + + +{/* TODO: Diagram — Layered "stack" diagram showing how multiple policies merge into an effective session. Bottom layer: Base Session object. Middle layer: Monolithic Policy ("Standard Tier") applying global rate limits and access rights. Top layers: Multiple Partitioned Policies ("Reporting Add-on", "Premium Support") side-by-side. Arrows show Gateway merging downward to produce "Effective Session". Callouts: "Access Rights: Union (Combined)" and "Rate Limits: Most Permissive (Maximum)". */} + + +### Permitted Combinations + +While you can mix and match most policies, the Tyk Gateway enforces strict validation rules to prevent unpredictable behavior. The following table outlines the permitted combinations when applying multiple policies to a single Session: + +| Policy Combination | Status | Description | +| :--- | :--- | :--- | +| **Multiple Monolithic** | ✅ Allowed | You can apply as many monolithic policies as you need. | +| **Multiple Partitioned** | ✅ Allowed | You can combine multiple partitioned policies to build modular access tiers. | +| **Monolithic + Partitioned** | ✅ Allowed | You can apply a base monolithic policy and overlay specific partitioned policies. | +| **Monolithic + Per-API** | ✅ Allowed | You can apply a base monolithic policy and overlay per-API limits. | +| **Partitioned + Per-API** | ❌ **Not Allowed** | The Gateway will reject this combination and log an error. You cannot mix standard partitioned policies with `per_api` policies on the same Session. | + +**Why is Partitioned + Per-API restricted?** + +A `per_api` Policy is intended for the declared limits to be scoped strictly to individual APIs. A standard partitioned Policy (such as a `rate_limit` partition) applies its limits across all APIs that the Session is permitted access to. + +If you were able to mix them, the merging logic would become ambiguous: should the global rate limit partition override the per-API limit, or vice versa? To prevent unpredictable access control and limit enforcement, the Gateway explicitly rejects this combination and will log an error (ErrMixedPartitionAndPerAPIPolicies). Furthermore, a single Policy cannot have both the per_api flag and any other partition flag enabled simultaneously. + + +## Managing Session Lifecycle with Policies + +Policies are often used to manage the lifecycle of a Session, including its expiration and active state. These settings are handled **globally**, completely independent of the partition logic. This means that any Policy (monolithic or partitioned) can set session lifecycle rules. + +**Validity period (`key_expires_in`)** +The length of time that the session is considered *valid* (before it "expires") is evaluated **when a session is first created** (for example, via the Gateway API or when dynamically generating a session for a JWT). It is not dynamically overlaid during a request. If multiple policies define a validity period, the Gateway uses the value from the **last Policy** in the `apply_policies` array that has `key_expires_in > 0`. The validity period is added to the current time when the session is created and set as the `expires` timestamp in the Session object in Redis. + +**Post-Expiry Actions** + +Unlike the expiry time, post-expiry settings (`post_expiry_action` and `post_expiry_grace_period`) are evaluated **dynamically in memory** on every request. If multiple policies define these fields, the **last Policy** in the `apply_policies` array will be used. This allows you to change the retention Policy for expired sessions after creation. + +**Temporarily Disabling a Session (is_inactive)** + +You can easily [disable a session](/api-management/access-control/sessions-and-keys/session-lifecycle#temporarily-revoking-access-to-a-key) temporarily by linking it to a Policy that has `is_inactive: true`. + +When multiple policies are applied, the Gateway evaluates the `is_inactive` flag dynamically on every request using a **logical OR** operation. If **any** of the linked policies has `is_inactive: true`, the session is immediately treated as inactive and the request will be rejected. + +This makes it incredibly easy to implement a "kill switch" Policy. You can create a single monolithic Policy with `is_inactive: true` and simply append its ID to the `apply_policies` array of any session you want to be able to suspend, without modifying other Policies. + + +If a Session has **any** policies linked to it, the Gateway ignores the Session's own `is_inactive` flag. You cannot disable a session by setting `is_inactive: true` directly on the Session object if it has policies applied; you **must** use a policy to disable it. + \ No newline at end of file diff --git a/api-management/access-control/policies/managing-policies.mdx b/api-management/access-control/policies/managing-policies.mdx new file mode 100644 index 0000000000..fcf988c242 --- /dev/null +++ b/api-management/access-control/policies/managing-policies.mdx @@ -0,0 +1,104 @@ +--- +title: "Managing Policies" +description: "Learn how to create, apply, update, and delete Tyk Policies." +keywords: "Policies, Manage Policies, Create Policy, Apply Policy, Delete Policy" +sidebarTitle: "Managing Policies" +--- + +## Introduction + +Once you understand how Policies work and how they are dynamically applied to Sessions, the next step is learning how to manage their lifecycle. This page covers how to create, apply, update, and delete Policies across the Tyk ecosystem. + +## Creating Policies + +You can create Policies using several different methods depending on your workflow: + +- **Dashboard UI:** You can create Policies interactively via the Tyk Dashboard by navigating to **System Management > Policies** and clicking **Add Policy**. +- **Dashboard API:** You can create a Policy by sending a request to the [`POST /api/portal/policies/`](https://tyk.io/docs/api-reference/policies/create-policy-definition) endpoint with the Policy object as the payload. +- **Developer Portal:** If you are using the Tyk Developer Portal, you use [API Products](/portal/api-products) to configure access to APIs and [API Plans](/portal/api-plans) to set rate and quota limits. These map onto [partitioned policies](/api-management/access-control/policies/applying-policies#partitioned-policies) within the Dashboard that are then linked to the Session created when access credentials are issued to a [Developer App](/portal/developer-app). +- **Gateway API (Open Source):** If you are using Tyk Open Source, you can create policies by sending a request to the Gateway's [`POST /tyk/policies/`](https://tyk.io/docs/api-reference/policies/create-a-policy) endpoint, or by adding the Policy to the [Policies file](/api-management/access-control/policies/managing-policies#configuring-tyk-open-source-to-use-file-based-policies). + +### Configuring Tyk Open Source to use file-based Policies + +When using Tyk Open Source Gateway, you can load Policies via the `POST /tyk/policies/` endpoint, or you can store Policies in a file that Gateway will then load. + +For the Gateway to expect file-based Policies you will need to add the `policies` section to your configuration file as follows: + +```json +"policies": { + "policy_source": "file", + "policy_record_name": "./policies/policies.json" +}, +``` + +The `policies.json` file should contain a single JSON object that contains a set of policy objects. The key for each object will be used as the Policy ID. + +For example: + +```json expandable +{ + "default": { + "rate": 1000, + "per": 1, + "quota_max": 100, + "quota_renewal_rate": 60, + "access_rights": { + "41433797848f41a558c1573d3e55a410": { + "api_name": "My API", + "api_id": "41433797848f41a558c1573d3e55a410", + "versions": [ + "Default" + ] + } + }, + "org_id": "54de205930c55e15bd000001", + "hmac_enabled": false + } +} +``` + +In this example we have only defined a single policy called `default`. + +## Associating Policies with Client Requests + +As explained in detail [here](/api-management/access-control/policies/applying-policies) Policies can be associated with client requests by: + +- registering the [Policy ID](/api-management/access-control/policies/applying-policies#policy-ids) with the client Session +- or, for APIs secured using JWT Authentication, with [**scope-to-policy mapping**](/api-management/authentication/jwt-authorization#identifying-the-tyk-policies-to-be-applied) that will be presented in the JWT's claims. +- or, for APIs secured using Tyk's OAuth 2.0 Authentication method, with the [client app](/api-management/authentication/oauth-2#manage-client-access-policies) + +## Updating Policies + +Updating a Policy is straightforward and follows the same methods as creation: + +- **Dashboard UI:** Navigate to **System Management > Policies**, select the Policy, and modify its settings. +- **Dashboard API:** Send a request to `PUT /api/portal/policies/{policy_id}` with the updated Policy object as the payload. +- **Gateway API (Open Source):** Send a request to `PUT /tyk/policies/{policy_id}` or manually edit the policy file on disk. +- **Developer Portal:** When you make changes to your API Products and Plans, Tyk will automatically make any necessary adjustments to the underlying Policies. + +### How Updates Propagate to Gateways + +Because Policies are applied dynamically during a request, any updates you make to a Policy are immediately reflected across all existing Sessions linked to it. You do not need to update the individual Sessions. + +However, the mechanism Tyk uses to synchronize the updated Policy to the Gateways depends on your deployment: + +- **Single Data Plane:** When you update a Policy, the Dashboard publishes a notification to your Redis cluster. The Gateways instantly receive this notification and perform a "hot reload", pulling the updated Policies directly from the Dashboard's internal API. +- **Multiple Data Planes:** In distributed deployments, the Data Plane Gateways poll the Control Plane via MDCB. When a Policy is updated, the Control Plane publishes a notification. MDCB receives this and flags a reload. The Data Plane Gateways, which continuously poll MDCB, detect the reload flag, pull the updated Policies from MDCB via RPC, load them into memory, and save a backup to the Data Plane Redis. +- **Open Source:** If you update a Policy via the Gateway API or by editing files on disk, the Gateway **does not** automatically reload. You must explicitly call the Gateway's `/tyk/reload/` endpoint to force it to read the updated Policies into memory. + +## Deleting Policies + +You can delete a Policy via the Dashboard UI, the Dashboard API, or the Gateway API (for OSS users). + +When you delete a Policy, Tyk removes it from the database (or disk) and notifies all connected Gateways to clear it from their memory. However, **Tyk does not automatically clean up existing Sessions** that have the deleted Policy linked to them. The deleted Policy's ID remains orphaned in their `apply_policies` array. + +### Implications of Deletion + +The Gateway handles these orphaned Policy IDs dynamically during request processing: + +- **Single Policy:** If the deleted Policy was the only Policy linked to a Session, the Gateway will reject all requests for that Session and logs a `key has no valid policies to be applied` error. +- **Multiple Policies:** If the Session has multiple Policies linked, the Gateway simply ignores the missing Policy (logging a `policy not found` warning) and continues to apply the remaining valid Policies. As long as the remaining Policies grant valid access rights, the request is allowed. +- **All Policies Missing:** If a Session has multiple Policies and all of them have been deleted, the Gateway rejects the request. + +Before deleting a Policy, we strongly recommend identifying and migrating any active Sessions that rely exclusively on it to prevent unintended access disruptions. +``` \ No newline at end of file diff --git a/api-management/access-control/sessions-and-keys/access-rights.mdx b/api-management/access-control/sessions-and-keys/access-rights.mdx new file mode 100644 index 0000000000..77070dbf37 --- /dev/null +++ b/api-management/access-control/sessions-and-keys/access-rights.mdx @@ -0,0 +1,94 @@ +--- +title: "Access Rights" +description: "Learn how to configure granular access rights, allow lists, and API-level limits within a Tyk Session." +keywords: "Access Rights, Granular Access, Allowed URLs, Allow List, API Limits, Session State" +sidebarTitle: "Access Rights" +--- + +## Introduction + +While a Session dictates the overall limits and lifecycle for a client, the `access_rights` map is the engine that drives exactly *what* that client is allowed to do. + +The `access_rights` map links the Session to specific APIs and defines the boundaries of access within those APIs. This includes restricting access to specific endpoints (paths and HTTP methods), and even overriding the global session limits for a particular API. + + +While you can configure `access_rights` directly on a Session object, it is highly recommended to manage these settings via [Policies](/api-management/access-control/policies/applying-policies) for easier management at scale. + + +## The Access Rights Map + +Under the hood, `access_rights` is a map where the key is the **API ID**, and the value is an **Access Definition** object. + +If an API ID is not present in this map, the Session has absolutely no access to that API. If it is present, Tyk evaluates the rules defined in the Access Definition. + +### Basic API Access + +At its simplest, an Access Definition grants access to an API identified by its unique ID: + +- `api_id`: The internal ID of the API. +- `api_name`: A human-readable name for the API. + +### Granular Endpoint Access + +By default, granting access to an API allows the client to access all paths and HTTP methods (endpoints) within that API. You can restrict this using the `allowed_urls` allow list. + +- `allowed_urls`: A list of objects that define specific endpoints the client can access. If this list is empty, all endpoints are allowed. If it contains even one entry, Tyk operates in a "default deny" mode, and the request must match an entry in this allow list to proceed. + +Each entry in the `allowed_urls` allow list contains: +- `url`: A regular expression pattern matching the allowed path (e.g., `/users/.*`). +- `methods`: A list of allowed HTTP methods for that path (e.g., `["GET", "POST"]`). + +*Example: Allowing read-only access to the `/users` endpoint:* + +```json +"allowed_urls": [ + { + "url": "/users/.*", + "methods": ["GET"] + } +] +``` + +### API-Level Limits and Quotas + +Sometimes, you want a client to have different rate limits or quotas for different APIs. For example, a client might be allowed 100 requests per second globally, but only 10 requests per second to a computationally expensive "Reports" API. + +You can override the Session's **global limits** (the rate limit and quota controls declared at the root of the session object) for a specific API using the `limit` object within the Access Definition. + +- `limit`: An object containing rate limit and quota settings that apply *only* to this API. If these fields are set, they take precedence over the global Session limits when the client accesses this specific API: + - `rate` and `per`: The API-specific rate limit. + - `throttle_interval` and `throttle_retry_limit`: The API-specific throttling configuration. + - `quota_max` and `quota_renewal_rate`: The API-specific quota configuration. + +### Endpoint-Level Rate Limits + +For even finer control, you can define rate limits for specific paths and methods within an API using the `endpoints` list. + +- `endpoints`: A list of objects that define rate limits for specific paths. + - `path`: The exact path (e.g., `/reports`). + - `methods`: A list of methods and their associated limits. + - `name`: The HTTP method (e.g., `GET`). + - `limit`: The rate limit configuration (`rate` and `per`) for this specific path and method. + +### Protocol-Specific Controls + +The Access Definition also contains fields for securing specific types of APIs: + +**GraphQL** +- `restricted_types` and `allowed_types`: Controls which GraphQL types the client can query. +- `field_access_rights`: Granular control over specific fields within GraphQL types. +- `disable_introspection`: Prevents the client from running introspection queries. + +**JSON-RPC and MCP** +- `json_rpc_methods` and `json_rpc_methods_access_rights`: Controls access to specific JSON-RPC methods. +- `mcp_primitives` and `mcp_access_rights`: Controls access to specific Model Context Protocol (MCP) primitives. + +### Tyk Classic API Controls + +If you are using Tyk Classic APIs, you can also restrict access to specific API versions: + +- `versions`: A list of strings representing the allowed versions (e.g., ["Default", "v2"]). The client's request must target one of these versions. + + +This field is ignored for Tyk OAS APIs, which are inherently unversioned from the Session's perspective, as each has its own unique API ID. + \ No newline at end of file diff --git a/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib.mdx b/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib.mdx new file mode 100644 index 0000000000..12de485e61 --- /dev/null +++ b/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib.mdx @@ -0,0 +1,212 @@ +--- +title: "API Token Generation via Tyk Identity Broker" +description: "Learn how to use Tyk Identity Broker (TIB) to authenticate users against external identity providers and issue API tokens for API access." +keywords: "Tyk Identity Broker, TIB, OAuth, API Tokens, LDAP, Social Provider, Authentication, Access Keys" +sidebarTitle: "Issue Access Tokens via TIB" +--- + +## Introduction + +[Tyk Identity Broker](/tyk-identity-broker/overview) (TIB) can act as a bridge between external identity providers (such as Okta, GitHub, or Entra ID) and Tyk Gateway to issue API tokens for client applications (such as Single Page Applications or mobile apps). + + +This is distinct from using TIB to log into the [Tyk Dashboard](/tyk-identity-broker/dashboard-sso) or [Developer Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + +Depending on how your API is secured, TIB supports two distinct flows for issuing tokens: +1. **Standard API Tokens (Auth Tokens)** +2. **OAuth 2.0 Tokens (Tyk as OAuth Provider)** + +## Issuing Standard API Tokens (Auth Tokens) + +When your API is secured with standard [Auth Token](/api-management/authentication/bearer-token) authentication you can use TIB to authenticate the user against an identity provider before directly generating a Tyk Session and associated Key (auth token), attaching a specific policy to it. + +### Prerequisites + +- An API configured to use [Auth Token authentication](/api-management/authentication/bearer-token). +- A [Tyk policy](/api-management/policies) to attach to generated tokens; this becomes the `MatchedPolicyID`. +- A [TIB service account](/tyk-identity-broker/dashboard-sso#tib-service-account) - a dedicated Tyk Dashboard user whose API key is used as `DashboardCredential`. + +This flow uses the `GenerateTemporaryAuthToken` [action](/tyk-identity-broker/overview#actions) and works with both embedded and [standalone](/tyk-identity-broker/standalone-tib) TIB. It is configured in the TIB profile as follows: + +```json expandable +{ + "ActionType": "GenerateTemporaryAuthToken", + "ID": "{profile-id}", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}", + "DisableOneTokenPerAPI": false, + "TokenAuth": { + "BaseAPIID": "{API-ID-TO-GRANT-ACCESS-TO}", + "Expires": 3600 + } + }, + "MatchedPolicyID": "{POLICY-ID}", + "OrgID": "{tyk-org-id}", + "ProviderConfig": { ... }, + "ProviderName": "{provider-name}", + "Type": "passthrough" +} +``` + +| Parameter | Required | Description | +|---|---|---| +| `ActionType` | Required | Must be `GenerateTemporaryAuthToken`. | +| `ID` | Required | Unique identifier for this profile. Forms part of the TIB authentication URL; see [Profile](/tyk-identity-broker/overview#profile). | +| `IdentityHandlerConfig.DashboardCredential` | Required | The [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard API key. Although the token grants access to Tyk Gateway, TIB creates it by calling the Tyk Dashboard API (`POST /api/keys`). | +| `IdentityHandlerConfig.TokenAuth.BaseAPIID` | Required | The ID of the API to which the generated token grants access. | +| `IdentityHandlerConfig.TokenAuth.Expires` | Optional | Token expiry in seconds. Defaults to `3600`. | +| `IdentityHandlerConfig.DisableOneTokenPerAPI` | Optional | Set to `true` to allow multiple active tokens per user. Defaults to `false`. See [Controlling Concurrent Sessions](#controlling-concurrent-sessions). | +| `MatchedPolicyID` | Required | The ID of the policy to apply to the generated session. | +| `OrgID` | Required | The Tyk Organisation ID. | +| `ProviderConfig` | Required | IdP-specific connection settings. See the [Identity Provider guides](/tyk-identity-broker/overview#what-would-you-like-to-do). | +| `ProviderName` | Required | The authentication method. Can be `SocialProvider`, `ADProvider`, `SAMLProvider`, or `ProxyProvider`. | +| `Type` | Required | Determined by provider: `redirect` for Social/SAML, `passthrough` for LDAP/Proxy. | + +## Issuing OAuth Tokens + +When your API is secured with [Tyk's built-in OAuth 2.0 authorization server](/api-management/authentication/oauth-2) you can use TIB as the [identity server](/api-management/authentication/oauth-2#integration-with-identity-server), authenticating the user against an external IdP and then handling the authorization code exchange with Tyk Dashboard on their behalf. + + +[This flow](/tyk-identity-broker/overview#api-token-generation) requires Tyk Gateway's built-in OAuth 2.0 authorization server. It does not apply if you are using an external OAuth authorization server such as Auth0 or Okta. + + + +### Prerequisites + +- An API configured to use [Tyk's OAuth 2.0 authentication method](/api-management/authentication/oauth-2#configuring-your-api-proxy). +- An OAuth client app [registered](/api-management/authentication/oauth-2#client-app-registration) for the target API in Tyk Dashboard. +- A [TIB service account](/tyk-identity-broker/dashboard-sso#tib-service-account) - a dedicated Tyk Dashboard user whose API key is used as `DashboardCredential`. +- TIB must be running as a [standalone instance](/tyk-identity-broker/standalone-tib), as this flow requires TIB to communicate directly with Tyk Gateway. + +This flow uses the `GenerateOAuthTokenForClient` [action](/tyk-identity-broker/overview#actions). Requests for tokens go via the base API's listen path (`{listen_path}/tyk/oauth/authorize-client/`), so TIB needs to know the listen path and ID of this API to make the correct API calls on your behalf. It is configured in the TIB profile as follows: + +```json expandable +{ + "ActionType": "GenerateOAuthTokenForClient", + "ID": "{profile-id}", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}", + "DisableOneTokenPerAPI": false, + "OAuth": { + "APIListenPath": "{API-LISTEN-PATH}", + "BaseAPIID": "{BASE-API-ID}", + "ClientId": "{TYK-OAUTH-CLIENT-ID}", + "RedirectURI": "http://{APP-DOMAIN}:{PORT}/{AUTH-SUCCESS-PATH}", + "ResponseType": "token", + "Secret": "{TYK-OAUTH-CLIENT-SECRET}" + } + }, + "MatchedPolicyID": "{policy-id}", + "OrgID": "{tyk-org-id}", + "ProviderConfig": { ... }, + "ProviderName": "SocialProvider", + "Type": "redirect" +} +``` + +| Parameter | Required | Description | +|---|---|---| +| `ActionType` | Required | Must be `GenerateOAuthTokenForClient`. | +| `ID` | Required | Unique identifier for this profile. Forms part of the TIB authentication URL; see [Profile](/tyk-identity-broker/overview#profile). | +| `IdentityHandlerConfig.DashboardCredential` | Required | The [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard API key, used to invalidate previous tokens on re-authentication. | +| `IdentityHandlerConfig.DisableOneTokenPerAPI` | Optional | Set to `true` to allow multiple active tokens per user. Defaults to `false`. See [Controlling Concurrent Sessions](#controlling-concurrent-sessions). | +| `IdentityHandlerConfig.OAuth.APIListenPath` | Required | The listen path of the API; TIB uses this to call the OAuth authorize endpoint (`{listen_path}/tyk/oauth/authorize-client/`). | +| `IdentityHandlerConfig.OAuth.BaseAPIID` | Required | The ID of the API secured with Tyk's built-in OAuth 2.0 authorization server. | +| `IdentityHandlerConfig.OAuth.ClientId` | Required | The client ID of the Tyk OAuth client registered for this API. | +| `IdentityHandlerConfig.OAuth.RedirectURI` | Required | The redirect URI registered for the Tyk OAuth client. The token is returned to the client as a URL fragment at this address. | +| `IdentityHandlerConfig.OAuth.ResponseType` | Required | `token` or `authorization_code`. Use `token` for SPAs and mobile apps. | +| `IdentityHandlerConfig.OAuth.Secret` | Required | The client secret of the Tyk OAuth client. | +| `IdentityHandlerConfig.OAuth.NoRedirect` | Optional | Set to `true` to return the token as JSON in the response body instead of redirecting. Useful for non-browser clients. Defaults to `false`. | +| `MatchedPolicyID` | Required | The ID of the policy to apply to the generated OAuth token. | +| `OrgID` | Required | The Tyk Organisation ID. | +| `ProviderConfig` | Required | IdP-specific connection settings. See the [Identity Provider guides](/tyk-identity-broker/overview#what-would-you-like-to-do). | +| `ProviderName` | Required | The authentication method. Can be `SocialProvider`, `ADProvider`, `SAMLProvider`, or `ProxyProvider`. | +| `Type` | Required | Determined by provider: `redirect` for Social/SAML, `passthrough` for LDAP/Proxy. | + +## Handling the Token Response + +When TIB successfully authorizes the user and generates the token, it delivers it back to the client application as follows: + +- **For `redirect` provider types** (such as Social providers or SAML): TIB redirects the user back to the application's `RedirectURI` with the token or auth code appended as a URL fragment (for example, `http://app-domain/callback#access_token=...`). The client app decodes and uses it as needed. +- **For `passthrough` provider types** (such as LDAP): TIB returns the token directly in the response or redirect, depending on the configuration. + +## Controlling Concurrent Sessions + +When a user authenticates, TIB checks a Redis cache to see whether a token has already been issued for that user and invalidates it before generating a new one. This means re-authentication automatically revokes the previous token, which is useful if a token is compromised. + +Set `"DisableOneTokenPerAPI": true` to skip this check and allow multiple active tokens per user. This is useful when users need concurrent sessions across multiple devices or applications. The trade-off is that old tokens accumulate until they expire via the policy TTL, rather than being revoked on re-authentication. + +## Worked Examples + +### GitHub (OAuth Token via Social Provider) + +The following video demonstrates this flow end-to-end: + + + +1. Register a GitHub OAuth Application + + - In GitHub, go to **Settings > Developer settings > OAuth Apps** and create a new OAuth app. + - Set the **Authorization callback URL** to: `http://{tib-host}/auth/{profile-id}/github/callback` + - Note the **Client ID** and **Client Secret**. + +2. Register an OAuth Client in Tyk Dashboard + + Before TIB can request a token on the user's behalf, you need an OAuth client registered in Tyk Dashboard for the target API. See [Client App Registration](/api-management/authentication/oauth-2#client-app-registration) for details. + +3. IdP-Specific Profile Configuration + + Configure the TIB profile for the [OAuth token flow](#issuing-oauth-tokens) setting `ProviderName` to `SocialProvider` and `Type` to `redirect`. The GitHub-specific settings go in `ProviderConfig`: + +```json expandable + { + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + "CallbackBaseURL": "http://{tib-host}", + "FailureRedirect": "http://{app-domain}/login?fail=true", + "UseProviders": [ + { + "Name": "github", + "Key": "{github-client-id}", + "Secret": "{github-client-secret}" + } + ] + } + } +``` + + +### OAuth Token via LDAP + +This example authenticates the user against LDAP before issuing the OAuth token. It is useful for internal APIs that require valid OAuth tokens but where user identity is managed in an LDAP directory such as Active Directory, rather than a web-based IdP. + +Because LDAP is a passthrough flow, users submit their credentials via a form `POST` directly to TIB; no browser redirect to an external IdP is involved. See [Login Page](/api-management/single-sign-on-ldap#login-page) for how to create the login form. + +1. Register an OAuth Client in Tyk Dashboard + + As with the GitHub example, you need an OAuth client registered in Tyk Dashboard for the target API. See [Client App Registration](/api-management/authentication/oauth-2#client-app-registration) for details. + +2. IdP-Specific Profile Configuration + + Configure the TIB profile for the [OAuth token flow](#issuing-oauth-tokens) setting `ProviderName` to `ADProvider` and `Type` to `passthrough`. The LDAP-specific settings go in `ProviderConfig`: + +```json expandable +{ + "ProviderName": "ADProvider", + "Type": "passthrough", + "ProviderConfig": { + "LDAPServer": "{ldap-server}", + "LDAPPort": "389", + "LDAPUserDN": "cn=*USERNAME*,dc=example,dc=com", + "LDAPAttributes": [], + "FailureRedirect": "http://{app-domain}/failure", + "GetAuthFromBAHeader": true + } +} +``` + +The key difference from the generic template is `Type: "passthrough"` and the LDAP-specific `ProviderConfig`. Set `GetAuthFromBAHeader: true` if your login form submits credentials via HTTP Basic Auth. Set `LDAPUserDN` to match your LDAP directory structure, keeping `*USERNAME*` as a literal placeholder; TIB replaces it with the submitted username at runtime. + +See the [LDAP field reference](/api-management/single-sign-on-ldap#tib-profile) for all available `ProviderConfig` options. diff --git a/api-management/access-control/sessions-and-keys/key-hashing.mdx b/api-management/access-control/sessions-and-keys/key-hashing.mdx new file mode 100644 index 0000000000..6c1b83647f --- /dev/null +++ b/api-management/access-control/sessions-and-keys/key-hashing.mdx @@ -0,0 +1,117 @@ +--- +title: "Key Hashing" +description: "Understand how Tyk secures your API keys using hashing, the available algorithms, and the implications for key management." +keywords: "Key Hashing, Security, Hash Algorithms, Murmur, SHA256, Redis" +sidebarTitle: "Key Hashing" +--- + +## Introduction + +When a client authenticates, Tyk uses a specific identifier (the "Key") to look up their Session in the Redis database. Depending on your authentication method, this Key might be an Auth Token, a Basic Auth username, an HMAC Key ID, or a JWT identity claim. + +To enhance security and protect these identifiers in the event that your Redis database is compromised, Tyk supports **Key Hashing**. + +When hashing is in use, Tyk never stores the plaintext Key in Redis. Instead, it runs the Key through a one-way hashing function and stores the resulting hash as the lookup key in Redis. When a client makes a request, Tyk extracts their identifier, hashes it on the fly, and compares it to the hash stored in Redis. The [algorithm](/api-management/access-control/sessions-and-keys/key-hashing#hashing-algorithms) used to perform the hashing is configurable, with both cryptographic and non-cryptographic options. + + +Key Hashing applies universally at the storage layer. If enabled, the lookup identifier for **every** authentication method is hashed. + + +### Enabling Key Hashing + +Hashing is enabled in the [Gateway](/tyk-oss-gateway/configuration#hash_keys) and [Dashboard](/tyk-dashboard/configuration#hash_keys) configuration using the common setting `hash_keys` (or the equivalent environment variables). + + +This **must** be enabled in both the Gateway and Dashboard (if used). + + + +Switching between hashed and non-hashed configuration means that any existing keys can no longer be used, as the Gateway and Dashboard will not be able to correctly validate them. Every existing API client will lose access immediately. + + +## Hashing Algorithms + +Tyk offers several hashing algorithms, allowing you to choose the right balance between performance and security for your environment. + +You can configure the algorithm using the [`hash_key_function`](/tyk-oss-gateway/configuration#hash_key_function) setting in your `tyk.conf` (or the equivalent environment variable). The available options are: + +- `murmur32` (Default): A fast, non-cryptographic hash function. It provides excellent performance and basic obfuscation, but is not cryptographically secure. +- `murmur64` and `murmur128`: Longer variants of the Murmur hash, providing a larger hash space to prevent collisions while maintaining high performance. These are also not cryptographically secure. +- `sha256`: A cryptographically secure hashing algorithm. This provides the highest level of security but is computationally more expensive and will slightly reduce Gateway performance. + + +For identifiers that are not standard Tyk tokens, such as Basic Auth usernames, Tyk will always use murmur32 for the lookup hash, regardless of this setting. + + + +## Implications of Key Hashing + +Enabling key hashing fundamentally changes how you interact with keys via the Tyk APIs and Dashboard. Because hashing is a one-way operation, **Tyk cannot reverse the hash to reveal the original key**. + +If you enable key hashing, you must design your workflows around the following constraints: + +### 1. Plaintext Keys are Returned Only Once + +When you create a new key via the Gateway API or the Dashboard, Tyk will return the plaintext key in the response payload **exactly once**. It is your responsibility to securely transmit this key to the client and store it if necessary. If the client loses the key, it cannot be recovered from Tyk; a new key must be generated. + +### 2. Listing Keys Returns Hashes + +By default, if key hashing is in use, then requests to the [Gateway API](https://tyk.io/docs/api-reference/keys/list-keys) or [Dashboard API](https://tyk.io/docs/api-reference/keys/list-all-the-keys) to retrieve a list of keys will be rejected - this functionality is disabled for security reasons. + +You must explicitly enable these endpoints by setting `enable_hashed_keys_listing: true` in the [Gateway](/tyk-oss-gateway/configuration#enable_hashed_keys_listing) and [Dashboard](/tyk-dashboard/configuration#enable_hashed_keys_listing) configurations respectively. + +### 3. Analytics Using Hashed Keys + +All analytics records, logs, and quota tracking will use the hashed version of the key. If you need to correlate analytics data with a specific client, you should use the Session's `alias` or `meta_data` [fields](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context), rather than relying on the key itself. + +### 4. Managing Keys by Hash + +If you need to retrieve, update, or delete the Session relating to a specific key, you can always do so using the plaintext key (Tyk will hash it on the fly). + +If you want to be able to use the key's hashed value instead, then you must set the appropriate configuration as follows: + +- **Gateway API**: + - Append the `?hashed=true` query parameter to your API calls when passing a hash instead of a plaintext key. +- **Dashboard**: + - You must *also* append the `?hashed=true` query parameter to your API calls. + - You must explicitly set [`enable_update_key_by_hash: true`](/tyk-dashboard/configuration#enable_update_key_by_hash) and [`enable_delete_key_by_hash: true`](/tyk-dashboard/configuration#enable_delete_key_by_hash) in your tyk_analytics.conf to allow modification of keys via their hash. + + +## Migrating Hashing Algorithms + +If you decide to change your `hash_key_function` (for example, upgrading from murmur32 to sha256 for better security), Tyk provides a seamless migration path. + +It is of course not possible to convert a one-way hashed key to another hashing algorithm, but you do not need to invalidate all your existing keys. Instead, you can use the [`hash_key_function_fallback array`](/tyk-oss-gateway/configuration#hash_key_function_fallback) in your Gateway configuration. + +When a request arrives, Tyk will first attempt to hash the incoming key using your current hashing algorithm (declared in `hash_key_function`). If the resulting hash is not found in Redis, Tyk will then try hashing the key using the algorithms listed in your fallback array. + +For example, given this configuration in `tyk.conf`: + +```json +"hash_key_function": "sha256", +"hash_key_function_fallback": ["murmur32"] +``` + +In this scenario, all new keys will be hashed using sha256, but existing keys hashed with murmur32 will continue to work perfectly. This gives you the opportunity to gradually retire the older keys as they expire. + +## Configuration Summary + +To ensure key hashing works correctly, your Gateway and Dashboard configurations must be kept in sync. + +**Gateway (`tyk.conf` or equivalent environment variable)** + +- `hash_keys`: Set to `true` to enable hashing. +- `hash_key_function`: The algorithm for standard keys (e.g., `murmur64`, `sha256`). +- `hash_key_function_fallback`: An array of legacy algorithms to support existing keys during a migration. +- `enable_hashed_keys_listing`: Set to true to allow the `GET /tyk/keys` endpoint to return hashes. + +**Dashboard (`tyk_analytics.conf` or equivalent environment variables)** + +- `hash_keys`: Must match the Gateway setting (`true`). +- `enable_hashed_keys_listing`: Must match the Gateway setting (`true`). +- `enable_update_key_by_hash`: Set to `true` to allow sessions to be updated using the key hash as reference. +- `enable_delete_key_by_hash`: Set to `true` to allow sessions to be deleted using the key hash as reference. + + +The Dashboard calls the Gateway API to obtain the list of keys, so it is important to set `enable_hashed_keys_listing: true` in both components. + \ No newline at end of file diff --git a/api-management/access-control/sessions-and-keys/session-lifecycle.mdx b/api-management/access-control/sessions-and-keys/session-lifecycle.mdx new file mode 100644 index 0000000000..4fcd205fbc --- /dev/null +++ b/api-management/access-control/sessions-and-keys/session-lifecycle.mdx @@ -0,0 +1,136 @@ +--- +title: "Session Lifecycle" +description: "Understand how Tyk manages the lifecycle of Sessions, including expiration, retention, and deletion from Redis." +keywords: "Session Lifecycle, Session Expiry, Redis TTL, Post Expiry Action, Session Lifetime" +sidebarTitle: "Session Lifecycle" +--- + +## Introduction + +When we talk about the "Key Lifecycle" in Tyk, we are actually referring to the lifecycle of the **Session** stored in Redis. The Key is simply the identifier used to look up that Session. + +Keeping an expired Session in Redis for a period of time can be useful for various reasons: +- **Graceful Renewal & Better UX:** Enables the Gateway to return specific `Token Expired` errors rather than generic `Invalid Token` responses, allowing client applications to trigger silent refresh flows without forcing users to log in again. +- **Audit & Troubleshooting:** Preserves session data post-expiration to aid in compliance and debugging, making it easier to trace requests and correlate logs for issues occurring near the expiration time. +- **Asynchronous Analytics & Billing:** Ensures background workers can accurately attribute final requests to the correct user or organization, preventing data loss in asynchronous analytics and billing processes. +- **Quota Preservation Across Renewals:** Retains quota data for users with long-term limits but short-lived access tokens, allowing renewed tokens to link to existing quota buckets without accidentally resetting usage. +- **Security & Fraud Detection:** Provides context for security systems to monitor replay attacks, identifying if an expired token is suddenly flooded with requests rather than just logging generic errors. + +Managing the Session lifecycle is crucial for security (ensuring access is revoked when it should be) and operational efficiency (ensuring your Redis database doesn't fill up with stale data). + +## Expiration vs. Deletion + +To understand Tyk's lifecycle controls, you must understand the difference between a Session expiring and a Session being deleted: + +1. **Expiration (expires)**: This is a Unix timestamp stored inside the Session. When a request arrives, Tyk checks if the current time is past this timestamp. If it is, Tyk rejects the request. The Session is "expired" and access is denied, even if the data still exists in Redis. +2. **Deletion (Redis TTL)**: This is the physical removal of the Session data from your Redis database. Tyk calculates a Time-To-Live (TTL) and passes it to Redis. When the TTL reaches zero, Redis automatically deletes the data. + +The lifecycle controls dictate how Tyk calculates that Redis TTL. + +## Preferred Controls (Tyk 5.13.0+) + +Starting in Tyk 5.13.0, Tyk introduced explicit controls that separate the concept of expiration from deletion. This allows you to deny access at a specific time, but retain the Session data in Redis for auditing, analytics, or delayed cleanup. + +### 1. Set the Expiration + +- `expires`: A Unix timestamp indicating exactly when the Session becomes invalid. If set to `0` or `-1`, the Session never expires. + +### 2. Define the Post-Expiry Action + +- `post_expiry_action`: Defines what happens to the data in Redis after the expires timestamp is reached. + - `delete`: The Redis TTL is set to match the `expires` timestamp. The moment the Session expires, Redis physically deletes it. + - `retain`: The Session is kept in Redis after it expires. You must also configure a [grace period](/api-management/access-control/sessions-and-keys/session-lifecycle#3-configure-the-grace-period). + +### 3. Configure the Grace Period + +- `post_expiry_grace_period`: If the action is `retain`, this defines how long (in seconds) the Session is kept in Redis after expiration. + - A value of `-1` means the Session is retained in Redis forever (no TTL is set). + - If the value is `0` then the [legacy controls](/api-management/access-control/sessions-and-keys/session-lifecycle#legacy-controls) will be applied to the Session + +For example, if `expires` is tomorrow, and `post_expiry_grace_period` is 86400 (24 hours), the Session will be denied access tomorrow, but the Session data will remain in Redis for one additional day before being physically deleted. + +## Legacy Controls + +If you are using an older version of Tyk (or if you do not configure `post_expiry_action` and `post_expiry_grace_period`), Tyk falls back to the legacy controls. + +The Tyk Vendor Extension (`x-tyk-api-gateway`) in the API definition contains an option to set a [`customKeyLifetime`](/api-management/gateway-config-tyk-oas#customkeylifetime) for all keys (Sessions) created that grant access to the API: + +```yaml +x-tyk-api-gateway: + authentication: + customKeyLifetime: + enabled: true, + value: 30d, + respectValidity: true +``` + +| Parameter | Description | +|:----------|:------------| +| `enabled` | Set a custom lifetime for Sessions granting access to the API | +| `value` | Human readable duration for the custom lifetime (d, m, s) | +| `respectValidity` | If the custom lifetime is shorter than the Session `expiry` then retain the Session until it expires and then delete | + +If the lifetime (`value`) is set to `0s` then sessions will be assigned a TTL of -1 and will not be automatically deleted from Redis. + + +If `respectValidity` is set to `false` and the `value` is lower than the value assigned for Session `expiry` then the Session will be deleted before it becomes invalid. + + +If using Tyk Classic APIs, the equivalent fields are `session_lifetime` (for `value`) and `session_lifetime_respects_key_expiration` (for `respectValidity`). + + +## Gateway Level Settings +In some environments, administrators need to enforce a strict maximum lifetime for all sessions across the entire Gateway, regardless of what individual users or policies configure. + +You can enforce this behaviour in your `tyk.conf` file (or by using the equivalent environment variables): + +- [`global_session_lifetime`](/tyk-oss-gateway/configuration#global_session_lifetime): The maximum allowed lifetime (in seconds) for any Session in Redis. If set to `0` then sessions will be assigned a TTL of -1 and will not be automatically deleted from Redis. +- [`force_global_session_lifetime`](/tyk-oss-gateway/configuration#force_global_session_lifetime): When set to `true`, the global lifetime will be enforced, taking precedence over all other lifecycle controls (`post_expiry_action`, `customKeyLifetime`, and `expires`). The Redis TTL for every Session will be strictly set to the `global_session_lifetime`. +- [`session_lifetime_respects_key_expiration`](/tyk-oss-gateway/configuration#session_lifetime_respects_key_expiration): as for the per-API [respect validity](/api-management/access-control/sessions-and-keys/session-lifecycle#legacy-controls) configuration, this will ensure that Sessions are not deleted before they have expired; if this is set to `true` then the per-API setting is ignored. + + + If the Global Session Lifetime is enforced, this will be applied to all Sessions, even those with no expiry. + + +## Summary of Session Lifetime Calculation + +With the ongoing support for [legacy controls](/api-management/access-control/sessions-and-keys/session-lifecycle#legacy-controls) it is possible to get confused how best to configure session lifetime to manage your Redis storage. We recommend using the more intuitive post-expiry action and grace period controls. + +This table summarises the effect of different combinations of controls. + +| `force_global_session_lifetime` | `post_expiry_action` | `post_expiry_grace_period` | `session_lifetime_respects_key_expiration` | Assigned lifetime | +|:--------:|:--------:|:--------:|:--------:|:---------| +| `true` | n/a | n/a | n/a | `global_session_lifetime` | +| `false` | `delete` | n/a | n/a | `expiry` | +| `false` | `retain` | `>0` | n/a | `expires+post_expiry_grace_period` | +| `false` | `retain` | `-1` | n/a | infinite (do not delete) | +| `false` | `retain` | `0` | `true` | later of `customKeyLifetime.value` or `expires` | +| `false` | `retain` | `0` | `false` | `customKeyLifetime.value` | + + +If using the Legacy Mode and `customKeyLifetime` is set to `0s` (or unset) then sessions will be assigned a TTL of `-1` and will not be automatically deleted from Redis. + +Likewise, if using the Global Override, if `global_session_lifetime` is set to `0` (or unset) then sessions will be assigned a TTL of `-1` and will not be automatically deleted from Redis. + + +## Temporarily revoking access to a key + +If you need to revoke access immediately without waiting for expiration or deletion, you can use the `is_inactive` flag: + +- `is_inactive`: A boolean flag on the Session. When set to `true`, Tyk rejects requests using this Session as if it has expired. Setting this back to `false` will reactivate the Session. The Session data remains in Redis until its TTL expires. This is useful for temporarily suspending a user. + +## OAuth 2.0 Token Lifecycle + +If you are using OAuth 2.0, it is important to understand how OAuth tokens interact with Tyk's session lifecycle controls. + +When Tyk generates an OAuth **access token**, it creates a standard Session object. The initial expiration time of this session is controlled by the global [`oauth_token_expire`](/tyk-oss-gateway/configuration#oauth_token_expire) Gateway configuration. Once this timestamp is reached, the access token's session is subject to the standard session lifecycle controls described [above](/api-management/access-control/sessions-and-keys/session-lifecycle#preferred-controls-tyk-5-13-0-), which determine its physical retention in Redis. + +Conversely, **refresh tokens** are not Session objects. They do not use these session lifecycle controls and are physically deleted from Redis exactly when their [`oauth_refresh_token_expire`](/tyk-oss-gateway/configuration#oauth_refresh_token_expire) TTL is reached. + + +**The `oauth_token_expired_retain_period` setting** + +You may notice the global Gateway configuration option [`oauth_token_expired_retain_period`](/tyk-oss-gateway/configuration#oauth_token_expired_retain_period). This setting **does not** control the Redis TTL or the retention of the actual Session data. Instead, it controls a background cleanup job that removes expired tokens from an internal tracking list used by the OAuth client. + + +For more detailed information on configuring OAuth token expiration, see the [OAuth 2.0 Token Expiration and Retention](/api-management/authentication/oauth-2#token-expiration-and-retention) documentation. \ No newline at end of file diff --git a/api-management/access-control/sessions-and-keys/session-metadata.mdx b/api-management/access-control/sessions-and-keys/session-metadata.mdx new file mode 100644 index 0000000000..329e6ed1c3 --- /dev/null +++ b/api-management/access-control/sessions-and-keys/session-metadata.mdx @@ -0,0 +1,56 @@ +--- +title: "Session Metadata" +description: "Understand what session metadata is, how it can be used, and where it can be modified and accessed." +keywords: "Session, Metadata, Context Variables, Identity Propagation, Dynamic Routing" +sidebarTitle: "Session Metadata" +--- + +## Introduction + +While standard Session fields handle access rights, rate limits, and quotas, Tyk also provides a flexible key-value store attached directly to the Session object known as Session Metadata (`meta_data`). + + +**Key vs. Session** + +While Tyk's APIs and UI often refer to adding metadata to a "Key", the metadata is actually stored on the underlying **Session**. The Key is simply the credential or identifier used by the client to look up this Session. + + +Metadata allows you to store custom, user-specific, or application-specific context. This data persists with the Session and is loaded into memory whenever the Key is used to authenticate a request, making it instantly available during the request lifecycle. + +## Use Cases + +Metadata is primarily used to make user-specific data available to Tyk middleware when handling a request without requiring additional database lookups. Common use cases include: + +- **Identity Propagation:** Storing a `user_id`, `tenant_id`, or `account_id` in the Session metadata and injecting it into upstream headers so your backend service knows exactly who is making the request. +- **Dynamic Routing:** Using metadata values in [URL Rewrites](/transform-traffic/url-rewriting) to route users to specific backend shards, geographic regions, or pricing tiers. +- **Custom Plugin Logic:** Passing custom attributes (like user roles, subscription levels, or feature flags) to [custom plugins](/api-management/plugins/overview) to make dynamic authorization or transformation decisions. + +## Where it can be modified + +Session metadata can be set or updated in several locations: + +- **Session creation and update:** You can directly update the `meta_data` field (a JSON object) in the Session object when creating or updating a Session + - via the Dashboard API + - via the Gateway API + - via the Dashboard UI +- **Applying Policies** Sessions can inherit metadata from [Policies](/api-management/policies). +- **Dynamic plugins:** Custom plugins (JSVM, Coprocess/gRPC) can dynamically modify the metadata during the request lifecycle. If a plugin modifies the metadata, Tyk automatically saves the updated Session back to the Redis store. +- **OAuth flow:** When generating an OAuth token, any `meta_data` configured on the OAuth Client is automatically copied into the generated Session's `meta_data`. + +## Where it can be accessed + +Metadata stored in the Session can be accessed dynamically during the request lifecycle in several ways: + +- **Middleware:** Some middleware have access to Session metadata using the `$tyk_meta.KEY_NAME` variable syntax. This is evaluated in the same way as [request context variables](/api-management/traffic-transformation/request-context-variables), with the Gateway replacing this reference with the corresponding value from the Session metadata in the following middleware: + - [URL Rewrite](/transform-traffic/url-rewriting#the-rewrite-target) + - [Request Header Transform](/api-management/traffic-transformation/request-headers#injecting-dynamic-data-into-headers) + - [Response Header Transform](/api-management/traffic-transformation/response-headers#injecting-dynamic-data-into-headers) + - [Request Body Transform](/api-management/traffic-transformation/request-body#data-accessible-to-the-middleware) + - [Response Body Transform](/api-management/traffic-transformation/response-body#data-accessible-to-the-middleware) + - [Rate Limiting](/api-management/rate-limit#how-custom-rate-limiting-works) + - [Signed Auth Token](/api-management/authentication/bearer-token#auth-token-with-signature) + - GraphQL & Persisted Queries +- **Go Templates:** In Body Transform and Response Body Transform middleware, if EnableSession is true, metadata is injected into the template context and can be accessed using `{{ ._tyk_meta.KEY_NAME }}`. +- **JSVM Plugins & Virtual Endpoints:** The session object is passed as a JSON argument to your JavaScript functions. Metadata is accessible via session.meta_data. +- **Custom Go Plugins:** Go plugins can extract the session state from the request context (post-authentication) and directly read from or write to the MetaData map. +- **Coprocess Plugins (gRPC, Python, etc.):** The session object is passed within the Coprocess request object, and metadata is accessible via Session.Metadata. diff --git a/api-management/access-control/sessions-and-keys/understanding-sessions.mdx b/api-management/access-control/sessions-and-keys/understanding-sessions.mdx new file mode 100644 index 0000000000..a9a2c7f246 --- /dev/null +++ b/api-management/access-control/sessions-and-keys/understanding-sessions.mdx @@ -0,0 +1,126 @@ +--- +title: "Understanding Sessions" +description: "Understand the Tyk Session, its purpose, and how it controls API access, limits, and client metadata." +keywords: "Session, Access Rights, Quotas, Rate Limits, Metadata, Session Lifecycle" +sidebarTitle: "Understanding Sessions" +--- + +## Introduction + +A **Session** is the core concept Tyk uses to manage client access. Whenever a client makes a request to a protected API, Tyk resolves their authentication credentials (the "Key") into a Session. + +This Session acts as the ultimate source of truth for that specific client's connection, dictating what they can access, how much they can consume, and carrying any custom metadata associated with them. + +## Purpose and Usage + +The primary purposes of a Session are: +1. **Access Control:** Defining exactly which APIs, versions, and paths the client is authorized to access. +2. **Traffic Management**: Enforcing session-level rate limits (requests per second) to prevent individual "noisy neighbor" clients from monopolizing resources, which complements your global [API-level protections](/api-management/rate-limit#api-level-rate-limiting). +3. **Tiered Access (Service Plans)**: Enforcing quotas (requests over a longer period) and custom rate limits to offer different levels of service to your clients (e.g., Free vs. Paid tiers) and support API monetization. +4. **State Tracking:** Keeping track of real-time quota usage and session expiration. +5. **Context Propagation:** Storing custom metadata and tags that can be injected into headers, used for routing, or sent to analytics. + +When a request arrives, Tyk retrieves the Session and evaluates its fields to determine if the request should be allowed, throttled, or rejected. All Sessions are stored in your Tyk Data Plane's Redis database, ensuring that rate limits and quotas are tracked accurately across all your Gateway nodes. + +## Configuration Options + +A Session contains several categories of configuration options. While you can configure these directly on individual sessions, it is highly recommended to manage them via [**Policies**](/api-management/policies). + +### Access Control + +This field determines what the client is allowed to access: + +- `access_rights`: A map defining the specific APIs the client can access. The key is the API ID, and the value contains detailed [access control](/api-management/access-control/sessions-and-keys/access-rights) rules, such as granular path/method restrictions. + +### Rate Limits + +Rate limits control the velocity of requests (e.g., 10 requests per second): + +- `rate`: The number of requests allowed. +- `per`: The time window in seconds for the rate limit. +- `throttle_interval`: The time in seconds to [hold requests before retrying](/api-management/request-throttling) if the client exceeds the rate limit. +- `throttle_retry_limit`: The number of times a throttled request will be retried before being rejected with an `HTTP 429` response. +- `smoothing`: Configuration for [rate limit smoothing](/api-management/rate-limit#rate-limit-smoothing) (dynamic adjustment of rate limits to mitigate against traffic spikes). + +These settings will be applied to all requests made using the Session except where [API](/api-management/access-control/sessions-and-keys/access-rights#api-level-limits-and-quotas) or [endpoint-level](/api-management/access-control/sessions-and-keys/access-rights#endpoint-level-rate-limits) limits have been configured in the access rights. + +### Quotas + +Quotas control the total volume of requests over a longer period (e.g., 1000 requests per month): + +- `quota_max`: The maximum number of requests allowed in the quota period. +- `quota_renewal_rate`: The time in seconds between quota resets (e.g., 2592000 for 30 days). + +The real-time status of the session's quota is tracked in these system-managed state fields: + +- `quota_renews`: A Unix timestamp indicating when the quota will reset. +- `quota_remaining`: The number of requests left in the current quota. + +These settings will be applied to all requests made using the Session except where [API-level quotas](/api-management/access-control/sessions-and-keys/access-rights#api-level-limits-and-quotas) have been configured in the access rights. + +### Authentication Data + +Depending on the authentication method used, the Session may store credentials or validation data: + +- `basic_auth_data`: An object containing the `password` and `hash` type (e.g., bcrypt) used for Basic Authentication. +- `certificate`: The hash of the client's TLS certificate used for Certificate Auth. +- `hmac_enabled` and `hmac_string`: Flags and secrets used to validate HMAC signatures. +- `enable_http_signature_validation`: A boolean flag to enable HTTP Signature validation for this specific session. +- `rsa_certificate_id`: The ID of the RSA certificate (stored in Tyk Certificate Store) used to validate HTTP Signatures when `enable_http_signature_validation` is enabled. +- `jwt_data`: An object containing the `secret` used to validate JWT signatures. +- `oauth_client_id` and `oauth_keys`: When a Session is generated via an OAuth flow, Tyk stores the ID of the originating OAuth Client here. This links the dynamically generated Session back to the client application that requested it. +- `mtls_static_certificate_bindings`: A list of static certificate signatures bound to this session, used when static mTLS is layered on top of another authentication method. + +### GraphQL Complexity Limit + +If the session grants access to a GraphQL API, you can enforce specific security limits: + +- `max_query_depth`: The maximum allowed depth for GraphQL queries, preventing deeply nested queries from overloading your upstream services. + +### Analytics & Monitoring + +These fields control how Tyk tracks and reports on this specific session: + +- `enable_detailed_recording`: A boolean flag to enable detailed analytics recording specifically for this session. When enabled, Tyk logs the raw HTTP request and response payloads. +- `monitor`: Configuration for the [quota monitor](/api-management/gateway-events#monitoring-quota-consumption), which Tyk uses to fire webhook events when a session's quota usage reaches those specific thresholds. + +### Metadata and Context + +These fields store custom information about the client: + +- `meta_data`: A key-value map for storing custom data. This data can be injected into headers sent to your upstream API or used in transform middleware. For more details see the [session metadata](/api-management/access-control/sessions-and-keys/session-metadata) section. +- `tags`: A list of strings used to categorize the session (often used for analytics filtering). +- `alias`: A human-readable name or identifier for the session. + +### Session Lifecycle + + +While Tyk's APIs and UI often refer to "Key Expiry", it is actually the Session that has a lifecycle. The Key is just the identifier used to look up the session. + + +These fields control how long the session remains valid and when it is permanently deleted from Redis. + +- `expires`: A Unix timestamp indicating when the session will become invalid (expire). If set to `0` or `-1`, the session never expires. +- `is_inactive`: A boolean flag to temporarily disable the session without deleting it. + +**Preferred Controls (Tyk 5.13.0+)** +Starting in Tyk 5.13.0, you can explicitly control what happens to a session *after* it expires using these fields: +- `post_expiry_action`: Defines the behavior when the session expires. Can be set to `retain` or `delete`. +- `post_expiry_grace_period`: If the action is `retain`, this is the time (in seconds) the session is kept in Redis after expiration before being deleted. A value of `-1` means retain forever. + +**Legacy Controls** +If you are using an older version of Tyk, or if you do not configure the preferred controls above, Tyk falls back to the legacy lifetime settings: +- `session_lifetime`: The maximum duration (in seconds) the session is allowed to exist in Redis, regardless of its `expires` value. + +## Policies + +Instead of configuring access rights and limits directly on the session, you can link the session to one or more [Policies](/api-management/policies). This is the recommended approach for managing access at scale. + +- `apply_policies`: A list of policy IDs to apply to this session. Where multiple policies are listed, they will be combined (e.g., a "Plan" policy for rate limits and a "Product" policy for access rights). Fields set from a Policy will override any configured directly in the Session. + + +## Next Steps + +- Learn how to manage the [Session Lifecycle](/api-management/access-control/sessions-and-keys/session-lifecycle) to control how long a key will be valid. +- Discover how to use [Policies](/api-management/policies) to manage Sessions at scale. +``` \ No newline at end of file diff --git a/api-management/air-gapped-deployment.mdx b/api-management/air-gapped-deployment.mdx new file mode 100644 index 0000000000..b9b7ac981c --- /dev/null +++ b/api-management/air-gapped-deployment.mdx @@ -0,0 +1,184 @@ +--- +title: "How to install Tyk in Air-Gapped Deployments" +description: "Learn how to deploy Tyk in air-gapped or network-restricted environments using private container registries and local package mirrors" +sidebarTitle: "Air-Gapped Deployments" +--- + +## Overview + +In some environments, direct access to public container registries and package repositories is restricted. This guide provides instructions for deploying Tyk in such air-gapped or network-restricted environments. + +There are two main deployment scenarios covered: + +- **Kubernetes (Helm)** — Mirroring container images and packaging Helm charts for offline installation. +- **Bare Metal / VMs (Linux packages)** — Creating a local mirror of Tyk's PackageCloud repositories for `apt` or `yum` based installations. + +## Kubernetes: Image Mirroring and Helm Chart Packaging + +### Step 1: Identify Required Container Images + +Use `helm template` on an internet-connected machine to extract all container images referenced by the Tyk chart you plan to deploy. + +First, add the Tyk Helm repository: + +```bash +helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ +helm repo update +``` + +Then render the chart templates and extract the image references. The example below uses `tyk-stack` (Tyk Self-Managed). Replace it with the chart that matches your deployment (e.g. `tyk-oss`, `tyk-data-plane`, `tyk-control-plane`): + +```bash +helm template tyk tyk-helm/tyk-stack | yq e '..|.image? | select(.)' - | sort -u +``` + + +Enable optional components with `--set` flags so their images are included in the output. For example, add `--set global.components.devPortal=true` for the Developer Portal or `--set global.components.operator=true` for the Tyk Operator. + + +Typical output includes images such as: + +```text +busybox:1.32 +curlimages/curl:8.8.0 +tykio/portal:v1.16.0 +tykio/tyk-dashboard:v5.8.9 +tykio/tyk-gateway-ee:v5.8.9 +tykio/tyk-k8s-bootstrap-post:v2.2.0 +tykio/tyk-k8s-bootstrap-pre-delete:v2.2.0 +tykio/tyk-k8s-bootstrap-pre-install:v2.2.0 +tykio/tyk-operator:v1.2.0 +tykio/tyk-pump-docker-pub:v1.12.0 +zalbiraw/alpine-curl-jq +``` + +### Step 2: Pull, Re-tag, and Push Images to Your Private Registry + +On an internet-connected machine, pull each image, re-tag it for your private registry, and push it: + +```bash +PRIVATE_REGISTRY="my-private-registry.com" + +# Example for the Gateway image +docker pull docker.tyk.io/tyk-gateway/tyk-gateway:v5.8 +docker tag docker.tyk.io/tyk-gateway/tyk-gateway:v5.8 ${PRIVATE_REGISTRY}/tyk-gateway/tyk-gateway:v5.8 +docker push ${PRIVATE_REGISTRY}/tyk-gateway/tyk-gateway:v5.8 +``` + +Alternatively, you can use `docker save` and `docker load` to transfer images via archive files if your air-gapped environment does not have a private registry: + +```bash +# Save all images to a tar archive on the connected machine +docker save -o tyk-images.tar \ + docker.tyk.io/tyk-gateway/tyk-gateway:v5.8 \ + tykio/tyk-dashboard:v5.8 \ + docker.tyk.io/tyk-pump/tyk-pump:v1.13 + +# Transfer tyk-images.tar to the air-gapped environment, then load +docker load -i tyk-images.tar +``` + +### Step 3: Package Helm Charts for Offline Use + +On the internet-connected machine, pull and package the chart as a `.tgz` archive: + +```bash +# Pull the chart archive +helm pull tyk-helm/tyk-stack --version + +# This creates a file like tyk-stack-.tgz in the current directory +``` + +Transfer the `.tgz` file to the air-gapped environment. You can then install directly from the archive: + +```bash +helm install tyk ./tyk-stack-.tgz -f values.yaml +``` + +### Step 4: Configure Helm Charts to Use the Private Registry + +Set `global.imageRegistry` in your `values.yaml` to point all image pulls at your private registry: + +```yaml +global: + imageRegistry: "my-private-registry.com/" +``` + +This prefix is prepended to every image repository defined in the chart, so all components (Gateway, Dashboard, Pump, bootstrap jobs, etc.) will pull from your private registry. + +If your private registry requires authentication, create an image pull secret and reference it: + +```bash +kubectl create secret docker-registry tyk-registry-secret \ + --docker-server=my-private-registry.com \ + --docker-username= \ + --docker-password= \ + -n tyk +``` + +Then in your `values.yaml`: + +```yaml +global: + imageRegistry: "my-private-registry.com/" + imagePullSecrets: + - name: tyk-registry-secret +``` + +### Alternative: Container Runtime (CRI) Mirror Configuration + +Instead of changing Helm values, you can configure your container runtime (Docker, containerd, CRI-O) to transparently redirect pull requests from public registries to your private registry. This is done at the Kubernetes node level and avoids any changes to your Helm configuration. + +Refer to your container runtime's documentation for mirror configuration instructions. + +## Bare Metal / VMs: Linux Package Mirror + +For installations on bare metal servers or VMs that use Linux packages (`deb` or `rpm`), you can create a local mirror of the Tyk repositories hosted on [PackageCloud](https://packagecloud.io/tyk/). + +### Required Tyk Packages + +A standard Tyk Self-Managed deployment requires these packages: + +| Package | Description | +| :--- | :--- | +| `tyk-gateway` | API Gateway | +| `tyk-dashboard` | Management Dashboard | +| `tyk-pump` | Analytics Pump | + +Optional packages depending on your deployment: + +| Package | Description | +| :--- | :--- | +| `tyk-identity-broker` | SSO / Identity Broker | +| `tyk-sync` | Git-based API definition sync | +| `tyk-mdcb` | Multi Data Center Bridge (from `tyk-mdcb-stable` repo) | + +### Option A: Mirroring Repositories + +Use a tool like `debmirror` (Debian/Ubuntu) or `reposync` (RHEL/CentOS) to create a local mirror of the Tyk repositories from PackageCloud. This allows you to maintain an up-to-date mirror that can be easily accessed by multiple machines in the air-gapped environment. + +### Option B: Direct Package Download + +If mirroring the full repository is not practical, you can download individual `.deb` or `.rpm` packages directly from PackageCloud and transfer them manually. + + + + ```bash + # On the connected machine, download the .deb files + # Visit https://packagecloud.io/tyk/tyk-gateway to find package URLs, or use: + apt-get download tyk-gateway tyk-dashboard tyk-pump + + # Transfer the .deb files, then install on the air-gapped machine + sudo dpkg -i tyk-gateway_*.deb tyk-dashboard_*.deb tyk-pump_*.deb + ``` + + + ```bash + # On the connected machine, download the .rpm files + yumdownloader tyk-gateway tyk-dashboard tyk-pump + + # Transfer the .rpm files, then install on the air-gapped machine + sudo rpm -ivh tyk-gateway-*.rpm tyk-dashboard-*.rpm tyk-pump-*.rpm + ``` + + diff --git a/api-management/api-sharding.mdx b/api-management/api-sharding.mdx new file mode 100644 index 0000000000..09cb62f536 --- /dev/null +++ b/api-management/api-sharding.mdx @@ -0,0 +1,145 @@ +--- +title: "Gateway and API Sharding" +description: "Learn how to segment a Tyk cluster into zones using node and segment tags, so that specific Gateways selectively load specific APIs" +keywords: "API Sharding, Gateway Sharding, Segmentation, Node Tags, Segment Tags, Zones, Multi Data Center, GDPR, DMZ" +sidebarTitle: "Gateway & API Sharding" +--- + +## What is API Sharding ? + +It is possible to use tags in various Tyk objects to change the behavior of a Tyk cluster or to modify the data that is sent to the analytics engine. Tags are free-form strings that can be embedded in Gateway configurations, API definitions, Policies and Individual Keys. + +Tags are used in two ways: To segment a cluster into various "zones" of API management, and secondly, to push more data into the analytics records to make reporting and tracking easier. + +### API Sharding + +API Sharding is what we are calling our approach to segmenting a Tyk cluster (or data centers) into different zones. An example of this in action would be to imagine you have separate VPCs that deal with different classes of services, lets say: Health, Banking and Pharma. + +You don't need the nodes that handle all the traffic for your Pharma APIs to load up the definitions for the other zones' services, this could allow someone to send unexpected traffic through (it may not go anywhere). + +Alternatively, you could use segmentation to have separate API definitions for multiple data centers. In this way you could shard your API definitions across those DC's and not worry about having to reconfigure them if there is a failover event. + +### Using Sharding to handle API life-cycle with multiple data centers + +You can use sharding to very quickly publish an API from a `development` system to `staging` or `live`, simply by changing the tags that are applied to an API definition. + +With Tyk Community Edition and Tyk Pro, these clusters must all share the same Redis DB. + +If you are an Enterprise user, then you can go a step further and use the [Tyk Multi Data Center Bridge](/api-management/mdcb#managing-geographically-distributed-gateways-to-minimize-latency-and-protect-data-sovereignty) to have full multi-DC, multi-zone cluster segmentation, and manage APIs in different segments across different database back-ends. + +### Analytics and Reporting + +In order to use tags in analytics, there are two places where you can add a `"tags":[]` section: a Policy Definition, and a Session object for a token. + +Policy tags completely replace key tags, these tags are then fed into the analytics system and can be filtered in the dashboard. + +### Node Tags + +If your API is segmented, node tags will be appended to the analytics data, this will allow you to filter out all traffic going through a specific node or node cluster. + + + +If you set `use_db_app_options.node_is_segmented` to `true` for multiple gateway nodes, you should ensure that `management_node` is set to `false`. This is to ensure visibility for the management node across all APIs. + + + + +`management_node` is available from v2.3.4 and onwards. + +See [Tyk Gateway Configuration Options](/tyk-oss-gateway/configuration) for more details on node tags. + + +## Gateway Sharding + +With Tyk, it is easy to enable a sharded configuration, you can deploy Gateways which selectively load APIs. This unlocks abilities to run Gateways in multiple zones, all connected to the same Control Plane. This allows for GDPR deployments, development/test Gateways, or even DMZ/NON-DMZ Gateways. + +Couple this functionality with the Tyk [Multi Data Center Bridge](/api-management/mdcb#managing-geographically-distributed-gateways-to-minimize-latency-and-protect-data-sovereignty) to achieve a global, multi-cloud deployment. + +### Configure a Gateway as a shard + +Setting up a Gateway to be a shard, or a zone, is very easy. All you do is tell the node in the tyk.conf file what tags to respect and that it is segmented: + +```{.copyWrapper} +... +"db_app_conf_options": { + "node_is_segmented": true, + "tags": ["qa", "uat"] +}, + ... +``` + +Tags are always treated as OR conditions, so this node will pick up all APIs that are marked as `qa` or `uat`. + + + + + +In order to expose more details about the Gateway to the Dashboard, you can now configure the [edge_endpoints](/tyk-dashboard/configuration#edge_endpoints) section in the tyk-analytics.conf, and the Dashboard UI will pick that up and present you a list of Gateways you can chose from when creating an API. + + +### Tag an API for a shard using the Dashboard + +From the API Designer, select the **Advanced Options** tab: + +Advanced options tab + +Scroll down to the **Segment Tags** options: + +Segment tags section + +Set the tag name you want to apply, and click **Add**. + +When you save the API, the tags will become immediately active. If any Gateways are configured to only load tagged API Definitions then this configuration will only be loaded by the relevant Gateway. + +### Tag an API for a shard using Tyk Operator + +Add the tag names to the tags mapping field within an API Definition as shown in the example below: + +```yaml {linenos=table,hl_lines=["8-9"],linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + tags: + - edge + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + + +### Exposed Gateway tags to Dashboard UI + +From version 3.2.2 of the Tyk Dashboard, if [edge_endpoints](/tyk-dashboard/configuration#edge_endpoints) are being configured in tyk-analytics.conf, your Dashboard will automatically pick that list up for you, and display it in the UI when you create your API. + +List of available Gateways + +Once you select one or more Gateways, the *Segment Tags* section will be automatically prefilled with the tag values from the `edge_endpoints` configuration. + +List of segment tags + +Also, for every Gateway selected, there will be an API URL presented at the top of the page, within the *Core Settings* tab. + +List of API URLs + +### Target an API Definition via JSON + +In your API definition, add a tags section to the root of the API Definition: + +```{.copyWrapper} +"tags": ["private-gw"] +``` + +This will also set the tags for the API and when API requests are made through this Gateway, these tags will be transferred in to the analytics data set. + +### API Tagging with On-Premises + +API Sharding with Self-Managed is very flexible, but it behaves a little differently to sharding with Tyk Cloud Hybrid & Tyk Global Self-Managed deployments. The key difference is that with the latter, you can have federated Gateway deployments with **their own redis databases**. However with Tyk Self-Managed the zoning is limited to tags only, and must share a single Redis database. + +To isolate Self-Managed Gateway installations across data centers you will need to use Tyk Multi Data Center Bridge component. This system powers the functionality of Tyk Cloud & Tyk Cloud Hybrid in our cloud and is available to our enterprise customers as an add-on. diff --git a/api-management/api-versioning.mdx b/api-management/api-versioning.mdx new file mode 100644 index 0000000000..d051694dab --- /dev/null +++ b/api-management/api-versioning.mdx @@ -0,0 +1,739 @@ +--- +title: "API Versioning" +description: "Learn how to create and manage multiple versions of an API in Tyk" +keywords: "API versioning, version, Tyk Classic, Tyk OAS, API, versioning" +sidebarTitle: "API Versioning" +--- + +## Introduction + +API versioning is a crucial practice in API development and management that allows you to evolve your API over time while maintaining backward compatibility for existing clients. As your API grows and changes, versioning provides a structured way to introduce new features, modify existing functionality, or deprecate outdated elements without breaking integrations for users who rely on previous versions. + +API versioning is important for several reasons: +- **Flexibility**: It allows you to improve and expand your API without disrupting existing users. +- **Stability**: Clients can continue using a specific version of the API, ensuring their applications remain functional. +- **Transition management**: You can gradually phase out older versions while giving clients time to migrate to newer ones. +- **Documentation**: Each version can have its own documentation, making it easier for developers to understand the specific capabilities and limitations of the version they're using. + +--- + +## When to use API versioning + +There are many occasions when you might use versioning with your APIs, here are just a few examples. + +### Adding new features + +Imagine you're running an e-commerce API, and you want to introduce a new recommendation engine. Instead of modifying the existing endpoint and potentially breaking current integrations, you could create a new version of the API that includes this feature. This allows you to roll out the enhancement to interested clients while others continue using the previous version without disruption. + +### Changing response formats + +Let's say you have a weather API that currently returns temperatures in Fahrenheit. You decide to switch to Celsius for international standardization. By creating a new API version with this change, you can transition to the new format without affecting existing users who expect Fahrenheit readings. This gives clients time to adapt their applications to handle the new response format. + +### Deprecating outdated functionality + +If your financial API includes a legacy payment processing method that you plan to phase out, versioning allows you to create a new version without this feature. You can then encourage users to migrate to the new version over time, eventually deprecating the old version containing the outdated functionality. + +### Optimizing performance + +You might discover a more efficient way to structure your API requests and responses. By introducing these optimizations in a new version, you can offer improved performance to clients who are ready to upgrade, while maintaining the existing version for those who aren't prepared to make changes yet. + +## Sunsetting API versions + +API sunsetting is the process of phasing out or retiring an older version of an API or an entire API. It's a planned, gradual approach to ending support for an API or API version. To aid with the automation of this process, all Tyk API versions can be configured with an optional expiry date `expiration` (`expires` for Tyk Classic APIs), after which the API will no longer be available. If this is left blank then the API version will never expire. This is configured in standard ISO 8601 format. + +When sunsetting API versions, you may have endpoints that become deprecated between versions. It can be more user friendly to retain those endpoints but return a helpful error, instead of just returning `HTTP 404 Not Found`. + +This is easy to do with Tyk. You could, for example, include the deprecated endpoint in the new version of the API and configure the [mock response](/api-management/traffic-transformation/mock-response#mock-response) middleware to provide your clients with relevant information and instruction. Alternatively, you could return a `HTTP 302 Found` header and redirect the user to the new endpoint. + +--- + +## How API versioning works with Tyk + +API versioning is supported for all APIs that can be deployed on Tyk, both Tyk OAS APIs (for REST and Streaming services) and Tyk Classic APIs (used for GraphQL and TCP services). There are differences in the approach and options available when using the two API formats: + +- With Tyk OAS APIs you're essentially creating distinct iterations of your API, each with its own API definition file, allowing almost complete differentiation of configuration between your API versions. +- With Tyk Classic APIs all versions of an API are configured from a single API definition. This means that they share many features with only a subset available to be configured differently between versions. + +For more details, see [this comparison](#comparison-between-tyk-oas-and-tyk-classic-api-versioning). + +Some key concepts that are important to understand when versioning your APIs are: + +- [Version identifiers](/api-management/api-versioning#version-identifiers) +- [Default version](/api-management/api-versioning#default-version) +- [Base and child versions](/api-management/api-versioning#base-and-child-apis) +- [Controlling access to versions](/api-management/api-versioning#controlling-access-to-versioned-apis) + +### Version Identifiers + +The version identifier is the method by which the API client specifies which version of an API it is addressing with each request. Tyk supports multiple [locations](/api-management/api-versioning#version-identifier-location) within the request where this identifier can be placed. Typically the value assigned in the version identifier will be matched to the list of versions defined for the API and, assuming that the client is [authorized to access](/api-management/api-versioning#controlling-access-to-versioned-apis) that version, Tyk will apply the version specific processing to the request. + +#### Version identifier location + +Tyk supports three different locations where the client can indicate which version of an API they wish to invoke with their request: + +- [Request URL (path)](/api-management/api-versioning#request-url-path) +- [Query parameter](/api-management/api-versioning#query-parameter) +- [Request header](/api-management/api-versioning#request-header) + +When choosing a version identifier location, consider your API design philosophy, infrastructure requirements, client needs, caching strategy, and backward compatibility concerns. Whichever method you choose, aim for consistency across your API portfolio to provide a uniform experience for your API consumers. + +##### Request URL (path) + +Including the version identifier in the path (for example `/my-api/v1/users`) is a widely used approach recognized in many API designs. The version identifier is clearly visible in the request and, with the unique URL, can simplify documentation of the different versions. Tyk can support the version identifier as the **first URL fragment** after the listen path, such that the request will take the form `//`. + +##### Query parameter + +Defining a query parameter that must be provided with the request (for example `/my-api/users?version=v1`) is easy to implement and understand. The version identifier is clearly visible in the request and can be easily omitted to target a default version. Many analytics tools can parse query parameters, making this a very analytics-friendly approach to versioning. + +##### Request header + +Defining a specific header that must be provided with the request (for example `x-api-version:v1`) keeps the URL *clean*, which can be aesthetically pleasing and easier to read. It works well with RESTful design principles, treating the version as metadata about the request and allows for flexibility and the ability to make changes to the versioning scheme without modifying the URL structure. Headers are less visible to users than the request path and parameters, providing some security advantage. Be aware that other proxies or caches might not consider headers for routing, which could bring issues with this method. + +#### Stripping version identifier + +Typically Tyk will pass all request headers and parameters to the upstream service when proxying the request. For a versioned API, the version identifier (which may be in the form of a header, path parameter or URL fragment) will be included in this scope and passed to the upstream. + +The upstream (target) URL will be constructed by combining the configured `upstream.url` (`target_url` for Tyk Classic APIs) with the full request path unless configured otherwise (for example, by using the [strip listen path](/api-management/gateway-config-tyk-oas#listenpath) feature). + +If the version identifier is in the request URL then it will be included in the upstream (target) URL. If you don't want to include this identifier, then you can set `stripVersioningData` (`strip_versioning_data` for Tyk Classic APIs) and Tyk will remove it prior to proxying the request. + +#### Version identifier pattern + +When using the [Request URL](/api-management/api-versioning#request-url-path) for the versioning identifier, if Tyk is configured to strip the versioning identifier then the first URL fragment after the `listenPath` (`listen_path` for Tyk Classic APIs) will be deleted prior to creating the proxy URL. If the request does not include a versioning identifier and Tyk is configured to [fallback to default](/api-management/api-versioning#fallback-to-default), this may lead to undesired behaviour as the first URL fragment of the endpoint will be deleted. + +In Tyk 5.5.0 we implemented a new configuration option `urlVersioningPattern` (`url_versioning_pattern` for Tyk Classic APIs) to the API definition where you can set a regex that Tyk will use to determine whether the first URL fragment after the `listenPath` is a version identifier. If the first URL fragment does not match the regex, it will not be stripped and the unaltered URL will be used to create the upstream URL. + +### Default version + +When multiple versions are defined for an API, one must be declared as the **default version**. If a request is made to the API without providing the version identifier, then this will automatically be treated as a request to the *default* version. This has been implemented to support future versioning of an originally unversioned API, as you can continue to support legacy clients with the default version. + +Tyk makes it easy for you to specify - and change - the *default* version for your APIs. + +#### Fallback to default + +The standard behaviour of Tyk, if an invalid version is requested in the version identifier, is to reject the request returning `HTTP 404 This API version does not seem to exist`. Optionally, Tyk can be configured to redirect these requests to the *default* version by configuring the `fallbackToDefault` option in the API definition (`fallback_to_default` for Tyk Classic APIs). + + +### Base and child APIs + +Tyk OAS introduces the concept of a **base API**, which acts as a *parent* that routes requests to the different *child* versions of the API. The base API stores the information required for Tyk Gateway to locate and route requests to the appropriate *child* APIs. + +The *child* APIs do not have any reference back to the *parent* and so can operate completely independently if required. Typically, and we recommend, the *child* versions should be configured as Internal APIs that are not directly reachable by clients outside Tyk. + +The base API is a working version of the API and is usually the only one configured as an *External API*, so that client requests are handled (and routed) according to the configuration set in the base API (via the version identifier included in the header, url or query parameter). + +You can configure a Tyk OAS API as a *base API* by adding the `versioning` object to the `info` section in the Tyk Vendor Extension. This is where you will configure all the settings for the versioned API. The *child* APIs do not contain this information. + +Note that any version (*child* or *base*) can be set as the [default version](/api-management/api-versioning#default-version). + + + **Note** + + Tyk Classic APIs do not have the base and child concept because all versions share an API definition. + + +#### Reassigning the Base API + + + Available in the Tyk Dashboard from Tyk 5.12.0 + + +You can promote a child API to become the base API, replacing the original base API. The promoted API will become the source of truth for versioning and will be used to route requests that use the versioning identifier. This allows you to delete the original base API without *orphaning* all the child APIs, which was the behavior prior to this version. + +It is important to remember that the base API must be configured as an *External API* so that client requests can be handled and routed to the appropriate child API. Typically, you should configure the new base API as *External* and the previous base API, if it is to be retained, as *Internal* so that it no longer handles traffic directly. + +Note also that the new base API's listen path will now be used by the Gateway to route traffic so you must ensure this is correct. If you aim to keep the same listen path for traffic (so as not to impact clients) you must remember to update the listen paths for both the original and new base APIs to avoid duplication. + +You can reassign the base API using both + +- the [Dashboard API](/api-management/api-versioning#changing-the-base-version) +- the Dashboard application's [API Designer](/api-management/api-versioning#change-the-base-api) + +### Controlling access to versioned APIs + +Tyk's access control model supports very granular permissions to versioned APIs using the [`access_rights`](/api-management/access-control/sessions-and-keys/access-rights) assigned when a Key is authenticated. + +This means that you could restrict client access to only the [base API](/api-management/api-versioning#base-and-child-apis), while allowing developers to create and test new versions independently. These will only be added to the "routing table" in the base API when the API owner is ready and access keys could then be updated to grant access to the new version(s). + +Note that an access key will only have access to the `default` version if it explicitly has access to that version (e.g. if `v2` is set as default, a key must have access to `v2` to be able to [fallback to the default](/api-management/api-versioning#fallback-to-default) if the versioning identifier is not correctly provided in the request. + +When using Tyk OAS APIs each version of the API has a unique [API Id](/api-management/gateway-config-tyk-oas#info), so you simply need to identify the specific versions in the `access_rights` list in the key in the same way that you would add multiple different APIs to a single key. + + + +Creating a new version of a Tyk OAS API will not affect its API Id, so any access keys that grant access to the API will continue to do so, however they will not automatically be granted access to the new version (which will have a new API Id). + + + +When using Tyk Classic APIs you can explicitly grant access to specific versions of an API by specifying only those versions in the `versions` list in the key within the single entry for the API in the `access_rights` list. + +### Comparison between Tyk OAS and Tyk Classic API versioning + +As explained, there are differences between the way that versioning works for Tyk OAS and Tyk Classic APIs. + +These are largely due to the fact that a separate API definition is generated for each version of a Tyk OAS API, with one designated as the [base](/api-management/api-versioning#base-and-child-apis) version (which should be exposed on Tyk Gateway with the other (child) versions set to [internal](/advanced-configuration/transform-traffic/looping#internal-only-apis) visibility) whereas all versions of a Tyk Classic API are described by a single API definition. + +The Tyk Classic approach limits the range of features that can differ between versions. + +This table gives an indication of some of the features that can be configured per-version (✅) or only per-API (❌️) for Tyk OAS and Tyk Classic APIs. + +| Feature | Configurable in Tyk OAS versioning | Configurable in Tyk Classic versioning | +| :--------- | :------------------------------------ | :---------------------------------------- | +| Client-Gateway security | ✅ | ❌️ | +| Request authentication method | ✅ | ❌️ | +| API-level header transform | ✅ | ✅ | +| API-level request size limit | ✅ | ✅ | +| API-level rate limiting | ✅ | ❌️ | +| API-level caching | ✅ | ❌️ | +| Endpoints (method and path) | ✅ | ✅ | +| Per-endpoint middleware | ✅ | ✅ | +| Context and config data for middleware | ✅ | ❌️ | +| Custom plugin bundle | ✅ | ❌️ | +| Upstream target URL | ✅ | ✅ | +| Gateway-Upstream security | ✅ | ❌️ | +| Traffic log config | ✅ | ❌️ | +| API segment tags | ✅ | ❌️ | + +--- + +## Configuring API versioning in the API definition + +You can configure a Tyk OAS API as a [base API](/api-management/api-versioning#base-and-child-apis) by adding the `info.versioning` [object](/api-management/gateway-config-tyk-oas#versioning) to the [Tyk Vendor Extension](/api-management/gateway-config-tyk-oas#tyk-vendor-extension). + +Some notes on this: + +- if the *base* version is to be used as the *default* then you can use the value `self` as the identifier in the `default` field +- in the `versions` field you must provide a list of key-value pairs containing details of the *child* versions: + - `id`: the unique API Id (`x-tyk-api-gateway.info.id`) assigned to the API (either automatically by Tyk or user-defined during API creation) + - `name`: an identifier for this version of the API, for example `v2` + +The *child API* does not require any modification to its API definition. The important thing is that its API Id must be added to the `versions` list in the *base API* definition. We strongly recommend, however, that you configure `info.state.internal` to `true` for all child APIs so that they can only be accessed via the *base API*. + + + + **Note** + + If you are using Tyk Classic APIs, please see [this section](/api-management/api-versioning#versioning-with-tyk-classic-apis). + + + +### Example Tyk OAS Base API + +In the following example, we configure a *base API*: + +```json {hl_lines=["11-27"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-base-api", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "components": {}, + "x-tyk-api-gateway": { + "info": { + "versioning": { + "default": "v1", + "enabled": true, + "key": "x-api-version", + "location": "header", + "name": "v1", + "versions": [ + { + "id": "", + "name": "v2" + } + ], + "fallbackToDefault": true, + "stripVersioningData": false, + "urlVersioningPattern": "" + }, + "expiration": "2030-01-01 00:00", + "name": "example-base-api", + "state": { + "active": true, + "internal": false + } + }, + "server": { + "listenPath": { + "strip": true, + "value": "/example-base-api/" + } + }, + "upstream": { + "url": "http://httpbin.org/" + } + } +} +``` + +This API definition will configure Tyk Gateway to expect the `x-api-version` header to be provided and will invoke a version of the API as follows: +- if the header key has the value `v1` then the base API will be processed +- if it is `v2` then the request will be forwarded internally to the API with API Id `` +- if any other value is provided in the header, then the `default` version will be used (in this instance, the base API) because `fallbackToDefault` has been configured +- if the header is not provided, then the request will be handled by the `default` version (in this instance the base API) + +This API version will automatically expire on the 1st January 2030 and stop accepting requests. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out API versioning - though it requires a valid API Id to be used in place of ``. + +--- + +## API versioning in the Tyk Dashboard API Designer + +You can use the API Designer in the Tyk Dashboard to manage versions for your APIs. + + + **Note** + + If you are using Tyk Classic APIs, please see [this section](/api-management/api-versioning#tyk-classic-api-versioning-in-the-api-designer). + + +### Configure versioning + +From Tyk 5.10, you can pre-configure the [versioning metadata](#how-api-versioning-works-with-tyk) for an API before you've created the first [child API](/api-management/api-versioning#base-and-child-apis). + +1. Choose the API for which you want to create a new version (this can be an unversioned or versioned API) and go to the **Versions** tab + + An unversioned Tyk OAS API in the API Designer + +2. Select **Edit** and you can pre-fill the following metadata: + + - [Version identifier location](/api-management/api-versioning#version-identifier-location) + - Version identifier name (or [pattern](/api-management/api-versioning#version-identifier-pattern) for URL versioning) + - Version name for the base API + - [Stripping identifier from upstream request](/api-management/api-versioning#stripping-version-identifier) + - [Default fallback behaviour](/api-management/api-versioning#fallback-to-default) + +3. Click **Save API** + +### Create a new child version + +You can easily add a new version in the Tyk Dashboard's API Designer by following these steps. + +1. Choose the API for which you want to create a new version (this can be an unversioned or versioned API) and go to the **Versions** tab + +2. Select **Add New Version** to open the version creation wizard. + + Creating a new version of a Tyk OAS API using the version creation wizard + + Choose whether to start from an existing API configuration or to start from a blank API template. If there's already at least one child version, you can select which version (child or base) you wish to use as the template for your new API. + +3. If you have not already [configured](/api-management/api-versioning#configure-versioning) the versioning for this API you will prompted to complete this information. + + Configuring the version identifier from the version creation wizard + + If you've already done this, then you'll just need to provide a unique identifier name for your new child API and choose whether to make the new version the default choice. + + Configuring the version identifier from the version creation wizard + + +4. The final step is to choose whether to publish your new version straight away or to keep it in draft until you've completed configuring it. You can also optionally choose to make the API externally accessible, which will allow direct calls to the child API not just via the base API. + + Configuring the version identifier from the version creation wizard + +5. Select **Create Version** to complete the wizard. Your new API will be created and you can now adjust the configuration as required. + + +### Working with versioned APIs + +When you have a versioned API the *base API* will appear in the **Created APIs** list, with an expansion icon that you can select to reveal the versions. + +Versioned API shows in the Created APIs list + +Note that the *base* and *default* versions are highlighted with appropriate labels. You can reach the API Designer for each version in the usual way, by selecting the API name in the list (or from the **Actions** menu for the API). + +#### Switch between API versions + +When you are in the API Designer for a versioned API, you can switch between versions using the drop-down next to the API name. + +Choose between API versions in the API designer + +#### Manage Version Metadata + +You can manage all versions of your API from the **Versions** tab + +Manage the different versions of your API + +- You can see the versioning metadata + - enter **Edit** mode to make changes + - note that the common metadata can only be edited from the base API. +- You can see a list of all versions for the API and, from the **Actions** menu: + - go directly to that version + - delete that version + - set the *default* version + +#### Change the Base API + + + Available in the Tyk Dashboard from Tyk 5.12.0 + + +You can promote any child API version to become the base API. This will reassign the routing table from the existing base API, converting it into a child API. Be careful to remember to check the listen path for the new base API and that you have the correct exposure (external or internal) for each version. + +You can perform this action from the **Versions** tab of the current base API: + +Promote a child API version to become the base API + +- Open the three-dot menu for the child version that you want to promote +- Select **Make Base API** +- Be sure to check the listen path and visibility of the new and previous base API versions + +## API versioning with the Tyk Dashboard and Gateway APIs + +If you are not using the Dashboard's [API Designer](/api-management/api-versioning#api-versioning-in-the-tyk-dashboard-api-designer), you can use the Tyk Dashboard API to manage your APIs, including creating and linking base and child versions and, from Tyk 5.12.0, promoting a child API to become the base version. + +If you are not using Tyk Dashboard, you can use the Tyk Gateway API to create base and child APIs. + +You can either: +- manually [configure the version metadata](/api-management/api-versioning#configuring-api-versioning-in-the-api-definition) using the `info.versioning` section of the Tyk Vendor Extension before [importing the API definition](/api-management/gateway-config-managing-oas#loading-the-api-definition-into-tyk) via the Dashboard or Gateway API +- let Tyk manage the configuration of version metadata, as explained in the following sections + + + **Note** + + If you are using Tyk Classic APIs, please see [this section](/api-management/api-versioning#versioning-with-tyk-classic-apis). + + +### Creating Base Version + +Typically an existing API is used as the base version. You do not need to do anything to this, as Tyk will manage the updates to that API definition when the first child version is created. + +From **Tyk 5.10.0** you can optionally pre-configure the version metadata in the Tyk Vendor Extension of an unversioned API before creating any child versions, leaving `versioning.enabled=false` so that it is ignored, for example: + +```yaml +x-tyk-api-gateway: + info: + versioning: + enabled: false + default: v1 + fallbackToDefault: true + key: x-api-version + location: url + name: v1 + stripVersioningData: true +``` + +All of the `info.versioning` settings will be ignored while `info.versioning.enabled` is set to `false`. + +### Creating Child Versions + +When you want to create a child version for an existing API using the control API, you simply provide additional query parameters when creating the child API, to link it to the base API. + +These parameters are common to the `POST /api/apis/oas` and `POST /tyk/apis/oas` endpoints: + +| Parameter | Description | +| :---------------------- | :---------- | +| `base_api_id` | The API ID of the *base API* to which the new API will be linked as a child. | +| `base_api_version_name` | The version name (e.g. `v1`) that should be used to call the *base API*. If this has been [pre-configured](/api-management/api-versioning#creating-base-version) or this is not the first child API then it can be omitted. If provided, this will overwrite any existing base API version name. | +| `new_version_name` | The version name (e.g. `v2`) that will be used to route to this new child API. | +| `set_default` | Set this to `true` to make the new child API the [default version](/api-management/api-versioning#default-version). | + +These options are also available when [updating an existing API definition](/api-management/gateway-config-managing-oas#updating-an-api) using the `PATCH /api/apis/oas` or `PATCH /tyk/apis/oas` endpoints. + +#### Configuring the Versioning Identifier + +When using the control API to create the first new child version, if you have not already [pre-configured](/api-management/api-versioning#creating-base-version) the versioning identifier in the base API, the folowing settings will be used: + +- versioning identifier location: header +- versioning identifier key: `x-tyk-version` + +If you need to change these, you should do so within the `info.versioning` section of the API definition for the base API as explained [previously](/api-management/api-versioning#configuring-api-versioning-in-the-api-definition). + +### Changing the Base Version + + + Available in the Tyk Dashboard API from Tyk 5.12.0 + + +If you have a base API routing to one or more child APIs, you can promote a child version to become the base API. During this process, the base API will be linked as a child of the new base API. You simply make a call to the `PATCH /api/apis/oas/{current-base-api-id}/{next-base-api-id}` endpoint in the Tyk Dashboard API. + +| Property | Description | +| :----------- | :-------------------------------------------------------- | +| Resource URL | `/api/apis/oas/{current-base-api-id}/{next-base-api-id}` | +| Method | `PATCH` | +| Type | None | +| Body | None | +| Parameters | Path: `{current-base-api-id}` `{next-base-api-id}` | + +- you need to specify which APIs to change - and do so using the API ID value from the `info.id` field in their respective Tyk Vendor Extensions. +- if [API ownership](/platform-management/api-ownership) is in use, the user calling this endpoint must have *write* access to both APIs. +- this request will return `HTTP 400 Bad Request` if the `next-base-api-id` is not currently a child version of `current-base-api-id` + +Note that there is no equivalent endpoint in the Tyk Gateway API. + +### Deleting the Base Version + + + Available in the Tyk Dashboard API from Tyk 5.12.0 + + +Deleting a base API is no different from any other Tyk OAS API, except that it contains all the routing information for the linked child APIs. Typically if you delete the base API all the child APIs will be left "orphaned". If they are only exposed internally, no traffic can be routed to them. + +In Tyk 5.12.0 we added the facility to [promote a child API]() to become the base API - useful if you want to maintain the versioned API while deleting the original base version. + +We also added a new optional `purge` parameter to the [delete API](https://tyk.io/docs/api-reference/oas-apis/delete-oas-api) endpoint in the Tyk Dashboard API which will delete all child APIs linked to the API being deleted, allowing you to remove all versions of an API with one command. + + +## Versioning with Tyk Classic APIs + +All configuration for versioning of Tyk Classic APIs is documented [here](/api-management/gateway-config-tyk-classic#tyk-classic-api-versioning). + +For details on how to create a Tyk Classic API, check out [this guide](/api-management/gateway-config-managing-classic). + + +### Example versioned Tyk Classic API + +Here's an example of the minimal configuration that would need to be added to the API definition for a Tyk Classic API with two versions (`v1` and `v2`): + +```json {linenos=true, linenostart=1} +{ + "version_data": { + "not_versioned": false, + "default_version": "v1", + "versions": { + "v1": { + "name": "v1", + "expires": "", + "paths": { + "ignored": [], + "white_list": [], + "black_list": [] + }, + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [], + "transform": [], + "transform_response": [], + "transform_jq": [], + "transform_jq_response": [], + "transform_headers": [], + "transform_response_headers": [], + "hard_timeouts": [], + "circuit_breakers": [], + "url_rewrites": [], + "virtual": [], + "size_limits": [], + "method_transforms": [], + "track_endpoints": [], + "do_not_track_endpoints": [], + "validate_json": [], + "internal": [], + "persist_graphql": [] + }, + "global_headers": {}, + "global_headers_remove": [], + "global_headers_disabled": false, + "global_response_headers": {}, + "global_response_headers_remove": [], + "global_response_headers_disabled": false, + "ignore_endpoint_case": false, + "global_size_limit": 0, + "override_target": "" + }, + "v2": { + "name": "v2", + "expires": "", + "paths": { + "ignored": [], + "white_list": [], + "black_list": [] + }, + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [], + "transform": [], + "transform_response": [], + "transform_jq": [], + "transform_jq_response": [], + "transform_headers": [], + "transform_response_headers": [], + "hard_timeouts": [], + "circuit_breakers": [], + "url_rewrites": [], + "virtual": [], + "size_limits": [], + "method_transforms": [], + "track_endpoints": [], + "do_not_track_endpoints": [], + "validate_json": [], + "internal": [], + "persist_graphql": [] + }, + "global_headers": {}, + "global_headers_remove": [], + "global_headers_disabled": false, + "global_response_headers": {}, + "global_response_headers_remove": [], + "global_response_headers_disabled": false, + "ignore_endpoint_case": false, + "global_size_limit": 0, + "override_target": "http://httpbin.org/ip" + } + } + }, + "definition": { + "location": "header", + "key": "x-api-version", + "strip_versioning_data": false, + "fallback_to_default": true, + "url_versioning_pattern": "" + } +} +``` + +In this example, there are two versions of the API +- the version identifier is expected in a request header `x-api-version` +- the versions are named `v1` and `v2` +- the only difference between `v1` and `v2` is that `v2` will proxy the request to a different upstream via the configured `override_target` +- the default version (`default_version`) is `v1` +- if the request header contains an invalid version named (e.g. `v3`), it will be directed to the default (`fallback_to_default:true`) + +### Tyk Classic API versioning in the API Designer + +You can use the API Designer in the Tyk Dashboard to add and manage versions for your Tyk Classic APIs. + +#### Create a versioned API + +1. **Enable versioning** + + In the API Designer, navigate to the **Versions** tab. + + Enabling versioning for a Tyk Classic API + + Deselect the **Do not use versioning** checkbox to enable versioning and display the options. + +2. **Configure the versioning identifier** + + Choose from the drop-down where the version identifier will be located and, if applicable, provide the key name (for query parameter or request header locations). + + Configuring the versioning identifier + +3. **Add a new version** + + You will see the existing (`Default`) version of your API in the **Versions List**. You can add a new version by providing a version name (which will be the value your clients will need to provide in the version location when calling the API). + + You can optionally configure an **Override target host** that will replace the target path that was set in the base configuration for the version. Note that this is not compatible with Service Discovery or Load Balanced settings. + + Select **Add** to create this new version for your API. + + Adding a new version to your API + +4. **Set the default version** + + You can choose any of your API versions to act as the [default](/api-management/api-versioning#default-version). + + Choosing the default version for your API + + Select **Update** to save the changes to your API. + +#### Switch between versions of a Tyk Classic API + +When you are in the API Designer for a versioned Tyk Classic API, you can switch between versions from the **Edit Version** dropdown in the **Endpoint Designer** tab. + +Choosing the API version for which to configure endpoint middleware + +Remember to select **Update** to save the changes to your API. + +### Configuring Tyk Classic API versioning in Tyk Operator + + +When using Tyk Operator, you can configure versioning for a Tyk Classic API within `spec.definition` and `spec.version_data`. + +In the following example: + +- the version identifier is a header with the name `x-api-version` (comments demonstrate how to configure the alternative version identifier locations) +- the API has one version with the name `v1` +- the default version is set to `v1` +- an allow list, block list and ignore authentication middleware have been configured for version `v1` +- an alternative upstream URL (`override_target`) is configured for `v1` to send requests to `http://test.org` + +```yaml {linenos=table,hl_lines=["14-17", "25-27", "29-82"], linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: versioned-api +spec: + name: Versioned API + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://version-api.example.com + listen_path: /version-api + strip_listen_path: true + definition: + # Tyk should find version data in Header + location: header + key: x-api-version + + # Tyk should find version data in First URL Element + #location: url + + # Tyk should find version data in URL/Form Parameter + #location: url-param + #key: api-version + version_data: + default_version: v1 + not_versioned: false + versions: + v1: + name: v1 + expires: "" + override_target: "http://test.org" + use_extended_paths: true + extended_paths: + ignored: + - path: /v1/ignored/noregex + method_actions: + GET: + action: no_action + code: 200 + data: "" + headers: + x-tyk-override-test: tyk-override + x-tyk-override-test-2: tyk-override-2 + white_list: + - path: v1/allowed/allowlist/literal + method_actions: + GET: + action: no_action + code: 200 + data: "" + headers: + x-tyk-override-test: tyk-override + x-tyk-override-test-2: tyk-override-2 + - path: v1/allowed/allowlist/reply/{id} + method_actions: + GET: + action: reply + code: 200 + data: flump + headers: + x-tyk-override-test: tyk-override + x-tyk-override-test-2: tyk-override-2 + - path: v1/allowed/allowlist/{id} + method_actions: + GET: + action: no_action + code: 200 + data: "" + headers: + x-tyk-override-test: tyk-override + x-tyk-override-test-2: tyk-override-2 + black_list: + - path: v1/disallowed/blocklist/literal + method_actions: + GET: + action: no_action + code: 200 + data: "" + headers: + x-tyk-override-test: tyk-override + x-tyk-override-test-2: tyk-override-2 +``` diff --git a/api-management/authentication/basic-authentication.mdx b/api-management/authentication/basic-authentication.mdx new file mode 100644 index 0000000000..874552f171 --- /dev/null +++ b/api-management/authentication/basic-authentication.mdx @@ -0,0 +1,194 @@ +--- +title: "Basic Authentication" +description: "How to configure basic authentication in Tyk?" +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Secure APIs, Basic Authentication" +sidebarTitle: "Basic Authentication" +--- + +## What is Basic Authentication? + +Basic Authentication is a straightforward authentication method where the user's credentials (username and password) are sent to the server, usually in a standard HTTP header. + +## How does Basic Authentication Work? + +The user credentials are combined and encoded in this form: + +``` +Basic base64Encode(username:password) +``` + +A real request could look something like: + +``` +GET /api/widgets/12345 HTTP/1.1 +Host: localhost:8080 +Authorization: Basic am9obkBzbWl0aC5jb206MTIzNDU2Nw== +Cache-Control: no-cache +``` + +In this example the username is `john@smith.com` and the password is `1234567` (see [base64encode.org](https://www.base64encode.org)) + +### The Problem with Basic Authentication + +With Basic Authentication, the authentication credentials are transferred from client to server as encoded plain text. This is not a particularly secure way to transfer the credentials as it is highly susceptible to intercept; as the security of user authentication is usually of critical importance to API owners, Tyk recommends that Basic Authentication should only ever be used in conjunction with additional measures, such as [mTLS](/api-management/implement-tls#secure-hosted-apis-with-mtls). + +## Configuring your API to use Basic Authentication + +The OpenAPI Specification indicates the use of [Basic Authentication](https://swagger.io/docs/specification/v3_0/authentication/basic-authentication/) in the `components.securitySchemes` object using the `type: http` and `scheme: basic`: + +```yaml +components: + securitySchemes: + myAuthScheme: + type: http + scheme: basic + +security: + - myAuthScheme: [] +``` + +With this configuration provided by the OpenAPI description, all that is left to be configured in the Tyk Vendor Extension is to enable authentication, to select this security scheme and to indicate where Tyk should look for the credentials. Usually the credentials will be provided in the `Authorization` header, but Tyk is configurable, via the Tyk Vendor Extension, to support custom header keys and credential passing via query parameter or cookie. + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + header: + enabled: true + name: Authorization +``` + +Note that URL query parameter keys and cookie names are case sensitive, whereas header names are case insensitive. + +You can optionally [strip the user credentials](/api-management/client-authentication#managing-authorization-data) from the request prior to proxying to the upstream using the `authentication.stripAuthorizationData` field (Tyk Classic: `strip_auth_data`). + +### Multiple User Credential Locations + +The OpenAPI Specification's `securitySchemes` mechanism allows only one location for the user credentials, but in some scenarios an API might need to support multiple potential locations to support different clients. + +The Tyk Vendor Extension supports this by allowing configuration of alternative locations in the basic auth entry in `server.authentication.securitySchemes`. Building on the previous example, we can add optional query and cookie locations as follows: + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + header: + enabled: true + name: Authorization + query: + enabled: true + name: query-auth + cookie: + enabled: true + name: cookie-auth +``` + +### Extract Credentials from the Request Payload + +In some cases, for example when dealing with SOAP, user credentials can be passed within the request body rather in the standard Basic Authentication format. You can configure Tyk to handle this situation by extracting the username and password from the body using regular expression matching (regexps). + +You must instruct Tyk to check the request body by adding the `extractCredentialsFromBody` field to the basic auth entry in `server.authentication.securitySchemes`, for example: + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + extractCredentialsFromBody: + enabled: true + userRegexp: '(.*)' + passwordRegexp: '(.*)' +``` + +Note that each regexp should contain only one match group, which must point to the actual values of the user credentials. + +### Caching User Credentials + +The default behaviour of Tyk's Basic Authentication middleware is to cache user credentials, improving the performance of the authentication step when a client makes frequent requests on behalf of the same user. + +When a request is received, it presents credentials which are checked against the users registered in Tyk. When a match occurs and the request is authorized, the matching credentials are stored in a cache with a configurable refresh period. When future requests are received, Tyk will check the presented credentials against those in the cache first, before checking the full list of registered users. + +The cache will refresh after `cacheTTL` seconds (Tyk Classic: `basic_auth.cache_ttl`). + +If you do not want to cache user credentials, you can turn this off using `disableCaching` in the basic auth entry in `server.authentication.securitySchemes` (Tyk Classic: `basic_auth.disable_caching`). + +### Using Tyk Classic APIs + +As noted in the Tyk Classic API [documentation](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis), you can select Basic Authentication using the `use_basic_auth` option. This will default to expect the user credentials in the `Authorization` header. + + +## Using Tyk Dashboard to Configure Basic Authentication + +Using the Tyk Dashboard, you can configure the Basic Authentication method from the Server section in the API Designer by enabling **Authentication** and selecting **Basic Authentication** from the drop-down: + +Target Details: Basic Auth + +- select the location(s) where Tyk should look for the token +- provide the key name for each location (we prefill the default `Authorization` for the *header* location, but you can replace this if required) +- optionally select [strip authorization data](/api-management/client-authentication#managing-authorization-data) to remove the auth token locations from the request prior to proxying to the upstream +- optionally configure the [basic authentication cache](/api-management/authentication/basic-authentication#caching-user-credentials) +- optionally configure [extraction of credentials from the request body](#extract-credentials-from-the-request-payload) + +## Registering Basic Authentication User Credentials with Tyk + +When using Basic Authentication, the API key used to access the API is not generated by the Tyk system, instead you need to create and register the credentials of your users with Tyk. Tyk will compare the credentials provided in the request against the list of users you have created. + +The way that this is implemented is through the creation of a key that grants access to the API (as you would for an API protected by [auth token](/api-management/authentication/bearer-token)), however for this key you will provide a username and password. + +When calling the API, users would never use the key itself as a token, instead their client must provide the Basic Auth credentials formed from the registered username and password, as [described previously](#how-does-basic-authentication-work). + + +### Using Tyk Dashboard UI + +You can use the Tyk Dashboard to register a user's Basic Authentication credentials that can then be used to access your API. + +Navigate to the **Keys** screen and select **Add Key**. + +Follow the instructions in the [access key guide](/api-management/gateway-config-managing-classic#access-an-api) and you'll notice that, when you select the Basic Auth protected API, a new **Authentication** tab appears: + +Note that the **Authentication** tab will also be displayed if you create a key from a policy that grants access to a Basic Auth protected API. + +Complete the user's credentials on this tab and create the key as normal. The key that is created in Tyk Dashboard is not in itself an access token (that is, it cannot be used directly to gain access to the API) but is used by Tyk to validate the credentials provided in the request and to determine the appropriate authorization, including expiry of authorization. + +### Using the Tyk Dashboard API + +You can register user credentials using the `POST /api/apis/keys/basic/{username}` endpoint in the [Tyk Dashboard API](/tyk-dashboard-api). The request payload is a [Session Object](/api-management/access-control/sessions-and-keys/understanding-sessions). + +- the user's *username* is provided as a path parameter +- the user's *password* is provided as `basic_auth_data.password` within the request payload + +You use the `POST` method to create a new user and `PUT` to update an existing entry. + + + +Be careful to ensure that the `org_id` is set correctly and consistently so that the Basic Authentication user is created in the correct organization. + + + +### Using the Tyk Gateway API + +You can register user credentials using the `POST /tyk/keys/{username}` endpoint in the [Tyk Dashboard API](/tyk-dashboard-api). The request payload is a [Session Object](/api-management/access-control/sessions-and-keys/understanding-sessions). + +- the user's *username* is provided as a path parameter +- the user's *password* is provided as `basic_auth_data.password` within the request payload + +You use the `POST` method to create a new user and `PUT` to update an existing entry. + + + +Be careful to ensure that the `org_id` is set correctly and consistently so that the Basic Authentication user is created in the correct organization. + + + + diff --git a/api-management/authentication/bearer-token.mdx b/api-management/authentication/bearer-token.mdx new file mode 100644 index 0000000000..fb6a5acaf6 --- /dev/null +++ b/api-management/authentication/bearer-token.mdx @@ -0,0 +1,198 @@ +--- +title: "Authentication Token" +description: "How to use Authentication Tokens to Secure APIs" +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Secure APIs, Bearer Tokens, Auth Token, Authentication Token, API Key" +sidebarTitle: "Auth Token" +--- + +## Introduction + +[IETF RFC6750](https://datatracker.ietf.org/doc/html/rfc6750) opens with: +> Any party in possession of a bearer token (a "bearer") can use it to get access to the associated resources (without demonstrating possession of a cryptographic key). To prevent misuse, bearer tokens need to be protected from disclosure in storage and in transport. + +The bearer token is a cryptic string, usually generated by the server. When the client makes a request to consume an API on the server, it must send this token in a header, typically as: `Authorization: Bearer `. When the server receives the token, it checks its validity and authentcates that the "bearer" of the token is the entity to which the token was issued. + +The [OpenAPI Specification](https://swagger.io/docs/specification/v3_0/authentication/api-keys/) defines an API Key as: +> a token that a client provides when making API calls. API keys are supposed to be a secret that only the client and server know ... API key-based authentication is only considered secure if used together with other security mechanisms such as HTTPS/SSL. + +Bearer Token, API Key, Authentication Token... from the perspective of Tyk these are different names for the same thing: a "token" string that can be used to access an API secured using Tyk's **Auth Token** authentication method. + +### An Overview of the Auth Token method + +Tyk's "Auth Token" authentication method is a flexible mechanism that functions like a Bearer Token but is defined as an 'API Key' according to OpenAPI 3.0 standards. + +- Tyk Gateway issues Auth Tokens (called **Keys** in the Tyk Dashboard App) +- For each token, a session state object is created with the Redis key containing the token string + - this session object contains details of the access rights and consumption limits to be applied to the client presenting the token +- The client can present the token in a header, query parameter or cookie + - the location is configurable within the API definition +- The `Bearer` identifier is optional when using a header for the token +- When the token is presented, it is used as a simple key lookup against the Redis keys and validated if a matching session object is found +- From Tyk 5.12.0 the token can be [bound to a client certificate](/api-management/authentication/bearer-token#client-certificate-token-binding) to provide an extra layer of authentication security + - when this is in use, Tyk validates that the certificate presented with the token matches that bound to the session + +## Configuring your API to use Auth Token + +The OpenAPI Specification indicates the use of Auth Tokens (API Keys) in the `components.securitySchemes` object using `type: apiKey`. It also includes specification of the location (`in`) and key (`name`) that are to be used when providing the token to the API, for example: + +```yaml +components: + securitySchemes: + myAuthScheme: + type: apiKey + in: header + name: Authorization + +security: + - myAuthScheme: [] +``` + +With this configuration provided by the OpenAPI description, all that is left to be configured in the Tyk Vendor Extension is to enable authentication and to select this security scheme. + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true +``` + +Note that URL query parameter keys and cookie names are case sensitive, whereas header names are case insensitive. + +You can optionally [strip the auth token](/api-management/client-authentication#managing-authorization-data) from the request prior to proxying to the upstream using the `authentication.stripAuthorizationData` field (Tyk Classic: `strip_auth_data`). + +## Multiple Auth Token Locations + +The OpenAPI Specification's `securitySchemes` mechanism allows only one location for the auth token, but in some scenarios an API might need to support multiple potential locations to support different clients. + +The Tyk Vendor Extension supports this by allowing configuration of alternative locations in the auth token entry in `server.authentication.securitySchemes`. Building on the previous example, we can add optional query and cookie locations as follows: + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + query: + enabled: true + name: query-auth + cookie: + enabled: true + name: cookie-auth +``` + +## Client Certificate - Token Binding + +In **Tyk 5.12.0** we introduced an option when combining the Auth Token method with [static mTLS](/api-management/implement-tls#using-a-static-client-certificate-allow-list) to form a binding association between the Auth Token issued to a client and their client certificate. + +This works as follows: + +1. The client certificate must first be registered with the Tyk Certificate Store and added to the statically declared *allow list* in the API definition, the act of doing this enforces the mTLS handshake for requests to the API. +2. When the Auth Token is issued by Tyk, the certificate is bound to the session object that is created in Redis (the certificateID is stored in the [`mtls_static_certificate_bindings`](/api-management/access-control/sessions-and-keys/understanding-sessions#authentication-data) field in the Session) +3. When a request is made by the client, they must provide a certificate to satisfy the mTLS handshake. This certificate is then checked against the allow list in the API definition and then the binding in the session object. If it is not present in the allow list or not in the bound certificates list, authentication will fail and the request will be rejected. + +Multiple client certificates can be bound to a token, for additional flexibility. Proactive maintenance of the list of bound certificates supports seamless rotation of certificates, as the new certificate can be registered and bound prior to the client switching to use it. + +This feature is fully backward-compatible. Existing tokens that do not have certificate bindings will continue to work as before, as the binding check is simply skipped. No additional configuration is required in the Tyk Gateway configuration nor in the API Definition. + + + Policies do not currently support certificate-token binding. This will be added in a future release. + + + +## Dynamic mTLS with Auth Token +The Auth Token method can support [Dynamic mTLS](/api-management/implement-tls#using-a-dynamic-client-certificate-allow-list) where the client can provide a TLS certificate in lieu of a standard Auth Token. This can be configured for an API using the [enableClientCertificate](/api-management/gateway-config-tyk-oas#token) option (Tyk Classic: `auth.use_certificate`). + +You can use your own identity provider to generate access tokens, then import them to Tyk via `POST /tyk/keys/{keyID}` in the Tyk Gateway API. This lets Tyk manage access control, quotas, and rate limiting for you. + +## Legacy Options + +### Dynamic mTLS with Auth Token + + + *Dynamic mTLS* became a standalone authentication method in Tyk 5.12.0 and is now more accurately called [Certificate Authentication](/api-management/authentication/certificate-auth) + + +Prior to Tyk 5.12.0, [Dynamic mTLS](/api-management/implement-tls#using-a-dynamic-client-certificate-allow-list) was configured via the Auth Token method by setting the `enableClientCertificate` flag (Tyk Classic: `auth_configs.authToken.use_certificate`). + +```json +server: + authentication: + enabled: true + securitySchemes: + authToken: + enabled: true + enableClientCertificate: true +``` + +This was later changed to Certificate Authentication, as explained [here](/api-management/implement-tls#legacy-dynamic-mtls-mode). + +### Auth Token with Signature + +If you are migrating from platforms like Mashery, which use request signing, you can enable signature validation alongside auth token by configuring the additional [signatureValidation](/api-management/gateway-config-tyk-oas#token) field (Tyk Classic: `auth.signature`). + +You can configure: + +- the location of the signature +- the algorithm used to create the signature (`MasherySHA256` or `MasheryMD5`) +- secret used during signature (which can be retrieved from the [Session metadata](/api-management/access-control/sessions-and-keys/session-metadata)) +- an allowable clock skew + +## Using Tyk Dashboard to Configure Auth Token + +Using the Tyk Dashboard, you can configure the Auth Token authentication method from the Server section in the API Designer by enabling **Authentication** and selecting **Auth Token** from the drop-down: + +Configuring the Auth Token method + +- select the location(s) where Tyk should look for the token +- provide the key name for each location (we prefill the default `Authorization` for the *header* location, but you can replace this if required) +- select **Strip authorization data** to remove the auth token locations from the request prior to proxying to the upstream, as described [here](/api-management/client-authentication#managing-authorization-data) + +Note that the [auth token + signature](/api-management/authentication/bearer-token#auth-token-with-signature) option is not available in the Tyk Dashboard API Designer. + +### Binding a Client Certificate to a Token + +Certificates are bound to tokens when the tokens are issued or when the session object is subsequently updated. + +When an API secured by Auth Token and mTLS is selected in the **Access Rights** tab of the **API Security > Keys** screen, an additional tab will be displayed: **Authentication**. + +Creating an API Key for an API secured with Auth Token and mTLS + +On the **Authentication** tab there is an option to bind certificates to the token: + +Optionally bind a client certificate to the API Key + +If you select this option, you can then select the client certificate to bind to the new token. You can choose from the existing client certificates in the Tyk Certificate Store or, if required, you can upload new certificates (in PEM format). Tyk will register these within the Tyk Certificate Store so that they are available for use. + +Select **Attach Certificate**. + +Uploading a client certificate to bind to the API Key + +The certificate will be added to a list of bound certificates. + +The new cert is shown in the list of bound certificates + +You can optionally add more certificates, or proceed to issue the key by selecting **Create Key**. + +You can add and remove certificate bindings for existing tokens from the **Update Key** screen: + +The new cert is shown in the list of bound certificates + + + + **Note** + + Binding a certificate to an Auth Token does not automatically add that certificate to the static allow list for the API(s) that the token is authorised to access. When a request is made using the token, the presented client certificate will be checked against the allow list after the mTLS handshake has completed and before the binding is checked. Authentication will fail if the certificate is not in the allow list. + + + +## Using Tyk Classic APIs + +As noted in the Tyk Classic API [documentation](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis), a new Tyk Classic API will use the auth (bearer) token method by default with the token expected in the `Authorization` header, so configuration is slightly different as there is no need to `enable` this method. You should configure the `auth` object for any non-default settings, such as a different token location or Dynamic mTLS. + + + diff --git a/api-management/authentication/certificate-auth.mdx b/api-management/authentication/certificate-auth.mdx new file mode 100644 index 0000000000..39da59c008 --- /dev/null +++ b/api-management/authentication/certificate-auth.mdx @@ -0,0 +1,95 @@ +--- +title: "Certificate Authentication" +description: "Authenticate using just an mTLS client certificate" +keywords: "Authentication, Tyk Authentication, Mutual TLS, mTLS, Client mTLS, certificate" +sidebarTitle: "Certificate Authentication" +--- + +## What is Certificate Authentication? + +Certificate Authentication is a client authentication method introduced in Tyk 5.12.0 that replaces the legacy [Dynamic mTLS](/api-management/implement-tls#using-a-dynamic-client-certificate-allow-list) feature. This method provides enhanced security and flexibility for API authentication using client certificates. + +### Evolution from Dynamic mTLS + +Certificate Authentication has evolved from Dynamic mTLS through the enforcement of the mutual TLS handshake and the disallowing of authentication using a token. Only the registered client certificate can now be used to authenticate with the Gateway. + +This change was introduced because Dynamic mTLS treated the certificate as optional and did not enforce the mTLS handshake. Mutual TLS was not enforced if only the token was presented in the request, reducing the security of the Dynamic mTLS authentication method. + +For more details, see [the problem with Dynamic mTLS](/api-management/implement-tls#legacy-dynamic-mtls-mode). + +If you are currently using Dynamic mTLS, no change is required to your API definition to use Certificate Authentication. + +When using Tyk OAS APIs, the legacy configuration (`x-tyk-api-gateway.server.authentication.securitySchemes.authToken.enableClientCertificate`) is still supported (though marked as deprecated in favor of a new, cleaner configuration). + +When using Tyk Classic APIs, there is no change to the configuration in the API definition (`auth_configs.authToken.useCertificate`). + + + The legacy mode (where the token can be used to authenticate with Tyk) is available via the Gateway configuration option `allow_unsafe_dynamic_mtls_token`. + + +## How does Certificate Authentication work? + +Certificate Authentication uses X.509 [client certificates](/api-management/certificates#digital-certificates) to authenticate API requests. It relies upon a one-to-one mapping between API clients and client certificates. + +When a client makes a request: + +1. The client presents their certificate during the mTLS handshake +3. If the client is successfully authenticated, Tyk checks the client certificate against a list of authorized certificates (the "dynamic allow list") +4. If a match is found, authorization proceeds as usual, based on the content of the linked session and any policies applied to it + +Each client certificate must be [pre-registered](/api-management/authentication/certificate-auth#registering-certificate-authentication-user-credentials) with the [Tyk Certificate Store](/api-management/certificates#tyk-certificate-store) and a [Session](/api-management/access-control/sessions-and-keys/understanding-sessions) created for each in the temporal storage (Redis) to create the dynamic allow list. This list is dynamic because certificate-linked session objects (and hence clients) can be added to or removed from the list without making any change to the API definition. This is in contrast to the [static allow list](/api-management/implement-tls#using-a-static-client-certificate-allow-list) approach where the list of authorized certificates is stored in the API definition. + + +## Configuring your API to use Certificate Authentication + + + The Gateway must be configured to use TLS for the [hosted API interface](/api-management/implement-tls#tyk-gateway-as-a-tls-server-inbound-connections). + + +Certificate Auth is configured within the Tyk Vendor Extension by adding the `certificateAuth` object within the `server.authentication` section and enabling authentication. + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + certificateAuth: + enabled: true +``` + +There are no additional configuration options for this authentication method. The client must present their certificate in the usual manner for the mTLS handshake, for example: + +```bash + curl --cert client_cert.pem --key client_key.pem https://my-gateway/my-api/ +``` + +Note that the `HTTPS` protocol must be used. + +### Using Tyk Classic + +As noted in the Tyk Classic API [documentation](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis), you can select Certificate Authentication using the `auth_configs.authToken.useCertificate` option. + + +## Using Tyk Dashboard to Configure Certificate Authentication + +Using the Tyk Dashboard, you can configure the Certificate Auth method from the Server section in the API Designer by enabling **Authentication** and selecting **Certificate Authentication** from the drop-down: + +Selecting Certificate Authentication in the Tyk OAS API Designer + +## Registering Certificate Authentication User Credentials + +The *dynamic allow list* comprises session state objects in the Gateway's temporal storage (typically Redis) that reference the client certificates that should be accepted. + +1. First you must [register](/api-management/certificates#tyk-certificate-store-api) the client certificate with the Tyk Certificate Store and note the certificate ID that is assigned. + +2. Next you *create a key*, providing the certificate ID in the `certificate` field of the session object payload. + - Tyk Gateway API: `POST /tyk/keys/create` + - Tyk Dashboard API: `POST /api/keys/create` + +3. Tyk will create a session object with the Redis key containing the certificate ID, which forms part of the dynamic allow list. + - The Redis key is formed from a combination of the Organization ID and Certificate ID + - Deleting this object (key) will remove the certificate from the allow list, restricting access to any client presenting that certificate + +From the Tyk Dashboard UI, if you [create a key](/getting-started/using-tyk-dashboard#api-security) that grants access to an API secured with a dynamic allow list, the **Authentication** tab will be displayed, where you can select the client certificate from the Tyk Certificate Store. + +Associating a client certificate with an API for Certificate Authentication \ No newline at end of file diff --git a/api-management/authentication/custom-auth.mdx b/api-management/authentication/custom-auth.mdx new file mode 100644 index 0000000000..0d2055fcd0 --- /dev/null +++ b/api-management/authentication/custom-auth.mdx @@ -0,0 +1,19 @@ +--- +title: "Custom Authentication" +description: "How to implement custom authentication in Tyk using Go plugins, Python CoProcess, and JSVM plugins." +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Go Plugins, Python CoProcess, JSVM Plugin" +sidebarTitle: "Custom Authentication" +--- + +## Go Plugins + +Go Plugin Authentication allows you to implement custom authentication logic using the Go programming language. This method is useful for scenarios where you need to implement specialized authentication mechanisms that are not natively supported by Tyk. +To learn more about using Tyk Golang Plugins, go [here](/api-management/plugins/golang) + +## Use Python CoProcess and JSVM Plugin Authentication + +Tyk allows for custom authentication logic using Python and JavaScript Virtual Machine (JSVM) plugins. This method is useful for implementing unique authentication mechanisms that are tailored to your specific requirements. + +* See [Custom Authentication with a Python plugin](/api-management/plugins/rich-plugins#custom-authentication-plugin-tutorial) for a detailed example of a custom Python plugin. +* See [JavaScript Middleware](/api-management/plugins/javascript#) for more details on using JavaScript Middleware. + diff --git a/api-management/authentication/jwt-authorization.mdx b/api-management/authentication/jwt-authorization.mdx new file mode 100644 index 0000000000..9f1f37e576 --- /dev/null +++ b/api-management/authentication/jwt-authorization.mdx @@ -0,0 +1,346 @@ +--- +title: "JWT Authorization" +description: "Tyk Gateway's JWT Authorization process extracts user identity and applies security policies based on JWT claims for API access control." +keywords: "Authentication, Authorization, JWT, JSON Web Tokens, Claims, Validation" +sidebarTitle: "Authorization" +--- + +## Availability + +| Component | Editions | +| :------------- | :------------------------- | +| Tyk Gateway | Community and Enterprise | + +## Introduction + +[JSON Web Tokens (JWT)](https://www.jwt.io/introduction) are a popular method for client authentication and authorization that can be used to secure access to your APIs via Tyk's [JWT Auth](/basic-config-and-security/security/authentication-authorization/json-web-tokens) method. + +After the JWT signature has been [validated](/basic-config-and-security/security/authentication-authorization/json-web-tokens), Tyk uses the **claims** within the token to determine which security policies (access rights, rate limits and quotas) should be applied to the request. + +From Tyk 5.10, Tyk can perform optional [validation](/api-management/authentication/jwt-claim-validation) of these claims. + +In this page, we explain how Tyk performs JWT authorization, including how it identifies the user and the policies to be applied. + +## JWT Authorization Flow + +When a request with a JWT arrives at Tyk Gateway, after the authentication (signature and claim validation) step, Tyk performs the following steps to authorize the request: + +1. **Identity Extraction**: The user identity is extracted from the token according to this order of precedence: + - The `kid` header (unless `skipKid` is enabled) + - A custom claim (specified in `subjectClaims`) + - The standard `sub` claim (fallback) + +2. **Policy Resolution**: Tyk determines which [Policy](/api-management/policies) to apply to the request: + - From scope-to-policy mapping + - From default policies + +3. **Update Session**: The [Session](/api-management/access-control/sessions-and-keys/understanding-sessions) is updated with the identity and policies. + +In the following sections, we provide a detailed explanation of each of these steps. + +## Identifying the Session Owner + +A unique identity is stored in the Session to associate it with the authenticated user. This identifier is extracted from the JWT by checking the following fields in order of precedence: + +1. The standard Key ID header (`kid`) in the JWT (unless the `skipKid` option is enabled) +2. The subject identity claim identified by the value(s) stored in `subjectClaims` (which allows API administrators to designate any JWT claim as the identity source (e.g., user_id, email, etc.). + + When multiple values are provided in the `subjectClaims` array, Tyk processes them as follows: + + 1. Tyk tries each claim **in the exact order they appear** in the array + 2. For each claim, Tyk checks if: + - The claim exists in the token + - The claim value is a string and is not empty + 3. Tyk uses the **first valid, non-empty value** it finds and stops processing further claims + 4. If none of the claims yield a valid identity, Tyk proceeds to the next stage (the `sub` claim) + + + + + Prior to Tyk 5.10, the subject identity claim was retrieved from `identityBaseField`; see [using multiple identity providers](#using-multiple-identity-providers) for details and for the Tyk Classic API alternative. + + + +3. The `sub` [registered claim](/api-management/authentication/jwt-claim-validation#registered-vs-custom-claims). + +**Example** + +In this example, `skipKid` has been set to `true`, so Tyk checks the `subjectClaims` and determines that the value in the custom claim `user_id` within the JWT should be used as the identity for the session object. + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + skipKid: true + subjectClaims: [user_id] +``` + + + +Session objects can be cached to improve performance, so the identity extraction is only performed on the first request with a JWT, or when the cache is refreshed. + + + +## Identifying the Tyk Policies to be applied + +[Policies](/api-management/policies) are applied (or mapped) to the Session to configure authorization for the request. Policies must be [registered](/api-management/access-control/policies/managing-policies#creating-policies) with Tyk, such that they have been allocated a unique [*Policy Id*](/api-management/access-control/policies/applying-policies#policy-ids). + +Tyk supports three different types of policy mapping, which are applied in this priority order: + +1. Direct policy mapping +2. Scope policy mapping +3. Default policy mapping + +### Direct policies + +You can optionally specify policies to be applied to the session via the *policy claim* in the JWT. This is a [Private](https://datatracker.ietf.org/doc/html/rfc7519#section-4.3) Claim (not a registered claim) and can be anything you want, but typically we recommend the use of `pol`. You must instruct Tyk where to look for the policy claim by configuring the `basePolicyClaims` field in the API definition. + +Note that we typically refer to Private Claims as Custom Claims. + +In this example, Tyk has been configured to check the `pol` claim in the JWT to find the *Policy Ids* for the policies to be applied to the session object: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + basePolicyClaims: [pol] +``` + +In the JWT, you should then provide the list of Tyk policy IDs as an array of values in that claim, for example you might declare: + +``` + "pol": ["685a8af28c24bdac0dc21c28", "685bd90b8c24bd4b6d79443d"] +``` + + + +Prior to Tyk 5.10, the base policy claim was retrieved from `policyFieldName`; see [using multiple identity providers](#using-multiple-identity-providers) for details and for the Tyk Classic API alternative. + + + +### Default policies + +A *default policy* is a fallback option if no specific policies are identified from the JWT claims and prevents a session from being created with no authorization to interact with APIs on the Gateway. +You **must** configure one or more default policies unless using [scope policies](/api-management/authentication/jwt-authorization#scope-policies). + + +Prior to **Tyk 5.11.0** a default policy was required even when using scope policies. + + +Default policies are configured using the `defaultPolicies` field in the API definition, which accepts a list of policy IDs. + + +The Gateway will return `HTTP 403 Forbidden` if no default policies are configured (prior to Tyk 5.11 or if scope policies are not in use), if the referenced policies don’t exist, or if policies are invalid or incorrectly formatted. + + + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + defaultPolicies: + - 685a8af28c24bdac0dc21c28 + - 685bd90b8c24bd4b6d79443d +``` + +### Scope policies + +Directly mapping policies to APIs relies on the sharing of Tyk Policy IDs with the IdP (so that they can be included in the JWT) and may not provide the required flexibility. + +Tyk supports a more advanced approach where policies are applied based on scopes declared in the JWT. This keeps separation between the IdP and Tyk-specific concepts, and supports much more flexible configuration. + +Within the JWT, you identify a Private Claim that will hold the authorization (or access) scopes for the API. You then provide, within that claim, a list of *scopes*. In your API definition, you configure the `scopes.claims` to instruct Tyk where to look for the scopes and then you declare a mapping of scopes to policies within the `scopes.scopeToPolicyMapping` object. + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + scopes: + scopeToPolicyMapping: + - scope: read: users + policyId: 685bd90b8c24bd4b6d79443d + - scope: write: users + policyId: 685a8af28c24bdac0dc21c28 + claims: [accessScopes] +``` + +In this example, Tyk will check the `accessScopes` claim within the incoming JWT and apply the appropriate policy if that claim contains the value `read: users` or `write: users`. If neither scope is declared in the claim, or the claim is missing, the default policy will be applied. + + + +Prior to Tyk 5.10, the authorization scopes claim was retrieved from `scopes.claimName`; see [using multiple identity providers](#using-multiple-identity-providers) for details and for the Tyk Classic API alternative. + + + +#### Declaring Multiple Scopes + +You can declare multiple scopes by setting the value of the **authorization scopes claim** in one of the following ways: + +* **String with space-delimited list of values (standard format)** + + ```json + "accessScopes": "read: users write: users" + ``` + +* **Array of strings** + + ```json + "accessScopes": ["read: users", "write: users"] + ``` + +* **String with space-delimited list inside a nested key** + + ```json + "accessScopes": { "access": "read: users write: users" } + ``` + +* **Array of strings inside a nested key** + + ```json + "accessScopes": { "access": ["read: users", "write: users"] } + ``` + +**Important:** + +* If your scopes are defined inside a nested key, use **dot notation** for the `scopes.claims` value. + + * For **examples 1 and 2**, set `scopes.claims` to: + + ``` + accessScopes + ``` + * For **examples 3 and 4**, set `scopes.claims` to: + + ``` + accessScopes.access + ``` + +**Example JWT fragment:** +If this JWT is provided to an API configured as described above, Tyk will apply both policies to the session object. + +```json +{ + "sub": "1234567890", + "name": "Alice Smith", + "accessScopes": ["read: users", "write: users"] +} +``` + +### Combining policies + +Where multiple policies are mapped to a session (for example, if several scopes are declared in the JWT claim, or if you set multiple *default policies*), Tyk will apply all the matching policies to the request, combining their access rights and using the most permissive rate limits and quotas. It's important when creating those policies to ensure that they do not conflict with each other. + +Policies are combined as follows: + +1. Apply direct-mapped policies declared via `basePolicyClaims` +2. Apply scope-mapped policies declared in `scopeToPolicyMapping` based upon scopes in the JWT +3. If no policies have been applied in steps 1 or 2, apply the default policies from `defaultPolicies` + +When multiple policies are combined, the following logic is applied: + +- **access rights** A user gets access to an endpoint if ANY of the applied policies grant access +- **consumption limits** Tyk uses the most permissive values (highest quota, highest throughput ) +- **other settings** The most permissive settings from any policy are applied + +### Policy Best Practices + +When creating multiple policies that might be applied to the same JWT, we recommend using [partitioned policies](/api-management/access-control/policies/applying-policies#partitioned-policies) - policies that control specific aspects of API access rather than trying to configure everything in a single policy. + +For example: + +- Create one policy that grants read-only access to specific endpoints +- Create another policy that grants write access to different endpoints +- Create a third policy that sets specific rate limits + +To ensure these policies work correctly when combined: + +- Set `per_api` to `true` in each policy. This ensures that the policy's settings only apply to the specific APIs listed in that policy, not to all APIs globally. +- Avoid listing the same `API ID` in multiple policies with conflicting settings. Instead, create distinct policies with complementary settings that can be safely combined. + + +## Session Updates + +After authenticating the token and extracting the necessary identity and policy information, Tyk creates or updates a session object that controls access to the API. + +The following [session attributes](/api-management/access-control/sessions-and-keys/understanding-sessions#configuration-options) are modified based on the Policies: + +1. **Access Rights**: Determines which API endpoints the token can access +2. **Rate Limits**: Controls how many requests per second/minute the token can make +3. **Quotas**: Sets the maximum number of requests allowed in a time period +4. **Metadata**: Custom metadata from the policies is added to the session +5. **Tags**: Policy tags are added to the session + +In addition to updating the session, Tyk extracts claims from the JWT and makes them available as context variables for use in other [middleware](/api-management/traffic-transformation). + + + +When a JWT's claims change (for example, by configuring different scopes or policies), Tyk updates the session with the new policies on the subsequent request made with the token. + + + +## Advanced Configuration + +### Using Multiple Identity Providers + +When using multiple Identity Providers (IdPs), you may need to check different claim locations for the same information. Tyk supports definition of **multiple claim locations** for subject identity and policy IDs. + +* **Before Tyk 5.10 (and for Tyk Classic APIs):** + + * The Gateway could only check **single claims** for: + + * Subject identity + * Base policy + * Scope-to-policy mapping + * This setup didn’t support multiple IdPs using different claim names (e.g **Keycloak** uses `scope` and **Okta** uses `scp`) + +* **From Tyk 5.10 onwards (Tyk OAS APIs):** + + * You can configure **multiple claim names** for: + + * Subject identity + * Base policy + * Scope-to-policy mapping + * This allows Tyk to locate data across various tokens and IdPs more flexibly. + +**Configuration summary:** + +| API Configuration Type | Tyk Version | Subject Identity Locator | Base Policy Locator | Scope-to-Policy Mapping Locator | +| :---------------------- | :----------- | :------------------------- | :----------------------- | :------------------------------- | +| Tyk OAS | pre-5.10 | `identityBaseField` | `policyFieldName` | `scopes.claimName` | +| Tyk OAS | 5.10+ | `subjectClaims` | `basePolicyClaims` | `scopes.claims` | +| Tyk Classic | all | `jwt_identity_base_field` | `jwt_policy_field_name` | `jwt_scope_claim_name` | + +**Example configuration:** + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + # Legacy single field (still supported) + identityBaseField: "sub" + + # New multi-location support (Tyk 5.10+) + subjectClaims: + - "sub" + - "username" + - "user_id" +``` + +#### Backward Compatibility + +The new configuration is fully backward compatible: + +- Existing `identityBaseField`, `policyFieldName`, and `scopes.claimName` settings continue to work +- If both old and new fields are specified, the new fields take precedence +- When using only new fields, the first element in each array is used to set the corresponding legacy field for backward compatibility + diff --git a/api-management/authentication/jwt-claim-validation.mdx b/api-management/authentication/jwt-claim-validation.mdx new file mode 100644 index 0000000000..f575ffbd05 --- /dev/null +++ b/api-management/authentication/jwt-claim-validation.mdx @@ -0,0 +1,813 @@ +--- +title: "JWT Claim Validation" +description: "Tyk Gateway's JWT Claim Validation enables fine-grained access control by validating registered and custom claims in JSON Web Tokens." +keywords: "Authentication, JWT, JSON Web Tokens, Claims, Validation" +sidebarTitle: "Claim Validation" +--- + +## Availability + +| Component | Editions | +| :------------- | :------------------------- | +| Tyk Gateway | Community and Enterprise | + +## Introduction + +A JSON Web Token consists of three parts separated by dots: `header.payload.signature`. The payload contains the claims, a set of key-value pairs that carry information about the token and its subject. + +Tyk can validate these claims to ensure that incoming JWTs meet your security requirements before granting access to your APIs. + +By validating JWT claims, you can enforce fine-grained access control policies, ensure tokens originate from trusted sources, and verify that users have the appropriate permissions for your APIs. + + + +**Viewing JWT Claims** + +To inspect the claims in a JWT, use online tools like [jwt.io](https://jwt.io) for quick debugging + + + +{/* ## Quick Start */} + +## JWT Claims Fundamentals + +### Registered vs Custom Claims + +JWT claims can be categorized into two types: + +- **Registered Claims**: + + Registered Claims are standardized by the JWT specification ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1)) and have predefined meanings. + + These claims are further grouped into: + + - **Temporal Claims:** time-based validation + - **Identity Claims:** content-based validation + + | Claim | Name | Purpose | Type | + | ----- | --------------- | ---------------------------------------- | -------- | + | `iss` | Issuer | Identifies who issued the token | Identity | + | `aud` | Audience | Identifies who the token is intended for | Identity | + | `sub` | Subject | Identifies the subject of the token | Identity | + | `exp` | Expiration Time | When the token expires | Temporal | + | `iat` | Issued At | When the token was issued | Temporal | + | `nbf` | Not Before | When the token becomes valid | Temporal | + | `jti` | JWT ID | Unique identifier for the token | Identity | + +- **Custom Claims**: + + Custom Claims, referred to as Private Claims in the [JWT Specification](https://datatracker.ietf.org/doc/html/rfc7519#section-4.3), are application-specific and can contain any information relevant to your use case, such as user roles, permissions, department, or metadata. + +**Example JWT Payload with Both Registered and Custom Claims**: + +```json +{ + // Registered claims + "iss": "https://auth.company.com", + "aud": "api.company.com", + "sub": "user123", + "exp": 1735689600, + "iat": 1735603200, + + // Custom claims + "department": "engineering", + "role": "admin" +} +``` + +### Supported Claims and API Types + +| Claim Category | Sub-Category | Tyk OAS APIs | Tyk Classic APIs | Version | +| :--------------------- | :----------------------------------------- | :------------------------- | :----------------------------- | :------------------ | +| **Registered Claims** | **Temporal** (`exp`, `iat`, `nbf`) | ✅ Yes | ✅ Yes | All versions | +| **Registered Claims** | **Identity** (`iss`, `aud`, `sub`, `jti`) | ✅ Yes | ❌ Yes | 5.10+ | +| **Custom Claims** | — | ✅ Yes | ❌ No | 5.10+ | + +### How Tyk Processes JWT Claims + +After [verifying](/basic-config-and-security/security/authentication-authorization/json-web-tokens) that the token hasn't been tampered with, Tyk processes claims in this order: + +1. **Claims Extraction**: All claims from the JWT payload are extracted and stored in [context variables](/api-management/traffic-transformation/request-context-variables) with the format `jwt_claims_CLAIMNAME`. For example, a claim named `role` becomes accessible as `jwt_claims_role`. + +2. **Claims Validation**: + - [Registered Claims Validation](#registered-claims-validation): Checks standard claims against your configuration + - [Custom Claims Validation](#custom-claims-validation): Applies your business rules to custom claims + - [Authorization](/api-management/authentication/jwt-authorization): Uses validated claims to determine API access and apply policies + +If any validation step fails, Tyk rejects the request with a specific error message indicating which claim validation failed and why. + +## Registered Claims Validation + +[Registered Claims](#registered-vs-custom-claims) are grouped into: +- **Temporal claims** (time-based validation): Supported in both Tyk Classic APIs and OAS APIs +- **Identity claims** (content-based validation): Available only in Tyk OAS APIs + +### Temporal Claims + +Temporal claims define the validity period of a JWT. Tyk automatically validates these claims when present in the token. + +- **Expiration Time (exp)**: the `exp` claim specifies when the token expires (as a Unix timestamp). Tyk rejects tokens where the current time is after the expiration time. +- **Issued At (iat)**: the `iat` claim specifies when the token was issued. Tyk rejects tokens that claim to be issued in the future. +- **Not Before (nbf)**: the `nbf` claim specifies the earliest time the token can be used. Tyk rejects tokens before this time. + +#### Clock Skew Configuration + +Due to the nature of distributed systems, you may encounter clock skew between your Identity Provider and Tyk servers. You can configure tolerance for timing differences: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + issuedAtValidationSkew: 5 # Allow tokens issued up to 5 seconds in the future + notBeforeValidationSkew: 2 # Allow tokens to be valid 2 seconds early + expiresAtValidationSkew: 2 # Allow tokens to be valid 2 seconds past expiration +``` + +- `expiresAtValidationSkew` allows recently expired tokens to be considered valid +- `issuedAtValidationSkew` allows tokens claiming future issuance to be valid +- `notBeforeValidationSkew` allows tokens to be valid before their `nbf` time + + + + + Temporal claim validation and the associated clock skew controls were supported by Tyk before 5.10.0 and also for [Tyk Classic APIs](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis) + + + +### Identity Claims + +Identity claims provide information about the token's origin and intended use. Unlike temporal claims, these require explicit configuration to enable validation. + +#### Issuer Validation (iss) + +Validates that a trusted Identity Provider issued the token: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + allowedIssuers: + - "https://auth.company.com" + - "https://auth.partner.com" +``` + +Tyk accepts tokens if the `iss` claim matches any configured issuer. If `allowedIssuers` is empty, no issuer validation is performed. + +#### Audience Validation (aud) + +Validates that the token is intended for your API: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + allowedAudiences: + - "api.company.com" + - "mobile-app" +``` + +The `aud` claim can be a string or an array. Tyk accepts tokens if any audience value matches any configured audience. If `allowedAudiences` is empty, no audience validation is performed. + +#### Subject Validation (sub) + +Validates the token subject against allowed values: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + allowedSubjects: + - "user" + - "service-account" + - "admin" +``` + +Useful for restricting API access to specific types of subjects or known entities. If `allowedSubjects` is empty, no subject validation is performed. + +#### JWT ID Validation (jti) + +Validates that the token contains a unique identifier: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + jtiValidation: + enabled: true +``` + +When enabled, Tyk requires the `jti` claim to be present. This is useful for token tracking and revocation scenarios. Note that Tyk does not perform any validation on the content of the claim, only that it is present. + +### Configuration Examples + +Basic registered claims validation: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + allowedIssuers: ["https://auth.company.com"] + allowedAudiences: ["api.company.com"] + jtiValidation: + enabled: true + expiresAtValidationSkew: 5 +``` + +Multi-IdP configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + allowedIssuers: + - "https://auth0.company.com" + - "https://keycloak.company.com" + allowedAudiences: + - "api.company.com" + - "mobile.company.com" + subjectClaims: ["sub", "username"] +``` + +In this example, we expect one Identity Provider to present the subject in the `sub` claim, and the other to present it in the `username` claim. + +## Custom Claims Validation + +Custom claims validation allows you to enforce business-specific rules on JWT tokens beyond the standard registered claims. + +**Use Cases**: + +- **Role-based access control**: Validate that users have required roles (for example, `admin`, `editor`, `viewer`) +- **Department restrictions**: Ensure users belong to authorized departments +- **Feature flags**: Check if users have access to specific features or API endpoints +- **Geographic restrictions**: Validate user location or region-based access +- **Subscription tiers**: Enforce access based on user subscription levels + +### Validation Types + +The custom claims validation supports three distinct validation types. These validation types can be applied to any custom claim in your JWT tokens, providing flexible control over your authorization logic. + +#### Required + +Required type validation ensures that a specific claim exists in the JWT token, regardless of its value. + +**Use Cases:** + +- Ensuring user metadata is present (even if empty) +- Validating that required organizational fields exist +- Confirming compliance with token structure requirements + +**Behavior:** + +- ✅ **Passes** if the claim exists with any non-null value (including empty strings, arrays, or objects) +- ❌ **Fails** if the claim is missing or explicitly set to `null` + +**Example Configuration:** + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + department: + type: required + user_metadata: + type: required +``` + +#### Exact Match + +Exact match type validation verifies that a claim's value exactly matches one of the specified allowed values. + +**Use Cases:** + +- Role validation (e.g., `admin`, `editor`, `viewer`) +- Environment-specific access (e.g., `production`, `staging`, `development`) +- Subscription tier validation (e.g., `premium`, `standard`, `basic`) +- Boolean flag validation (`true`, `false`) + +**Behavior:** + +- ✅ Passes if the claim value exactly matches any value in the allowedValues array +- ❌ Fails if the claim value doesn't match any allowed value, if the claim is missing, or if allowedValues is empty +- Case-sensitive for string comparisons +- Type-sensitive (string "true" ≠ boolean true) + +**Example Configuration:** + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + role: + type: exact_match + allowedValues: + - admin + - editor + - viewer + subscription_tier: + type: exact_match + allowedValues: + - premium + - standard +``` + +#### Contains + +The Contains type validation checks whether a claim's value contains or includes one of the specified values. This validation type works differently depending on the data type of the claim and is particularly useful for array-based permissions and substring matching. + +**Use Cases:** + +- Permission arrays (`["read: users", "write: posts", "admin: system"]`) +- Tag-based access control +- Partial string matching for departments or locations +- Multi-value scope validation + +**Behavior by Data Type:** + +Arrays: +- ✅ Passes if the array contains any of the specified values +- ❌ Fails if none of the specified values are found in the array + +Strings: +- ✅ Passes if the string contains any of the specified substrings +- ❌ Fails if none of the specified substrings are found + +Other Types: +- Converts to a string and performs substring matching + +Example Configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + permissions: + type: contains + allowedValues: + - admin: system + - write:api + department_code: + type: contains + allowedValues: + - ENG + - SALES +``` + +With this configuration, a token might contain these claims: + +```json +{ + "permissions": ["read: users", "write: posts", "admin: system"], + "department_code": "ENG-BACKEND", +} +``` + +In this example: +- `permissions` validation passes because the array contains `"admin: system"` +- `department_code` validation passes because the string contains `"ENG"` + +### Data Type Support + +The framework is designed to handle the diverse data types commonly found in JWT tokens. The validation behavior adapts intelligently based on the actual data type of each claim, ensuring robust and predictable validation across different token structures. + +#### Supported Data Types + +##### String Values + +String claims are the most common type in JWT tokens and support all three validation types with intuitive behavior. + +**Validation behavior** + +- **Required**: Passes if the string exists (including empty strings `""`) +- **Exact Match**: Performs case-sensitive string comparison +- **Contains**: Checks if the string contains any of the specified substrings + +**Example** + +Claims: + +```json +{ + "department": "Engineering", + "user_id": "user123", + "email": "john.doe@company.com" +} +``` + +Validation configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + department: + type: exact_match + allowedValues: + - Engineering + - Sales + - Marketing + email: + type: contains + allowedValues: + - "@company.com" + - "@partner.com" +``` + +##### Numeric Values + +Numeric claims (integers and floating-point numbers) are validated with type-aware comparison logic. + +**Validation behavior** + +- **Required**: Passes if the number exists (including `0`) +- **Exact Match**: Performs numeric equality comparison (`42` matches `42.0`) +- **Contains**: Converts to a string and performs substring matching + +**Example** + +Claims: + +```json +{ + "user_level": 5, + "account_balance": 1250.75, + "login_count": 0 +} +``` + +Validation configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + user_level: + type: exact_match + allowedValues: + - 1 + - 2 + - 3 + - 4 + - 5 + account_balance: + type: required +``` + +##### Boolean Values + +Boolean claims are commonly used for feature flags and permission toggles. + +**Validation Behavior** + +- **Required**: Passes if the boolean exists (`true` or `false`) +- **Exact Match**: Performs strict boolean comparison +- **Contains**: Converts to string (`"true"` or `"false"`) and performs substring matching + +**Example** + +Claims: + +```json +{ + "is_admin": true, + "email_verified": false, + "beta_features": true +} +``` + +Validation configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + is_admin: + type: exact_match + allowedValues: + - true + email_verified: + type: required +``` + +##### Array Values + +Arrays are particularly powerful for permission systems and multi-value attributes. + +**Validation behavior** + +- **Required**: Passes if the array exists (including empty arrays `[]`) +- **Exact Match**: Checks if the entire array exactly matches one of the allowed arrays +- **Contains**: Checks if the array contains any of the specified values (most common use case) + +**Example** + +Claims: + +```json +{ + "roles": ["user", "editor"], + "permissions": ["read: posts", "write: posts", "delete: own"], + "departments": ["engineering", "product"], + "tags": [] +} +``` + +Validation configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + permissions: + type: contains + allowedValues: + - write: posts + - admin: system + roles: + type: contains + allowedValues: + - admin + - editor + - moderator + tags: + type: required +``` + +##### Object Values + +Complex object claims can be validated, though typically you'll want to validate specific nested properties using [dot notation](#nested-claims). + +**Validation Behavior** + +- **Required**: Passes if the object exists (including empty objects `{}`) +- **Exact Match**: Performs deep object comparison (rarely used) +- **Contains**: Converts to a JSON string and performs substring matching + +**Example** + +Claims: + +```json +{ + "user_metadata": { + "department": "Engineering", + "level": 5, + "location": "US" + }, + "preferences": {} +} +``` + +Configuration: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + user_metadata: + type: required + preferences: + type: required +``` + +##### Type Coercion and Edge Cases + +**Null and Undefined Values** + +- null values: Always fail validation (treated as missing) +- undefined/missing claims: Fail all validation types except when validation is not configured + +**Mixed-Type Arrays** + +Arrays containing different data types are supported. The `contains` validation will attempt to match values using appropriate type comparison, + +```json +{ + "mixed_permissions": ["read", 42, true, "admin"] +} +``` + +**Type Mismatches** + +When the expected value type doesn't match the claim type, Tyk performs intelligent conversion: + +- Numbers to strings: `42` becomes `"42"` +- Booleans to strings: `true` becomes "`true"` +- Objects/arrays to strings: Converted to JSON representation + +##### Best Practices + +- Be Explicit About Types: When configuring `allowedValues`, use the same data type as expected in the token +- Use Arrays for Multi-Value Validation: Prefer array-based claims for permissions and roles +- Consider Empty Values: Remember that empty strings, arrays, and objects pass `required` validation +- Test Type Coercion: Verify behavior when token types don't match expected types + +### Nested Claims + +JSON Web Tokens often contain complex, hierarchical data structures with nested objects and arrays. Tyk's custom claims validation framework supports validating nested claim structures using dot notation syntax. + +**Basic Syntax:** + +- `user.name` - Access the `name` property within the `user` object +- `permissions.0.resource` - Access the `resource` property of the first element in the `permissions` array + + + + + **Dot Notation** + + Tyk uses [gjson](https://github.com/tidwall/gjson) to parse dot notation paths. + + + +#### Nested Object Validation + +The most common use case for dot notation is validating properties within nested objects, such as user metadata, organizational information, or configuration settings. + +**Example Token** + +```json +{ + "user": { + "name": "John Doe", + "email": "john.doe@company.com", + "profile": { + "department": "Engineering", + "level": "senior", + "location": { + "country": "US", + "region": "West" + } + } + } +} +``` + +You could set the following configuration to validate the requester's department and level: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + user.profile.department: + type: exact_match + allowedValues: + - Engineering + - Sales + - Marketing + user.profile.level: + type: contains + allowedValues: + - senior + - lead + - principal +``` + + +#### Nested Array Validation + +Arrays are commonly used in JWT claims to represent lists of permissions, roles, or other multi-value attributes. Tyk supports validating specific elements within arrays using dot notation with numeric indices. + +**Example Token** + +```json +{ + "permissions": [ + { + "resource": "users", + "actions": ["read", "write"] + }, + { + "resource": "reports", + "actions": ["read"] + } + ] +} +``` + +You can validate specific array elements: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + "permissions.0.resource": + type: exact_match + allowedValues: ["users"] + "permissions.1.actions.0": + type: exact_match + allowedValues: ["read"] +``` + + + +**Dot Notation** + +When a nested path doesn't exist (e.g., `user.profile.level` but `profile` doesn't exist) or when an array index is out of bounds (e.g., `permissions.999.resource`), the claim is treated as missing. This will cause validation to fail for blocking rules or generate a warning for non-blocking rules. + + + +#### Recommendations + +Test your nested claim validation rules with representative JWT tokens to ensure they behave as expected. Use online tools like [gjson.dev](https://gjson.dev/) to experiment with dot notation paths and verify they correctly access the desired values. + +### Non-blocking Validation + +Non-blocking validation allows JWT claims to fail validation with a warning logged, while still permitting the request to proceed. + +This behavior allows you to: + +- Monitor how new validation rules would affect traffic without disrupting users +- Gradually roll out stricter validation requirements +- Debug validation issues in production environments + +#### How Non-blocking Validation Works + +When configured, a validation rule can be set to "non-blocking" mode, which means: + +1. If validation passes, the request proceeds normally +2. If validation fails, instead of rejecting the request: + - A warning is logged to the Tyk Gateway log file at the `WARN` log level + - The validation process continues to evaluate other custom claims + - The request is allowed to proceed to the upstream API + +#### Configuring Non-Blocking Mode + +Non-blocking mode can be configured for any custom claim validation rule with the addition of the boolean `nonBlocking` flag, for example: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + customClaimValidation: + user.profile.department: + type: exact_match + allowedValues: + - Engineering + - Sales + - Marketing + user.preferences.notifications: + type: required + nonBlocking: true +``` + +The `nonBlocking` flag in the validation rule for `user.preferences.notifications` means that if this claim is missing from the received token, the token will not fail validation, but a warning will be logged. + +## FAQ + + + + +Yes, you can configure `AllowedIssuers` to specify which iss (issuer) claim values are accepted. Tokens from other issuers will be rejected. + + + +Use the `CustomClaimValidation` configuration to validate specific claims with different validation types (Required, ExactMatch, or Contains). + + + \ No newline at end of file diff --git a/api-management/authentication/jwt-quick-start.mdx b/api-management/authentication/jwt-quick-start.mdx new file mode 100644 index 0000000000..5afd1473ad --- /dev/null +++ b/api-management/authentication/jwt-quick-start.mdx @@ -0,0 +1,95 @@ +--- +title: "JWT Quick Start: Securing APIs with Auth0 or Keycloak" +description: "Learn how to secure your Tyk OAS APIs using JWT authentication with Auth0 or Keycloak as identity providers." +keywords: "Authentication, JWT, JSON Web Tokens, Quick Start" +sidebarTitle: "Quick Start" +--- + +In this tutorial, we'll secure a Tyk OAS API using JWT authentication with either Auth0 or Keycloak as the identity provider. + + +If you want to try out JWT Auth without linking up to a third-party IdP then you can skip step 1 and provide the base64 encoded public key for your JWT (in the `source` field rather than configuring `jwksURIs`) in step 3. You'll need to generate a JWT for the request, but otherwise everything stays the same. + +Now back to the tutorial... + + +We'll start by configuring the identity provider, then set up JWT validation in Tyk, create a security policy, configure the API to use the policy, and finally test the secured API with a valid token. + +### Prerequisites + +- A Tyk installation (Cloud or Self-Managed) with Tyk Dashboard license +- An Auth0 account or Keycloak installation +- An existing Tyk OAS API (see [this tutorial](/api-management/gateway-config-managing-oas#using-tyk-dashboard-api-designer-to-create-an-api)) +- Postman, cURL, or another API testing tool + +### Step-by-Step Guide + +1. **Configure Your Identity Provider to obtain your JWKS URI** + + The first step is to configure your Identity Provider (IdP) to issue JWTs and provide a JWKS URI that Tyk can use to validate the tokens. Below are instructions for both Auth0 and Keycloak. + + + + + 1. Log in to your Auth0 dashboard + 2. Navigate to Applications > APIs and click Create API + 3. Enter a name and identifier (audience) for your API + 4. Note your Auth0 domain (e.g. `your-tenant.auth0.com`) + 5. Your JWKS URI will be: `https://your-tenant.auth0.com/.well-known/jwks.json` + + + + + + 1. Log in to your Keycloak admin console + 2. Create or select a realm (e.g. `tyk-demo`) + 3. Navigate to Clients and create a new client with: + - Client ID: `tyk-api-client` + - Client Protocol: `openid-connect` + - Access Type: `confidential` + 4. After saving, go to the Installation tab and select "OIDC JSON" format + 5. Your JWKS URI will be: `http://your-keycloak-host/realms/tyk-demo/protocol/openid-connect/certs` + + + + + +2. **Create a Security Policy** + + 1. In the Tyk Dashboard, navigate to **Policies** + 2. Click **Add Policy** + 3. Configure the policy: + - Name: `JWT Auth Policy` + - APIs: Select your Tyk OAS API + - Access Rights: Configure appropriate paths and methods + - Authentication: Select JWT + - JWT Scope Claim Name: Enter the JWT claim that contains scopes (e.g. `scope` or `permissions`) + - Required Scopes: Add any required scopes for access (optional) + 4. Click Create to save your policy + +3. **Configure JWT Authentication in Tyk OAS API** + + 1. Navigate to APIs and select your API + 2. Click **Edit** + 3. Enable **Authentication** in the **Server** section, select **JSON Web Token (JWT)** as the authentication method + 4. Configure the JWT settings: + - Token Signing Method: Select `RSA Public Key` + - Subject identity claim: Set to `sub` + - JWKS Endpoint: Enter your JWKS URI for your IdP obtained in step 1 + - Policy claim: Set to `pol` + - Default policy: Select `JWT Auth Policy` (the policy you created previously) + - Clock Skew (optional): Set to accommodate time differences (e.g. `10`) + - Authentication Token Location: `header` + - Header Name: `Authorization` + - Strip Authorization Data: `Enabled` + 5. Click **Save API** + +4. **Test your API** + + 1. Obtain a JWT from your IdP + 2. Make a request to your API providing the JWT as a Bearer token in the `Authorization` header; Tyk will validate the JWT using the JWKS that it retrieves from your JWKS URI + 3. Observe that the request is successful + + ```bash + curl -X GET {API URL} -H "Accept: application/json" -H "Authorization: Bearer {token}" + ``` \ No newline at end of file diff --git a/api-management/authentication/jwt-signature-validation.mdx b/api-management/authentication/jwt-signature-validation.mdx new file mode 100644 index 0000000000..4267493d8a --- /dev/null +++ b/api-management/authentication/jwt-signature-validation.mdx @@ -0,0 +1,301 @@ +--- +title: JWT Signature Validation +description: How to validate JWT signatures in Tyk API Gateway. +keywords: ["Authentication", "JWT", "JSON Web Tokens", "Signature", "Validation"] +sidebarTitle: "Signature Validation" +--- + +## Availability + +| Component | Editions | +| ----------- | ------------------------ | +| Tyk Gateway | Community and Enterprise | + +## Introduction + +A JSON Web Token consists of three parts separated by dots: `header.payload.signature`. The signature verifies that the sender of the JWT is who it claims to be and that the message wasn't altered along the way. + +Tyk can validate the signature of incoming JWTs to ensure that they meet your security requirements before granting access to your APIs. + +## JWT Signature Fundamentals + +The JWT signature serves three main purposes: + +1. **Integrity:** + If anyone modifies the header or payload, the signature will no longer match. + +2. **Authenticity:** + Confirms that the token was issued by a trusted source. + +3. **Security:** + Prevents malicious users from forging tokens or altering claims. + +### How JWT Signatures Are Created + +The JWT signature is created by taking the encoded header, the encoded payload, a secret, and the algorithm specified in the header. + +**Example**: + +``` +HMACSHA256( + base64UrlEncode(header) + "." + base64UrlEncode(payload), + secret +) +``` + +Or, for asymmetric algorithms like RSA or ECDSA: + +``` +RSASHA256( + base64UrlEncode(header) + "." + base64UrlEncode(payload), + private_key +) +``` + +### Verification Process + +When Tyk receives a JWT, it performs the following steps to validate the signature: + +1. It extracts the header and payload. +2. Recomputes the signature using its own secret/public key. +3. Compares it with the token’s signature. + * If they match, the token is valid. + * If not, the token is rejected. + +## Supported Algorithms for Signature Validation + +| Method | Cryptographic Style | Secret Type | Supported Algorithms | +| --------- | ------------------- | ------------- | ---------------------------------------------------- | +| **HMAC** | Symmetric | Shared secret | `HS256`, `HS384`, `HS512` | +| **RSA** | Asymmetric | Public key | `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512` | +| **ECDSA** | Asymmetric | Public key | `ES256`, `ES384`, `ES512` | + +## Configuration Options + +Tyk supports two approaches to supplying the key or secret used to validate incoming JWTs: + +- **[Identity Provider](#identity-providers) (IdP)**: Tyk fetches public keys from a JWKS endpoint exposed by the IdP. The IdP can be configured centrally in the Identity Provider Registry, or directly in the API definition. +- **[Local Keys](#local-keys)**: The key or secret is supplied directly in the API definition, either as a hard-coded value or as a reference to an external secret store. + +### Identity Providers + +An Identity Provider (IdP) issues JWTs and exposes a JWKS endpoint where Tyk fetches the public keys needed to validate token signatures. The IdP can be configured centrally in the [Identity Provider Registry](/api-management/client-idp-registry), or directly in the API definition. + +#### Feature Compatibility Summary + +| Feature | Tyk Classic APIs | Tyk OAS APIs | Available From | +| -------------------------------------------------- | ---------------- | ------------ | ------------------- | +| Identity Provider Registry (recommended) | ✅ | ✅ | 5.14.0 (Enterprise) | +| Single JWKS endpoint in API definition | ✅ | ✅ | All versions | +| Multiple JWKS endpoints in API definition | ❌ | ✅ | 5.9.0 | + +#### Identity Provider Registry + +The [Identity Provider Registry](/api-management/client-idp-registry) is a centralized store for IdP configuration (JWKS endpoints, scope claim names, and [scope-to-policy mappings](/api-management/authentication/jwt-authorization#scope-policies)) managed independently of API definitions in the Tyk Dashboard. Available from **Tyk 5.14.0** (Enterprise), it is the recommended approach when using Dynamic Client Registration or when multiple systems need to manage IdP configuration for the same API. + +Adopting the registry is a non-breaking change: existing API definitions are unaffected. + +#### Configuring IdPs in the API Definition + +If you are not using the Identity Provider Registry, JWKS endpoints can be configured directly in the API definition. + +**Multiple JWKS Endpoints** + +From **Tyk 5.9.0** onwards, Tyk OAS APIs can validate against multiple JWKS endpoints, allowing different IdPs to issue JWTs for the same API. + +Multiple JWKS endpoints can be configured in the `.jwksURIs` array. Tyk retrieves the JSON Web Key Sets from each endpoint and uses them to attempt to validate the received JWT. + +For example, the following fragment configures the JWT authentication middleware to retrieve the JWKS from both Auth0 and Keycloak when validating the signature of incoming JWTs: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + jwksURIs: + - url: https://your-tenant.auth0.com/.well-known/jwks.json + - url: http://your-keycloak-host/realms/tyk-demo/protocol/openid-connect/certs +``` + + +Multiple JWKS endpoints and the `jwksURIs` array are not supported by Tyk Classic APIs. + + +**Single JWKS Endpoint** + +If using Tyk Classic APIs, or Tyk OAS APIs on versions before 5.9.0, a single JWKS endpoint can be configured in `server.authentication.securitySchemes..source` (in Tyk Classic, this is [jwt_source](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis)). This field accepts the base64-encoded full URI (including the protocol) of the JWKS endpoint. + +For example, the following Tyk OAS fragment configures the JWT authentication middleware to retrieve the JWKS from `https://your-tenant.auth0.com/.well-known/jwks.json`: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + source: aHR0cHM6Ly95b3VyLXRlbmFudC5hdXRoMC5jb20vLndlbGwta25vd24vandrcy5qc29u +``` + + +The `.source` URIs must be base64 encoded in the API definition. + +If both `.source` and `.jwksURIs` are configured, the latter will take precedence. + + +### Local Keys + +The key or secret used to validate JWTs can be supplied directly in the API definition via `server.authentication.securitySchemes..source` (in Tyk Classic, this is [jwt_source](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis)). The value must be base64 encoded. + +This approach supports both symmetric (HMAC shared secret) and asymmetric (RSA/ECDSA public key) algorithms. + +Rather than hard-coding the value, the recommended approach is to store the key or secret in an [external secret store](/tyk-configuration-reference/kv-store) such as Consul or Vault, and place a KV reference in `source` instead. The reference is base64 encoded in the same way as a hard-coded value. + +For example, the following fragment hard-codes the secret `mysecret`: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + source: bXlzZWNyZXQ= # base64("mysecret") +``` + +The following fragment uses a Consul KV reference instead, keeping the secret out of the API definition: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + source: Y29uc3VsOi8vc2VjcmV0cy9qd3Qtc2VjcmV0 # base64("consul://secrets/jwt-secret") +``` + +Refer to the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#jwt) reference for details. + +## JWKS Caching + +Tyk caches public keys fetched from Identity Providers to reduce the performance impact of contacting external services during request handling. + +### Feature Compatibility Summary + +| Feature | Tyk Classic | Tyk OAS | Available From | +| ------------------------------ | ----------- | ------- | -------------- | +| Configurable cache timeout | ✅ | ✅ | Tyk 5.12.0+ | +| Per-IdP cache timeout | ❌ | ✅ | Tyk 5.10.0+ | +| Pre-fetch on API load | ❌ | ✅ | Tyk 5.10.0+ | +| Cache management API | ✅ | ✅ | Tyk 5.10.0+ | + +### Configuration Options + +#### Gateway-Level Configuration + +Unless otherwise configured, all cached keys expire after 240 seconds, after which the cache is refreshed when a new request is received. + +From **Tyk 5.12.0**, this default timeout is configurable in the Gateway config file (`tyk.conf`) or the equivalent environment variable: + +```json +{ + "jwks": { + "cache": { + "timeout": 90 + } + } +} +``` + +The [`timeout`](/tyk-oss-gateway/configuration#jwks-cache-timeout) is given in seconds. This setting applies to all IdPs, including those configured via the [Identity Provider Registry](/api-management/client-idp-registry). + +#### API-Level Configuration + +From **Tyk 5.10**, Tyk OAS APIs support a per-IdP cache timeout on each IdP configured via `jwksURIs`. If set, this overrides the gateway-level timeout for that IdP. + +For example, the following fragment assigns a 300 second cache timeout to Auth0 and 180 seconds to Keycloak: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + jwtAuth: + jwksURIs: + - url: https://your-tenant.auth0.com/.well-known/jwks.json + cacheTimeout: "300s" + - url: http://your-keycloak-host/realms/tyk-demo/protocol/openid-connect/certs + cacheTimeout: "3m" +``` + +| Field | Type | Description | Default | Supported Formats | +| -------------- | ------ | --------------------- | -------- | ------------------------------ | +| `url` | string | JWKS endpoint URL | Required | Full URI including protocol | +| `cacheTimeout` | string | Cache validity period | 240s | `"300s"`, `"5m"`, `"1h"`, etc. | + +For more details, refer to the [Tyk OAS API definition reference](/api-management/gateway-config-tyk-oas#jwk). + + +Tyk Classic APIs do not support per-IdP cache timeouts and use the gateway-level timeout (from Tyk 5.12.0) or the default of 240 seconds. + + + +Per-IdP `cacheTimeout` applies only to IdPs [configured directly in the API definition](#configuring-idps-in-the-api-definition) via `jwksURIs`. IdPs configured in the [Identity Provider Registry](/api-management/client-idp-registry) always use the gateway-level `jwks.cache.timeout` and cannot currently be tuned per IdP. Per-IdP cache timeout configuration is planned for a future release. + + +### Cache Management + +Tyk Gateway and Tyk Dashboard APIs expose endpoints to manage JWKS caches programmatically for both Tyk OAS and Tyk Classic APIs: + +| Endpoint | Method | Description | Availability | +| ------------------------- | -------- | ---------------------------------------- | ------------ | +| `/tyk/cache/jwks` | `DELETE` | Invalidate JWKS caches for all APIs | Tyk 5.10.0+ | +| `/tyk/cache/jwks/{apiID}` | `DELETE` | Invalidate JWKS cache for a specific API | Tyk 5.10.0+ | +| `/api/cache/jwks/{apiID}` | `DELETE` | Invalidate JWKS cache for a specific API on all connected Gateways | Tyk 5.11.0+ | + + +The Dashboard API endpoint is restricted to users with `admin` privileges and can only be used to flush the cache for APIs in the user's [Organisation](/tyk-dashboard-api#organisations-apis-and-users). + + +**Example usage:** +```bash +# Flush all JWKS caches +curl -X DELETE http://your-gateway:8080/tyk/cache/jwks \ + -H "x-tyk-authorization: your-gateway-secret" + +# Flush JWKS cache for specific API +curl -X DELETE http://your-gateway:8080/tyk/cache/jwks/your-api-id \ + -H "x-tyk-authorization: your-gateway-secret" + +# Flush JWKS cache for specific API on all connected Gateways +curl -X DELETE http://your-dashboard:8080/api/cache/jwks/your-api-id \ + -H "authorization: your-dashboard-secret" +``` + +## FAQ + + + +Yes, each API definition can have its own JWT configuration with different signing methods and keys. + + + +The recommended approach is to use the [Identity Provider Registry](/api-management/client-idp-registry) (Enterprise, from Tyk 5.14.0), which natively supports multiple IdPs per API. If you are not using the registry, Tyk OAS APIs from Tyk 5.9.0 support multiple JWKS endpoints via the `jwksURIs` array. + + + +Tyk caches public keys fetched from IdPs, so a temporary outage does not immediately affect API availability. The cache timeout defaults to 240 seconds and is configurable via the gateway-level `jwks.cache.timeout` setting. For IdPs configured directly in a Tyk OAS API definition via `jwksURIs`, a per-IdP `cacheTimeout` can also be set on each entry. + + + +No, JWKS endpoints are designed for asymmetric cryptography (RSA and ECDSA), where public keys are used for signature verification. Symmetric cryptography (HMAC) requires a shared secret, which cannot be retrieved from a JWKS endpoint. + + + +By default, cached keys expire after 240 seconds. This can be changed gateway-wide using the `jwks.cache.timeout` setting in the Gateway config. For Tyk OAS APIs with IdPs configured directly in the API definition via `jwksURIs`, a per-IdP `cacheTimeout` can override the gateway-level setting. IdPs configured via the Identity Provider Registry always use the gateway-level timeout and cannot currently be tuned per IdP. + + + +The JWKS (JSON Web Key Set) pre-fetching functionality in Tyk Gateway is automatic and not configurable in terms of enabling/disabling it. When you configure JWKS URLs in your API definition, Tyk automatically pre-fetches the keys when the API loads. + + + diff --git a/api-management/authentication/jwt-split-token.mdx b/api-management/authentication/jwt-split-token.mdx new file mode 100644 index 0000000000..0208196316 --- /dev/null +++ b/api-management/authentication/jwt-split-token.mdx @@ -0,0 +1,210 @@ +--- +title: JWT Split Token +description: Learn how to implement JWT Split Token flow in Tyk to enhance security by separating JWT components and storing sensitive data server-side. +keywords: ["Authentication", "JWT", "JSON Web Tokens", "Split Token", "Security"] +sidebarTitle: "Split Token" +--- + +## Availability + +| Component | Editions | +| :------------- | :------------------------- | +| Tyk Gateway | Community and Enterprise | + +## Introduction + +Split Token Flow addresses a fundamental security concern with JWT tokens: when a JWT is stored on a client device (browser, mobile app, etc.), all of its contents can be easily decoded since JWTs are only base64-encoded, not encrypted. This means sensitive information in the payload is potentially exposed. + +The JWT consists of three parts: + +Split Token Example + +In the above example you can see that they are: + +- Header: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9` +- Payload: `eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbWFpbCI6ImhlbGxvQHdvcmxkLmNvbSJ9` +- Signature: `EwIaRgq4go4R2M2z7AADywZ2ToxG4gDMoG4SQ1X3GJ0` + +The Split Token approach provides a solution by: + +1. Separating the JWT into its three component parts: header, payload, and signature +2. Storing only the signature on the client side (which by itself is meaningless) +3. Keeping the header and payload securely on the server side (in Tyk) +4. Reconstructing the complete JWT when needed for authentication + +This approach combines the benefits of JWTs (rich claims, stateless validation) with the security of opaque tokens (no information disclosure). + +### When to Use Split Token Flow + +Consider using Split Token Flow when: + +- Your JWT payload contains sensitive information that shouldn't be exposed to clients +- You want to prevent token inspection by malicious actors +- You need the flexibility of JWT while maintaining higher security +- You're implementing systems that must meet strict security compliance requirements + +## How Split Token Flow Works + +Here's how the process works with Tyk Gateway: + +```mermaid +sequenceDiagram + participant Client + participant Tyk as Tyk Gateway + participant Redis as Tyk Redis + participant Auth as Authorization Server + + %% Token Issuance Flow + rect rgb(240, 240, 255) + note over Client, Auth: Token Issuance + Client->>Tyk: Request token from /token endpoint + Tyk->>Auth: Forward request to Auth Server + Auth->>Tyk: Return complete JWT (header.payload.signature) + Tyk->>Tyk: Split JWT into components + Tyk->>Redis: Store header & payload using signature as key + Tyk->>Client: Return only signature as "opaque" token + end + + %% Token Usage Flow + rect rgb(245, 255, 245) + note over Client, Auth: Token Usage + Client->>Tyk: API request with signature as Bearer token + Tyk->>Redis: Look up header & payload using signature + Redis->>Tyk: Return stored header & payload + Tyk->>Tyk: Reconstruct complete JWT + Tyk->>Tyk: Validate JWT + Tyk->>Auth: Forward request with complete JWT + Auth->>Tyk: Response + Tyk->>Client: Return response to client + end +``` + +1. **Token Issuance**: + + - A `/token` endpoint is configured on Tyk from which the client should request the access token + - Tyk requests an access token from an authorization server (e.g., Keycloak) on behalf of the client + - The authorization server returns a complete JWT + - Tyk intercepts this response through a [Virtual Endpoint](/api-management/traffic-transformation/virtual-endpoints) + - Tyk splits the JWT into its components and stores the header and payload in its Redis database + - Only the signature portion is returned to the client as an "opaque" token + +2. **Token Usage**: + + - The client makes API requests using only the signature as their access token + - Tyk receives the request and looks up the stored header and payload using the signature + - Tyk reconstructs the complete JWT and validates it + - If valid, Tyk forwards the request to the upstream API with the full JWT + +3. **Security Benefits**: + + - The client never possesses the complete JWT, only a meaningless signature + - Token contents cannot be inspected by client-side code or malicious actors + - Token validation still occurs using standard JWT verification + + +## Implementing Split Token Flow + +1. **Create a Virtual Endpoint for Token Issuance** + + First, create a virtual endpoint in Tyk that will: + + - Receive authentication requests from clients + - Forward these requests to your authorization server + - Split the returned JWT + - Store the header and payload in Tyk's storage + - Return only the signature to the client + - Here's a simplified implementation: + + ```javascript + function splitTokenHandler(request, session, config) { + // 1. Forward the client's credentials to the authorization server + var authServerResponse = forwardToAuthServer(request); + + if (authServerResponse.Code !== 200) { + return TykJsResponse({ + Body: authServerResponse.Body, + Code: authServerResponse.Code + }, session.meta_data); + } + + // 2. Extract the JWT from the response + var responseBody = JSON.parse(authServerResponse.Body); + var fullJWT = responseBody.access_token; + + // 3. Split the JWT into its components + var jwtParts = fullJWT.split("."); + var header = jwtParts[0]; + var payload = jwtParts[1]; + var signature = jwtParts[2]; + + // 4. Store the complete JWT in Tyk's Redis database using the signature as the key + // This function would use Tyk's storage API to save the data + storeJWTComponents(signature, header, payload, fullJWT); + + // 5. Modify the response to return only the signature + responseBody.access_token = signature; + + return TykJsResponse({ + Body: JSON.stringify(responseBody), + Code: 200 + }, session.meta_data); + } + ``` + + Note that this example includes some level of abstraction for clarity and so is not a full implementation. + +2. **Configure Custom Pre-Auth Plugin** + + Next, create a custom pre-auth plugin that reconstructs the JWT before it reaches the standard Tyk JWT Auth middleware: + + ```javascript + function reconstructJWT(request, session, config) { + // 1. Extract the signature from the Authorization header + var authHeader = request.Headers["Authorization"]; + var signature = authHeader.replace("Bearer ", ""); + + // 2. Retrieve the stored JWT components using the signature + var storedJWT = retrieveJWTComponents(signature); + + if (!storedJWT) { + return TykJsResponse({ + Body: "Invalid token", + Code: 401 + }, session.meta_data); + } + + // 3. Replace the Authorization header with the full JWT + request.SetHeaders["Authorization"] = "Bearer " + storedJWT.fullJWT; + + return request; + } + ``` + +3. **Test the Implementation** + + To test your Split Token Flow: + + Request a token from your Tyk virtual endpoint: + + ```bash + curl -X POST https://your-tyk-gateway/token \ + -d "grant_type=client_credentials&client_id=your-client-id&client_secret=your-client-secret" + ``` + + You'll receive a response with only the signature as the access token, for example: + + ```json + { + "access_token": "EwIaRgq4go4R2M2z7AADywZ2ToxG4gDMoG4SQ1X3GJ0", + "token_type": "bearer", + "expires_in": 3600 + } + ``` + + Use this token to access your JWT Auth protected API where you have configured the custom pre-auth plugin and JWT Auth: + + ```bash + curl https://your-tyk-gateway/protected-api \ + -H "Authorization: Bearer EwIaRgq4go4R2M2z7AADywZ2ToxG4gDMoG4SQ1X3GJ0" + ``` + diff --git a/api-management/authentication/oauth-2.mdx b/api-management/authentication/oauth-2.mdx new file mode 100644 index 0000000000..a95131ebad --- /dev/null +++ b/api-management/authentication/oauth-2.mdx @@ -0,0 +1,764 @@ +--- +title: "Tyk OAuth 2.0 Authorization Server" +description: "Learn how to use Tyk Gateway as a built-in OAuth 2.0 authorization server to issue and manage access tokens for APIs deployed on Tyk." +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Secure APIs, OAuth 2.0" +sidebarTitle: "Tyk OAuth 2.0" +--- + +## OAuth 2.0 without an external Authorization Server + +Tyk can act as an OAuth 2.0 *authorization server*, performing token generation and management for *clients* accessing APIs deployed on Tyk. There are many great resources on the Internet that will help you to understand the OAuth 2.0 Authorization Framework, which we won't attempt to duplicate here. We will provide a basic introduction to the [concepts and terminology](#oauth-20-core-concepts) before we dive into the details of using Tyk as your *auth server*. + +Tyk offers some great features when used as the *authorization server* including: + +- **Fine-Grained Access Control:** Manage access using Tyk's built-in access controls, including versioning and named API IDs +- **Usage Analytics:** Leverage Tyk's analytics capabilities to monitor OAuth 2.0 usage effectively, grouping data by Client Id +- **Multi-API Access**: Enable access to multiple APIs using a single OAuth token; configure one API for OAuth 2.0 token issuance and the other APIs with the [Auth Token](/api-management/authentication/bearer-token) method, linking them through a common policy + +*Tyk as OAuth authorization server* supports the following *grant types*: + +- [Authorization Code Grant](#using-the-authorization-code-grant): the *client* is redirected to an *identity server* where the *user* must approve access before an *access token* will be issued +- [Client Credentials Grant](#using-the-client-credentials-grant): used for machine-to-machine access, authentication is performed using only the *client Id* and *client secret* +- [Resource Owner Password Grant](#using-the-resource-owner-password-grant) (a.k.a. Password Grant): only for use where the *client* is highly trusted, as the *client* must provide the *Resource Owner*'s own credentials during authentication + + + + + **Tyk does not recommend the use of Resource Owner Password Grant**. This method is considered unsafe and is prohibited in the [OAuth 2.0 Security Best Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13#section-3.4") but is supported for use with legacy clients. + + + +To make use of this, you'll need to: + +- understand how to integrate your *client* (and, for Authorization Code grant, your *identity server*) according to the OAuth grant type +- [register a client app](#client-app-registration) for each client that needs to access the API +- [configure your API proxy](#configuring-your-api-proxy) to use the *Tyk OAuth 2.0* authentication method + +{/* TODO: This video probably needs to be re-recorded with Tyk OAS, so not publishing for now: */} + + + +## OAuth 2.0 Core Concepts + +**OAuth 2.0** (Open Authorization 2.0) is a widely adopted authorization protocol that allows third-party applications to access user resources securely, without needing to expose sensitive credentials such as user passwords. It is an industry-standard framework that enables a delegated approach to securing access to APIs and services. The [IETF OAuth 2.0 specification](https://datatracker.ietf.org/doc/html/rfc6749) outlines the standard for OAuth 2.0. + +> "The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf." — [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) + +OAuth 2.0 provides a mechanism for **client applications** to request limited access to resources hosted by a **resource server**, on behalf of a **resource owner** (typically a user), without exposing the resource owner's credentials. This allows secure sharing of data between applications—for example, allowing a calendar app to access a user's contacts to automatically find available time slots for meetings. + +OAuth 2.0 has many variations and flows suited for different use cases, this section will provide an overview of the core principles, terminology, and key concepts, specifically focusing on how you can implement OAuth 2.0 with Tyk. + +### Terminology + +- **Protected Resource**: The service or data that is protected by OAuth (e.g. an API endpoint) and requires authorization to access. +- **Resource Owner**: The **user** or system that owns the *Protected Resource* and has the ability to grant or deny access to it. +- **Client**: The application or system that seeks access to the *Protected Resource*. It acts on behalf of the *Resource Owner*. +- **Access Token**: A short-lived piece of data that grants the *Client* access to the *Protected Resource*. The token proves that the *Client* has been authorized by the *Resource Owner*. +- **Authorization Server**: The server that issues *Access Tokens* to the *Client* after validating the *Client*'s identity and obtaining consent from the *Resource Owner*. +- **Client Application**: The application that requests authorization from the *Authorization Server*. This application must first be registered with the *Authorization Server* to obtain credentials (*Client Id* and *Client Secret*). +- **Resource Server**: The server that hosts the *Protected Resource*. It receives access requests from *Clients*, which must include a valid *Access Token*. +- **Identity Server**: A server that authenticates the *Resource Owner*, offering the facility to log in and authorize *Client* access to *Protected Resources*. +- **Scope**: Defines the specific permissions or access levels being requested by the *Client* (e.g. read, write, delete). +- **Grant Type**: The method by which the *Client* obtains an *Access Token*, based on the OAuth flow being used (e.g. Authorization Code, Client Credentials, Resource Owner Password Credentials). + +### Access Tokens + +In OAuth 2.0, **access tokens** are used to represent the authorization granted to the *client* by the *resource owner*. These tokens are typically small, opaque data objects that are passed along with each API request to authenticate the *client*. While the OAuth 2.0 specification does not mandate a specific format, **JSON Web Tokens (JWTs)** are commonly used as they can encode metadata, such as the *user*'s identity, permissions, and token expiry time. + +Tokens usually come with an expiration date to limit the time they are valid and minimize the risk of abuse. *Access tokens* can often be refreshed via a **refresh token** if they expire, allowing for long-lived access without requiring the *user* (*resource owner*) to reauthorize the *application* (*client*). + +### Client Application + +For a *client* to request an *Access Token* from the *Authorization Server*, it must first authenticate itself. This ensures that the *Resource Owner* can confidently delegate access to the requested resources. + +To do this, the *client* is registered with the *Authorization Server* as a **Client Application**, which requires the following elements: + +- **Client Id**: A unique, public identifier for the *client application* (e.g., a username or application name). +- **Client Secret**: A confidential string (like a password) that is shared between the *client* and the *Authorization Server*. The *client secret* is never exposed to the *Resource Owner*. +- **Redirect URI**: The URL to which the *client* will be redirected after the authorization process is complete (either granted or denied). + +The *client* sends the *client Id* and *client secret* during the authorization request to prove its identity and authenticate its request for an *access token*. Depending on the OAuth *grant type* being used (e.g. Authorization Code Flow, Client Credentials Flow), the *Authorization Server* will authenticate the *client* and, if successful, issue an *Access Token*. + + +## Manage Client Access Policies + +The *access tokens* issued to clients by *Tyk Authorization Server* are lookup keys to [Sessions](/api-management/access-control/sessions-and-keys/understanding-sessions) and are usually associated with [Policies](/api-management/policies) at the point of creation. These allow the application of quotas, rate limits and access rights in the normal manner. + +Policies can be assigned to *client apps* and will be applied to all access tokens issued for that *client app*. + + +## Client App Registration + +For all grant types, the first common step is the registration of the *client* with Tyk Dashboard by creation of a *Client App*. This will allocate a *client Id* and *client secret* that must be provided in future authentication requests by the *client*. + +### Using the Tyk Dashboard UI + +1. *Client apps* are registered per-API, so the first step is to [configure Tyk OAuth 2.0](#configuring-your-api-proxy) as the security method to be used for the API. With this done, you can navigate to the OAuth Client management screen for the API from the **Actions** menu on the **Created APIs** screen: + +Accessing the list of OAuth Clients for an API + +2. You will now be prompted to register a *client app* that will be granted access to the API configuring: + +- redirect URI +- [optional] [security policies](#manage-client-access-policies) to be applied to access tokens generated for the client +- [optional] [metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) to be added to the access tokens + +Add New OAuth Client + +**Note**: when using *Authorization Code grant* the *redirect uri* configured for the *client app* must be the same as that configured in the API definition. + +Select the **Create** button to register the *client app*. + +3. In the OAuth Client management screen, you will see a list of *client apps* registered with the API (as identified by their *client Id*). By clicking on the list item, or from the **Actions** menu's **Edit** option you will be taken to the *Edit Client app* screen, where you can see the *client secret* and make any modifications you need. There is also the option to [revoke tokens](#revoking-access-tokens) that have been issued for this *client app*. + +View client Id and client secret + +### Using the Tyk Dashboard API + +The Tyk Dashboard API contains several endpoints that are provided to manage *client apps*. *Client apps* are registered per-API, so each takes as an input the *API Id* for the API: + +| Action | Endpoint | Reference | +| :--- | :--- | :--- | +| Register a new client app | `POST /api/apis/oauth/{{api-id}}` | [link](https://tyk.io/docs/api-reference/oauth/create-a-new-oauth20-client) | +| Get a list of registered client apps | `GET /api/apis/oauth/{{api-id}}` | [link](https://tyk.io/docs/api-reference/oauth/list-oauth-clients) | +| Get the details of a client app | `GET /api/apis/oauth/{{api-id}}/{{client_id}}` | [link](https://tyk.io/docs/api-reference/oauth/get-single-oauth-client-details) | +| Delete a client app | `DELETE /api/apis/oauth/{{api-id}}/{{client_id}}` | [link](https://tyk.io/docs/api-reference/oauth/delete-oauth-client) | + + +## Using the Authorization Code Grant + +When using Tyk as the Authorization Server with the Authorization Code grant, the following steps are followed after [registering the Client App](#client-app-registration): + +Authorization grant type flow + +**Explanatory notes:** + +(1) *client* makes a request to the [authorization endpoint](#authorization-request) on the *Auth Server* + +(2) The *Auth Server* notes the request parameters and returns `HTTP 307 Temporary Redirect`, redirecting the user to an *Identity Server* + +(5) the *user* must log in on the *Identity Server* and authorize the *client* + +(6) when the *user* successfully authenticates and authorizes the request, the *Identity Server* must request an [Authorization Code](#authorization-code-request) from the *Auth Server* + +(8) The *Identity Server* provides the *Authorization Code* to the *client* + +(9) The *client* exchanges the *Authorization Code* for an [Access Token](#exchange-the-authorization-code-for-an-access-token) from the *Auth Server* + +(10) The *client* uses the *Access Token* to authenticate with the protected API using the [Auth Token](/api-management/authentication/bearer-token) method + +### Integration with Identity Server + +Whilst Tyk can provide the *authorization server* functionality, issuing and managing access and authorization tokens, the *identity server* functions (authenticating users (resource owners) and allowing them to authorize client access) must be performed by a separate Identity Provider (IdP). + +The identity server will need access to the Tyk Dashboard API to [obtain an Authorization Code](/api-management/authentication/oauth-2#oauth2-0-authorization-code). + +[Tyk Identity Broker (TIB)](/tyk-identity-broker/overview) can fulfill this role, authenticating users against an external IdP and handling the authorization code exchange with Tyk Dashboard on their behalf. See [Issue API Access Tokens via TIB](/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib) for details. + +### Authorization Request + +The authorization endpoint for an API proxy on Tyk is a special endpoint automatically added to the proxy definition, accessible from `POST //oauth/authorize` + +The following parameters are required in a request to this endpoint: + +| Parameter | Value | +| :--------------- | :-------------------------- | +| `response_type` | `code` | +| `client_id` | client Id | +| `redirect_uri` | Redirect URI (URL encoded) | + +For example: + +```bash +curl -X POST https://tyk.cloud.tyk.io/my-api/oauth/authorize/ \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "response_type=code&client_id=my-client-id&redirect_uri=http%3A%2F%2Fidentityserver.com%2Fclient-redirect-uri" +``` + +This command, issued by the *client* is the first step of requesting access to the `/my-api` proxy deployed on a Tyk Gateway at `https://tyk.cloud.tyk.io`. + +If the *client Id* (`my-client-id`) is valid, the response will be `HTTP 307 Temporary Redirect` with the redirect URI (`http://identityserver.com/client-redirect-uri`) in the `location` header. + +### Authorization Code Request + +The *Identity Server* requests an *Authorization Code* from the *Authentication Server*. Tyk's *authorization code* endpoint is hosted in the [Tyk Dashboard API](/api-management/authentication/oauth-2#oauth2-0-authorization-code), accessible from `POST /api/apis/{api_id}/authorize-client`. The same `redirect_uri` as provided in the original request must be provided alongside the `client_id` as a security feature to verify the client identity. + +This endpoint is protected using the Dashboard API secret assigned to the *Identity Server*, which must be provided in the `Authorization` header. + +The following parameters are required in a `POST` request to this endpoint: + +| Parameter | Value | +| :--------------- | :-------------------------- | +| `response_type` | `code` | +| `client_id` | client Id | +| `redirect_uri` | Redirect URI (URL encoded) | + +For example: + +```bash +curl -X POST \ + https://admin.cloud.tyk.io/api/apis/oauth/{my-api-id}/authorize-client/ \ + -H "Authorization: " \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "response_type=code&client_id=my-client-id&redirect_uri=http%3A%2F%2Fidentityserver.com%2Fclient-redirect-uri" +``` + +This command, issued by the *identity server* requests an *authorization code* from the Tyk Dashboard at `https://admin.cloud.tyk.io` to access the proxy with API Id `my-api-id`. + +If the *client Id* (`my-client-id`) is valid and `redirect_uri` matches the one provided in the initial request, an *authorization code* will be provided in the response payload, for example: + +```json +{ + "code": "EaG1MK7LS8GbbwCAUwDo6Q", + "redirect_to": "http://example.com/client-redirect-uri?code=EaG1MK7LS8GbbwCAUwDo6Q" +} +``` + +### Exchange the Authorization Code for an Access Token + +Once the *client* has the *authorization code*, it can exchange this for an *access token*, which is used to access the protected API. The token exchange endpoint for an API proxy on Tyk is a special endpoint automatically added to the proxy definition, accessible from `POST //oauth/token`. + +This endpoint is protected using [Basic Authentication](/api-management/authentication/basic-authentication) where the username is the *client Id* and the password is the *client secret*. + +The following parameters are required in the request: + +| Parameter | Value | +| :--------------- | :-------------------------- | +| `grant_type` | `authorization_code` | +| `client_id` | client Id | +| `code` | Authorization Code | +| `redirect_uri` | Redirect URI (URL encoded) | + +For example: + +```bash +curl -X POST \ + https://tyk.cloud.tyk.io/my-api/oauth/token/ \ + -H "Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=authorization_code&client_id=my-client-id&code=EaG1MK7LS8GbbwCAUwDo6Q&redirect_uri=http%3A%2F%2Fidentityserver.com%2Fclient-redirect-uri" +``` + +This command, issued by the *client* is the final step to obtain an access token for the `/my-api` proxy deployed on a Tyk Gateway at `https://tyk.cloud.tyk.io`. The basic auth key is the base64 encoded representation of `my-client-id:my-client-secret` The `client_id` and `redirect_uri` match those provided in the initial [authorization request](#authorization-request). The `code` is the *authorization code* provided to the *identity server* in the [authorization code request](#authorization-code-request). + +The response payload contains: +- `access_token`: the token which can be used by the *client* to access the protected API +- `expires_in`: the expiration date/time of the access token +- `token_type`: set to `bearer` indicating that the access token should be provided in an [Auth Token](/api-management/authentication/bearer-token) request to the protected API +- `refresh_token`: [optional] a special token that can be used in the [Refresh Token](#using-refresh-tokens) flow + +For example: + +```json +{ + "access_token": "580defdbe1d21e0001c67e5c2a0a6c98ba8b4a059dc5825388501573", + "expires_in": 3600, + "refresh_token": "NWQzNGVhMTItMDE4Ny00MDFkLTljOWItNGE4NzI1ZGI1NGU2", + "token_type": "bearer" +} +``` + + + +## Using the Client Credentials Grant +When using Tyk as the *authorization server* with the Client Credentials grant, the *client* accesses resources on behalf of itself rather than on behalf of a *user*, so there is no user login/authorization step (as seen with [Authorization Code grant](#using-the-authorization-code-grant)). This flow is ideal for server-to-server interactions. + +After [registering the Client App](#client-app-registration), the *client* simply requests an access token directly from the authorization server: + +Client Credentials grant type flow + +### Access Token Request + +The *client* obtains an access token for an API proxy on Tyk from a special endpoint automatically added to the proxy definition, accessible from `POST //oauth/token`. + +This endpoint is protected using Basic Authentication where the username is the client Id and the password is the client secret. + +The following parameters are required in the request: + +| Parameter | Value | +| :--------------- | :-------------------------- | +| `grant_type` | `client_credentials` | +| `client_id` | client Id | +| `secret` | client secret | + +For example: + +```bash +curl -X POST \ + https://tyk.cloud.tyk.io/my-api/oauth/token/ \ + -H "Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials&client_id=my-client-id&client_secret=my-client-secret" +``` + +This command, issued by the *client* will obtain an access token for the `/my-api` proxy deployed on a Tyk Gateway at `https://tyk.cloud.tyk.io`. The basic auth key is the base64 encoded representation of `my-client-id:my-client-secret` The `client_id` and `client_secret` match those allocated by Tyk (the auth server) for the *client app*. + +The response payload contains: +- `access_token`: the token which can be used by the *client* to access the protected API +- `expires_in`: the expiration date/time of the access token +- `token_type`: set to `bearer` indicating that the access token should be provided in an [Auth Token](/api-management/authentication/bearer-token) request to the protected API + +For example: + +```json +{ + "access_token": "580defdbe1d21e0001c67e5c2a0a6c98ba8b4a059dc5825388501573", + "expires_in": 3600, + "token_type": "bearer" +} +``` + + + +Note that Client Credentials grant does not produce a *refresh token*. + + + + + +## Using the Resource Owner Password Grant +When using Tyk as the *authorization server* with the Resource Owner Password grant, the *client* provides the *user's* credentials when requesting an access token. There is no user login/authorization step (as seen with [Authorization Code grant](#using-the-authorization-code-grant)). **This flow is not recommended and is provided only for integration with legacy clients.** + +After [registering the Client App](#client-app-registration), the *client* simply requests an access token directly from the authorization server: + +Username and password grant sequence + +### Access Token Request + +The *client* obtains an access token for an API proxy on Tyk from a special endpoint automatically added to the proxy definition, accessible from `POST //oauth/token`. + +This endpoint is protected using [Basic Authentication](/api-management/authentication/basic-authentication) where the username is the client Id and the password is the client secret. + +The following parameters are required in the request: + +| Parameter | Value | +| :--------------- | :------------------------------------------------------ | +| `grant_type` | `password` | +| `client_id` | client Id | +| `username` | resource owner's username (`resource-owner-username`) | +| `password` | resource owner's password (`resource-owner-password`) | + +For example: + +```bash +curl -X POST \ + https://tyk.cloud.tyk.io/my-api/oauth/token/ \ + -H "Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=password&client_id=my-client-id&username=resource-owner-username&password=resource-owner-password" +``` + +This command, issued by the *client* will obtain an access token for the `/my-api` proxy deployed on a Tyk Gateway at `https://tyk.cloud.tyk.io`. The basic auth key is the base64 encoded representation of `my-client-id:my-client-secret` The `client_id` and `client_secret` match those allocated by Tyk (the auth server) for the *client app*. + +The response payload contains: +- `access_token`: the token which can be used by the *client* to access the protected API +- `expires_in`: the expiration date/time of the access token +- `token_type`: set to `bearer` indicating that the access token should be provided in an [Auth Token](/api-management/authentication/bearer-token) request to the protected API +- `refresh_token`: [optional] a special token that can be used in the [Refresh Token](#using-refresh-tokens) flow + +For example: + +```json +{ + "access_token": "580defdbe1d21e0001c67e5c2a0a6c98ba8b4a059dc5825388501573", + "expires_in": 3600, + "refresh_token": "YjdhOWFmZTAtNmExZi00ZTVlLWIwZTUtOGFhNmIwMWI3MzJj", + "token_type": "bearer" +} +``` + + +## Configuring your API Proxy + +As explained [previously](/api-management/client-authentication#how-does-tyk-implement-authentication-and-authorization), the AuthN/Z methods to be used to secure an API proxy are configured in the API definition. This permits granular application of the most appropriate method to each API deployed on Tyk Gateway. + +When using Tyk as the Authorization Server, the API configuration can be applied using the Tyk Dashboard's API Designer UI, or by direct modification of the API definition. We will provide examples here when using Tyk OAS APIs. If you are using Tyk Classic APIs, the process is very similar, though there are differences in the location and specific labelling of options. + +### Using the Tyk API Designer + +1. Client Authentication is configured on the **Settings** screen within the API Designer, within the **Server** section. Ensure that you are in **Edit** mode, click on the button to **Enable** *Authentication* and then select **Tyk OAuth 2.0** from the drop down options: + +Set Authentication Mode + +2. Select the OAuth Grant Type that you wish to use for the API, if appropriate you can also select the *Refresh Token* grant so that the Auth Server (Tyk) will generate both access and refresh tokens. + +3. Provide the requested configuration options depending on the selected Grant Type. Note that for *Authorization Code Grant*, **Redirect URL** should be the login page for your Identity Server and must be matched by the `redirect_uri` provided in the *client app* (and in the client's authentication request). The [Notifications](#oauth-token-notifications) configuration can be provided for *Authorization Code* and *Password* grants. + +4. Select **Save API** to apply the new settings. + +### Using the API Definition + +The OpenAPI Specification indicates the use of [OAuth 2.0 authentication](https://swagger.io/docs/specification/v3_0/authentication/oauth2/) in the `components.securitySchemes` object using the `type: oauth2`. Tyk supports the [authorizationCode](/api-management/authentication/oauth-2#using-the-authorization-code-grant), [clientCredentials](#using-the-client-credentials-grant) and [password](#using-the-resource-owner-password-grant) flows and implements Relative Endpoint URLs for the `authorizationUrl`, `tokenUrl` and `refreshUrl`. + +```yaml +components: + securitySchemes: + myAuthScheme: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: ... + tokenUrl: ... + scopes: ... + +security: + - myAuthScheme: [] +``` + +With this configuration provided by the OpenAPI description, in the Tyk Vendor Extension we need to enable authentication, to select this security scheme and to indicate where Tyk should look for the OAuth token. Usually the token will be provided in the `Authorization` header, but Tyk is configurable, via the Tyk Vendor Extension, to support custom header keys and credential passing via query parameter or cooke. + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + header: + enabled: true + name: Authorization +``` + +Note that URL query parameter keys and cookie names are case sensitive, whereas header names are case insensitive. + +You can optionally [strip the user credentials](/api-management/client-authentication#managing-authorization-data) from the request prior to proxying to the upstream using the `authentication.stripAuthorizationData` field (Tyk Classic: `strip_auth_data`). + +With the OAuth method selected, you'll need to configure Tyk to handle the specific configuration of OAuth grants that you will support. All of the OAuth specific configuration is performed within the [authentication.securitySchemes.oauth](/api-management/gateway-config-tyk-oas#oauth) object in the Tyk Vendor Extension. + +For example: + +```json {hl_lines=["7-11", "14-24", "35-55"],linenos=true, linenostart=1} +{ + "info": { + "title": "My OAuth API", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "security": [ + { + "oauth": [] + } + ], + "paths": {}, + "components": { + "securitySchemes": { + "oauth": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "/oauth/authorize", + "scopes": {}, + "tokenUrl": "/oauth/token" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "My OAuth API", + "state": { + "active": true, + } + }, + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "oauth": { + "enabled": true, + "allowedAuthorizeTypes": [ + "code" + ], + "authLoginRedirect": "http:///client-redirect-uri", + "header": { + "enabled": true, + "name": "Authorization" + }, + "notifications": { + "onKeyChangeUrl": "http://notifyme.com", + "sharedSecret": "oauth-shared-secret" + }, + "refreshToken": true + } + } + }, + "listenPath": { + "strip": true, + "value": "/my-oauth-api/" + } + }, + "upstream": { + "url": "http://httpbin.org/" + } + } +} +``` + +In this example: + +- Client authentication has been enabled (line 44) +- The OpenAPI description declares the `oauth` security scheme that expects **Authorization Code** flow. Note that the `authorization URL` and `token URL` are declared relative to the API proxy listen path +- Authorization requests (made to `POST /my-oauth-api/oauth/authorize`) will be redirected to `http:///client-redirect-uri` where the *Resource Owner* should be prompted to authorize the request +- [Notifications](#oauth-token-notifications) of token issuance will be sent to `http://notifyme.com` with the `X-Tyk-Shared-Secret` header set to `oauth-shared-secret` + +The *auth server* (Tyk) will issue an *access token* and *refresh token* in exchange for a valid *authorization code*. Once the client has a valid access token, it will be expected in the `Authorization` header of the request. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk and, with correctly configured and integrated *identity server* can be used to try out OAuth Client Authentication using Tyk as the Authorization Server. + +### Using Tyk Classic APIs + +As noted in the Tyk Classic API [documentation](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis), you can select the Tyk as OAuth Server method using the `use_oauth2` option. + +## Managing OAuth Tokens + +### Using Refresh Tokens + +The Refresh Token flow is used to obtain a new *access token* when the current token has expired or is about to expire. This allows clients to maintain a valid *access token* without requiring the user to go through the authentication and authorization process again. + +*Refresh tokens* are single use and, when used, automatically invalidate the access token with which they were issued. This prevents accidental duplication of access tokens granting authorized access to a resource (API). + +A *refresh token* can be issued by the *auth server* alongside the *access token* at the last stage of the OAuth flow for: +- Authentication Code grant +- Resource Owner Password grant + +You configure whether Tyk should issue a refresh token within the [API proxy definition](#configuring-your-api-proxy). + +#### Refreshing an Access Token + +If you have correctly configured your API, then Tyk will provide a *refresh token* with the *access token*. The *client* can subsequently exchange the *refresh token* for a new *access token* without having to re-authenticate, with another call to the `POST //oauth/token` endpoint as follows: + +Refresh Token flow + +This endpoint is protected using Basic Authentication where the username is the *client Id* and the password is the *client secret*. + +The following data is required in the request payload: + +| Parameter | Value | +| :--------------- | :--------------------------------------------------------- | +| `grant_type` | `refresh_token` | +| `client_id` | client Id | +| `client_secret` | client secret | +| `refresh_token` | The refresh token provided with the original access token | + +For example: + +```bash +curl -X POST \ + https://tyk.cloud.tyk.io/my-api/oauth/token/ \ + -H "Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=refresh_token&client_id=my-client-id&client_secret=my-client-secret&refresh_token=YjdhOWFmZTAtNmExZi00ZTVlLWIwZTUtOGFhNmIwMWI3MzJj" +``` + +This command, issued by the *client* will obtain a new access token for the `/my-api` proxy deployed on a Tyk Gateway at `https://tyk.cloud.tyk.io`. The basic auth key is the base64 encoded representation of `my-client-id:my-client-secret` The `client_id` and `client_secret` match those allocated by Tyk (the auth server) for the *client app*. The `refresh_token` is a valid *refresh token* previously issued to the *client*. + +The response payload contains: +- `access_token`: a new *access token* which can be used by the *client* to access the protected API +- `expires_in`: the expiration date/time of the access token +- `token_type`: set to `bearer` indicating that the access token should be provided in an [Auth Token](/api-management/authentication/bearer-token) request to the protected API +- `refresh_token`: a new *refresh token* that can be used later to refresh the new *access token* + +For example: + +```json +{ + "access_token": "580defdbe1d21e0001c67e5c2a0a6c98ba8b4a059dc5825388501573", + "expires_in": 3600, + "refresh_token": "NWQzNGVhMTItMDE4Ny00MDFkLTljOWItNGE4NzI1ZGI1NGU2", + "token_type": "bearer" +} +``` + +### Revoking Access Tokens + +OAuth access tokens have built in expiry, but if you need to [revoke](https://tools.ietf.org/html/rfc7009) a client's access to the API before this time, then you can use the option on the [OAuth Client management screen](#using-the-tyk-dashboard-ui) screen in Tyk Dashboard UI or the Tyk Dashboard API to do so. + +Using the **Tyk Dashboard API** you can revoke specific tokens (both access and refresh) or all tokens issued for a specific *client app* as follows: + +- [retrieve a list of all tokens for a client app](https://tyk.io/docs/api-reference/oauth/list-oauth-client-tokens) +- [revoke a single token](/api-management/authentication/oauth-2#revoke-a-single-oauth-client-token) +- [revoke all tokens for a client app](/api-management/authentication/oauth-2#revoke-all-oauth-client-tokens) + +These endpoints are protected using the Dashboard API secret assigned to the user managing the tokens, which must be provided in the `Authorization` header. + +In this example, we issue a request to the `/revoke` endpoint of the *auth server* via the Tyk Dashboard API to invalidate a specific *access token*: + +```bash +curl -X POST \ + https://admin.cloud.tyk.io/api/apis/oauth/{CLIENT_ID}/revoke/ \ + -H "Authorization: " \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "token=580defdbe1d21e0001c67e5c2a0a6c98ba8b4a059dc5825388501573&token_type_hint=access_token&client_id=my-client-id&client_secret=my-client-secret" +``` + +Note that the `token_type_hint` must be set to `access_token` or `refresh_token` to match the type of `token` to be revoked. + +### Token Expiration and Retention + +Tyk provides several global Gateway configuration options (in `tyk.conf`) that control the expiration and retention of OAuth 2.0 tokens: + +- [`oauth_token_expire`](/tyk-oss-gateway/configuration#oauth_token_expire): Overrides the default expiration time (in seconds) for newly generated access tokens. This value sets the logical `expires` timestamp in the access token's [Session](/api-management/access-control/sessions-and-keys/understanding-sessions). +- [`oauth_refresh_token_expire`](/tyk-oss-gateway/configuration#oauth_refresh_token_expire): Overrides the default expiration time (in seconds) for newly generated refresh tokens. If not set, it defaults to 14 days (1,209,600 seconds). When this TTL expires, the refresh token is physically deleted from Redis. +- [`oauth_token_expired_retain_period`](/tyk-oss-gateway/configuration#oauth_token_expired_retain_period): Controls a background cleanup job that removes expired tokens from an internal tracking list (a Redis sorted set used to track all tokens issued to a specific OAuth Client). It does **not** control the Redis TTL or the retention of the actual Session data. + +#### Interaction with Session Lifecycle Controls + +Because OAuth access tokens are stored as standard Session objects, their physical retention in Redis is governed by Tyk's standard session lifecycle controls, not by the `oauth_token_expired_retain_period`. + +The `oauth_token_expire` setting defines the initial `expires` timestamp for the Session relating to the access token. Once that timestamp is reached, the [session lifecycle controls](/api-management/access-control/sessions-and-keys/session-lifecycle#summary-of-session-lifetime-calculation) determine how long the expired session data is retained in Redis. + +Refresh tokens, however, are not Session objects. They do not use the session lifecycle controls and are physically deleted from Redis exactly when their `oauth_refresh_token_expire` TTL is reached. + +For more details on how to control the physical retention of access token session data, see the [Session Lifecycle](/api-management/access-control/sessions-and-keys/session-lifecycle) documentation. + + +### OAuth Token Notifications + +When operating as an OAuth authorization server, Tyk can generate an event whenever it issues an *access token*. You can configure a dedicated webhook that will be triggered to notify the Resource Owner service of the occurrence of the event. + +OAuth token notifications can only be configured when using **Authorization Code** or **Resource Owner Password Credentials** grants, not when using *Client Credentials* grant because this flow is primarily used for server-to-server communication, where the client acts on its own behalf without user-specific authorization changes. + +You can configure the URL that the webhook will issue a `POST` request and a "shared secret" value that will be provided in a header (`X-Tyk-Shared-Secret`) used to secure the communication to the target application. The OAuth token notification webhook does not support any other authentication method. + +The body of the webhook request will have this content: + +```json +{ + "auth_code": "", + "new_oauth_token": "", + "refresh_token": "", + "old_refresh_token": "", + "notification_type": "" +} +``` + +where +- `auth_code` is the Authorization Code that has been issued +- `new_oauth_token` is the Access Token that has been issued +- `refresh_token` is the Refresh Token that has been issued +- `old_refresh_token` is the Refresh Token that has been consumed when refreshing an access token +- `notification_type` will indicate the cause of the event: + - `new`: a new access token has been issued + - `refresh`: a token has been refreshed and a new refresh token has been issued + +#### Configuring Notifications in the Tyk API Designer + +Client Authentication is configured on the **Settings** screen within the Tyk OAS API Designer, within the **Server** section. Ensuring that you are in **Edit** mode, go to the *Authentication* section where you should have selected **Tyk OAuth 2.0** from the drop down options. + +Here you will see the *Notifications* section where you can configure: + +- Notifications URL +- Notifications Shared Secret + +Remember to select **Save API** to apply these settings to your API. + +#### Configuring Notifications in the Tyk OAS API Definition + +The example given [above](#using-the-api-definition) includes the configuration necessary to issue notifications for token issuance (see lines 48-51 in the example). + +## Managing OAuth Tokens via the Tyk Dashboard API + +In addition to the OAuth client management endpoints in the [Tyk Dashboard API](/tyk-dashboard-api), the following endpoints are available to manage OAuth tokens and obtain authorization codes. + +### Revoke a Single OAuth Client Token + +| **Property** | **Description** | +| :------------ | :---------------------------------------------- | +| Resource URL | `/api/apis/oauth/{oauthClientId}/revoke` | +| Method | POST | +| Type | JSON | +| Body | Client Object | +| Param | None | + + +**Sample Request** + +```http +POST /api/apis/oauth/411f0800957c4a3e81fe181141dbc22a/revoke +Host: localhost +Authorization 64c8e662f6924c4f55e94a873d75e44d +Body: { + "token": "eyJvcmciOiI1ZTIwOTFjNGQ0YWVmY2U2MGMwNGZiOTIiLCJpZCI6IjIyODQ1NmFjNmJlMjRiMzI5MTIyOTdlODQ5NTc4NjJhIiwiaCI6Im11cm11cjY0In0=", + "token_type_hint": "access_token" +} +``` +**Sample Response** + +```json +{ + "Status": "OK", + "Message": "token revoked successfully", + "Meta": null +} +``` +### Revoke all OAuth Client Tokens + +| **Property** | **Description** | +| :------------ | :---------------------------------------------- | +| Resource URL | `/api/apis/oauth/{oauthClientId}/revoke_all` | +| Method | POST | +| Type | JSON | +| Body | Client Object | +| Param | None | + +**Sample Request** + +```http +POST /api/apis/oauth/411f0800957c4a3e81fe181141dbc22a/revoke_all +Host: localhost +Authorization: 64c8e662f6924c4f55e94a873d75e44d +Body: { + "client_secret":"MzUyNDliNzItMDhlNy00MzM3LTk1NWUtMWQyODMyMjkwZTc0" +} +``` + +**Sample Response** + +```json +{ + "Status": "OK", + "Message": "tokens revoked successfully", + "Meta": null +} +``` + +### OAuth2.0 Authorization Code + +This endpoint is used in the [Authorization Code Grant](/api-management/authentication/oauth-2#using-the-authorization-code-grant) flow, generating an authorization code that can be used by the client to request an access token. + +| **Property** | **Description** | +| :------------ | :---------------------------------------------- | +| Resource URL | `/api/apis/oauth/{{api_id}}/authorize-client/` | +| Method | POST | +| Type | Form-Encoded | +| Body | Fields (see below) | + +* `api_id`: Unlike the other requests on this page, this must be the `api_id` value and **NOT** the API's `id` value. +* `response_type`: Should be provided by requesting client as part of authorization request, this should be either `code` or `token` depending on the methods you have specified for the API. +* `client_id`: Should be provided by requesting client as part of authorization request. The Client ID that is making the request. +* `redirect_uri`: Should be provided by requesting client as part of authorization request. Must match with the record stored with Tyk. +* `key_rules`: A string representation of a Session Object (form-encoded). *This should be provided by your application in order to apply any quotas or rules to the key.* + +Note that in the following example, the `policy_id` isn't included in the request as these are optional. OAuth2.0 Flow also supports callbacks which can be added to the `key_rules` in the payload in requests that don't include the `policy_id`. + + +**Sample Request** + +```curl +curl -vX POST -H "Authorization: {{API Access Credentials}}" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'response_type=code&client_id={{client_id}}&redirect_uri=http%3A%2F%2Foauth.com%2Fredirect&key_rules=%7B+++++%22allowance%22%3A+999%2C+++++%22rate%22%3A+1000%2C+++++%22per%22%3A+60%2C+++++%22expires%22%3A+0%2C+++++%22quota_max%22%3A+-1%2C+++++%22quota_renews%22%3A+1406121006%2C+++++%22quota_remaining%22%3A+0%2C+++++%22quota_renewal_rate%22%3A+60%2C+++++%22access_rights%22%3A+%7B+++++++++%22528a67c1ac9940964f9a41ae79235fcc%22%3A+%7B+++++++++++++%22api_name%22%3A+%22{{api_name}}%22%2C+++++++++++++%22api_id%22%3A+%{{api_id}}%22%2C+++++++++++++%22versions%22%3A+%5B+++++++++++++++++%22Default%22+++++++++++++%5D+++++++++%7D+++++%7D%2C+++++%22org_id%22%3A+%22{{org_id}}%22+%7D' +http://{{dashboard-hostname}}/api/apis/oauth/{{api_id}}/authorize-client +``` + +**Sample Response** + +``` +{ + "code": "MWY0ZDRkMzktOTYwNi00NDRiLTk2YmQtOWQxOGQ3Mjc5Yzdk", + "redirect_to": "http://localhost:3000/oauth-redirect/?code=MWY0ZDRkMzktOTYwNi00NDRiLTk2YmQtOWQxOGQ3Mjc5Yzdk" +} +``` + diff --git a/api-management/authentication/oauth2-authentication.mdx b/api-management/authentication/oauth2-authentication.mdx new file mode 100644 index 0000000000..85e3dde901 --- /dev/null +++ b/api-management/authentication/oauth2-authentication.mdx @@ -0,0 +1,225 @@ +--- +title: "OAuth 2.0 with an External IdP" +description: "Use the oauth2 security scheme to validate tokens issued by an external OAuth 2.0 provider, enforce per-operation scopes, and publish Protected Resource Metadata." +keywords: "OAuth 2.0, external IdP, scope enforcement, Protected Resource Metadata, token exchange, MCP, oauth2 scheme" +sidebarTitle: "OAuth 2.0 (External IdP)" +--- + +## Introduction + +The `oauth2` security scheme lets Tyk Gateway validate bearer tokens issued by an external OAuth 2.0 or OIDC provider. The scheme integrates scope enforcement, Protected Resource Metadata (PRM) publishing, and RFC 8693 token exchange into a single API-level declaration driven by your OpenAPI description. + +## How it fits with other OAuth options + +Tyk offers three distinct OAuth-related client authentication mechanisms: + +- **Tyk OAuth 2.0**: Tyk Gateway acts as the authorization server, issuing and managing tokens itself. See [Tyk OAuth 2.0](/api-management/authentication/oauth-2). +- **`oauth2` security scheme** (this page): Tyk enforces scopes and can publish a PRM document for tokens issued by an external IdP. +- **External OAuth (deprecated)**: A previous mechanism for external IdP integration, deprecated in Tyk 5.7.0. If you are using the `externalOAuthServer` block, the `oauth2` scheme is its replacement. Migration requires manual reconfiguration; there is no automatic migration for `externalOAuthServer` configs. + +## Configure the oauth2 scheme + +The `oauth2` scheme is declared in two places: the standard OAS `components.securitySchemes` section, and the Tyk Vendor Extension (`x-tyk-api-gateway`). + +### OAS security scheme declaration + +In `components.securitySchemes`, declare the scheme with `type: oauth2` and define the OAuth 2.0 flows your IdP supports. The scopes declared in `flows` serve as the documented catalog for your API and are used to populate the PRM document's `scopes_supported` field. + +```yaml +components: + securitySchemes: + idpAuth: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://idp.example.com/realms/demo/protocol/openid-connect/auth + tokenUrl: https://idp.example.com/realms/demo/protocol/openid-connect/token + scopes: + api:read: Read access to API resources + api:write: Write access to API resources +``` + +The scheme name (`idpAuth` in this example) is used throughout the document to reference this scheme in `security:` arrays and in the Tyk Vendor Extension. + +### Tyk Vendor Extension + +Enable the scheme in the `x-tyk-api-gateway` extension using the matching scheme name: + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + idpAuth: + enabled: true + header: + enabled: true + name: Authorization +``` + +With `enabled: true` and the `header` block configured, Tyk reads the bearer token from the specified header. To add scope enforcement, PRM, or token exchange, extend the block as described in the sections below. + +To read the token from a different header, cookie, or query parameter, add the matching `header`, `cookie`, or `query` block. For example, to read from a custom header: + +```yaml +securitySchemes: + idpAuth: + enabled: true + header: + enabled: true + name: X-Auth-Token +``` + +### Apply the scheme to operations + +Reference the scheme in the OAS `security:` array to require it for all operations on the API, or on individual operations: + +```yaml +# Root-level: applies to all operations +security: + - idpAuth: [] + +# Per-operation: require specific scopes on this operation +paths: + /reports: + get: + security: + - idpAuth: [api:read] +``` + +When scopes are listed alongside the scheme name (for example `[api:read]`), they become the required scopes for that operation when scope enforcement is enabled. + +### Apply the scheme to MCP primitives + +MCP primitives (tools, resources, and prompts) have no OAS path entries of their own, so their security requirements are declared in the Tyk Vendor Extension rather than under `paths`. The structure mirrors the OAS `security:` array exactly: + +```yaml +x-tyk-api-gateway: + middleware: + mcpTools: + get-report: + security: + - idpAuth: [api:read] + mcpResources: + customer-data: + security: + - idpAuth: [data:read] + mcpPrompts: + summarise: + security: + - idpAuth: [] +``` + +The scope enforcement engine reads these the same way as per-operation `security:` declarations, subject to the same `scopeSource` rules. The root-level `security:` array still applies as a fallback when a primitive carries no `security` entry of its own. + +--- + +## Scope enforcement + +Scope enforcement checks that the inbound token carries the scopes required by the matched operation's `security:` declaration. Configure it under `scopeCheck` in the scheme block: + + +Scope enforcement reads claims from the inbound bearer token at request time. The `oauth2` scheme does not validate the token's JWT signature itself in Tyk 5.14.0. JWT authentication must be configured on the API so that the inbound token is verified before scope check runs. Without a JWT auth method configured, Tyk's auth chain will reject the request before scope enforcement is reached. + + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + idpAuth: + enabled: true + scopeCheck: + enabled: true + claimNames: ["scope", "scp"] + separator: " " + scopeSource: "union" +``` + +When a request fails scope enforcement, Tyk returns `403 Forbidden` with a `WWW-Authenticate` challenge containing `error="insufficient_scope"`. + +### Scope check fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `enabled` | boolean | `false` | Enables scope enforcement for this scheme. | +| `claimNames` | array of strings | `["scope", "scp"]` | JWT claim names to read scopes from. Tyk reads all listed claims present on the token and merges the results into a single scope set. | +| `separator` | string | `" "` (space) | Character used to split string-valued scope claims. Set to `","` for comma-separated IdPs. | +| `scopeSource` | string | `"union"` | Which `security:` declarations drive the required scope set. See below. | + +### Scope source modes + +`scopeSource` controls which `security:` declarations Tyk enforces against: + +- **`union`** (default): Merges per-operation and root `security:` alternatives. A request passes if it satisfies any one alternative from the combined set. +- **`operation`**: Only the matched operation's `security:` array applies. The root `security:` array is ignored. +- **`global`**: Only the root `security:` array applies, uniformly across every operation on this API. + +### Per-operation and per-MCP-primitive overrides + +You can exempt individual operations or MCP primitives from scope enforcement by setting `enabled: false` on the operation or primitive: + +```yaml +x-tyk-api-gateway: + middleware: + operations: + getHealthCheck: + scopeCheck: + enabled: false + mcpTools: + ping: + scopeCheck: + enabled: false +``` + +This is useful where a specific operation enforces scopes upstream, or where a health-check endpoint should be reachable without a fully-scoped token. + +--- + +## Protected Resource Metadata + +Protected Resource Metadata (PRM) is a discovery document defined in [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) that tells OAuth 2.0 clients where to obtain a token and which scopes the resource accepts. Tyk publishes this document from the `oauth2` scheme configuration, serving it at a well-known path on the API. + +Configure PRM under `protectedResourceMetadata` in the scheme block: + +```yaml +x-tyk-api-gateway: + server: + authentication: + securitySchemes: + idpAuth: + enabled: true + protectedResourceMetadata: + enabled: true + resource: https://api.example.com/ + authorizationServers: + - https://idp.example.com/realms/demo + wellKnownPath: .well-known/oauth-protected-resource + autoDeriveScopes: true +``` + +Tyk serves the PRM document at `{listenPath}/{wellKnownPath}`. The default well-known path is `.well-known/oauth-protected-resource`. + +### PRM fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `enabled` | boolean | `false` | Enables PRM document publishing. | +| `resource` | string | - | Canonical identifier for this resource. Surfaced as `resource` in the PRM document. Accepts Tyk context variables. | +| `authorizationServers` | array of strings | - | Issuer URLs published in the PRM document. Clients use these to discover where to obtain a token. | +| `wellKnownPath` | string | `.well-known/oauth-protected-resource` | Path at which the PRM document is served, relative to the API listen path. | +| `autoDeriveScopes` | boolean | `true` | When `true`, the `scopes_supported` field is populated from both the `flows.scopes` catalog and every `security:` array on the API. When `false`, only the `flows.scopes` catalog is used. | + +### Migration + +Prior to Tyk 5.14.0, Protected Resource Metadata was configured at the root-level `authentication.protectedResourceMetadata` block. It is now configured under the `oauth2` security scheme at `authentication.securitySchemes[name].protectedResourceMetadata`. + +When Tyk Dashboard starts, it automatically migrates existing configurations across all OAS API definitions. No manual action is required. The migration is non-destructive: it skips any API where the new block already exists. If both locations are present, the scheme-level block takes precedence. + + +## Token exchange + +Token exchange (RFC 8693) is a client authentication feature that replaces the inbound token with a backend-scoped token before forwarding the request upstream. It is configured within the `oauth2` scheme but operates in the upstream authentication phase, after scope enforcement and before the reverse proxy. + +Token exchange is Enterprise Edition only. For full configuration details, see [Token exchange](/api-management/authentication/token-exchange). diff --git a/api-management/authentication/token-exchange.mdx b/api-management/authentication/token-exchange.mdx new file mode 100644 index 0000000000..39cb2ab88e --- /dev/null +++ b/api-management/authentication/token-exchange.mdx @@ -0,0 +1,193 @@ +--- +title: "Token Exchange" +description: "Configure RFC 8693 token exchange to replace the inbound token with a backend-scoped token before forwarding requests upstream." +keywords: "token exchange, RFC 8693, client authentication, OAuth 2.0, MCP, enterprise" +sidebarTitle: "Token Exchange" +--- + +## Availability + +| Feature | Editions | +|:---|:---| +| Token exchange | Enterprise | + +*Available from Tyk 5.14.0* + +## Introduction + +Token exchange ([RFC 8693](https://www.rfc-editor.org/rfc/rfc8693)) is a client authentication feature that replaces the inbound bearer token with a backend-scoped token before forwarding a request to the upstream service. Tyk Gateway presents itself as a confidential client to an external authorization server, exchanges the inbound token for one audienced to the upstream, and injects the result into the `Authorization` header. The inbound token never reaches the upstream service. + +This approach addresses two common problems in multi-service architectures and MCP Gateway deployments: + +- **Token audience mismatch**: An SSO or agent token issued for Tyk is not accepted by the upstream service, which expects a token carrying its own audience claim. +- **Audit trail continuity**: The raw inbound token is not forwarded to the upstream. The upstream receives a token scoped to its own audience, issued after the exchange, which keeps the token chain auditable at the authorization server level. + +Token exchange runs in the middleware chain after scope enforcement and before the reverse proxy. + + +In open source deployments, token exchange does not execute at runtime. The inbound token is forwarded to the upstream unchanged, and an error is logged. + + +--- + +## How it works + +When a request arrives at a Tyk API with token exchange enabled: + +1. Tyk validates the inbound bearer token using the configured `oauth2` security scheme. +2. If scope enforcement is enabled, Tyk checks the token's scopes against the operation's `security:` requirements. +3. Tyk reads the `iss` claim from the validated token and matches it against the `issuers` list on each configured provider. +4. Tyk POSTs an RFC 8693 exchange request to the matched provider's `tokenEndpoint`, presenting the inbound token as `subject_token`. +5. The authorization server returns a new access token. Tyk replaces the `Authorization` header with the exchanged token. +6. Tyk forwards the modified request upstream. + +If no configured provider's `issuers` list matches the inbound token's `iss` claim, Tyk returns `403 Forbidden` with `error="no_matching_provider"`. + +--- + +## Configure token exchange + +Token exchange is configured under `tokenExchange` within the `oauth2` security scheme in your Tyk OAS API definition. The `oauth2` scheme must be enabled on the API; see [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication). You must configure at least one provider. + + +Token exchange reads the `iss` claim from the inbound bearer token at request time. The `oauth2` scheme does not validate the token's JWT signature itself in Tyk 5.14.0. JWT authentication must be configured on the API so that the inbound token is verified before the exchange middleware runs. Without a JWT auth method configured, Tyk's auth chain will reject the request before token exchange is reached. + + +### Minimal example + +```yaml expandable +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + idpAuth: + enabled: true + tokenExchange: + enabled: true + providers: + - name: keycloak-prod + issuers: + - https://idp.example.com/realms/demo + tokenEndpoint: https://idp.example.com/realms/demo/protocol/openid-connect/token + clientAuth: + method: client_secret_basic + clientId: tyk-gateway + clientSecret: env://EXCHANGE_CLIENT_SECRET + defaultTarget: + audience: https://api.internal.example.com + scopes: + - api:read + - api:write +``` + +String fields in the API definition accept `env://`, `secrets://`, `vault://`, and `consul://` prefixes so sensitive values are not stored directly in the definition. + +### Provider fields + +Each entry in `providers` matches inbound tokens by `iss` claim and routes exchange requests to the corresponding token endpoint. Provider names must be unique, and issuer values must not overlap across providers. + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | Yes | Operator-assigned identifier used in log output. Must be unique across providers. | +| `issuers` | array of strings | Yes | Inbound token `iss` values routed to this provider. Must not overlap with issuers declared on other providers. | +| `tokenEndpoint` | string | Yes | Authorization server token endpoint. Must accept `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`. | +| `clientAuth.method` | string | No | How Tyk authenticates to the token endpoint: `client_secret_basic` (default) sends credentials in the `Authorization` header; `client_secret_post` sends them in the request body. | +| `clientAuth.clientId` | string | Yes | Client ID Tyk presents to the authorization server. | +| `clientAuth.clientSecret` | string | No | Client secret. Accepts `env://`, `secrets://`, `vault://`, `consul://` prefixes. | +| `defaultTarget.audience` | string | No | Default audience requested for the exchanged token. Applied when no per-operation override is set. | +| `defaultTarget.scopes` | array of strings | No | Default scopes requested. Applied when no per-operation override is set. | +| `timeout` | duration string | No | Per-call timeout for requests to `tokenEndpoint`. Accepts values such as `"5s"` or `"500ms"`. Defaults to `"15s"`. | +| `customParams` | map | No | Additional form parameters appended to the exchange request. Standard RFC 8693 keys (`grant_type`, `subject_token`, `audience`, and others) are reserved and cannot be overridden. | + +### Caching + +Tyk can cache exchanged tokens in Redis to avoid a round-trip to the authorization server on every request. Configure caching under `cache` within a provider: + +```yaml +providers: + - name: keycloak-prod + ... + cache: + enabled: true + mode: derived + maxTimeout: 5m + safetyMargin: 30s +``` + +| Field | Type | Default | Description | +|---|---|---|---| +| `enabled` | boolean | `false` | Enables Redis-backed caching for this provider's exchanged tokens. | +| `mode` | string | `"derived"` | How the cache TTL is computed. `"derived"`: TTL is `min(expiresIn, inboundRemaining, maxTimeout) − safetyMargin`, where `expiresIn` is the exchanged token's lifetime, `inboundRemaining` is the inbound token's remaining life, and `maxTimeout` is an optional operator ceiling. `"static"`: TTL is `min(timeout, expiresIn) − safetyMargin`; the exchanged token's expiry still acts as an upper bound even in fixed-TTL mode. | +| `maxTimeout` | duration string | - | Optional operator ceiling on the cache TTL in `"derived"` mode (for example, `"5m"`). Has no effect in `"static"` mode. | +| `timeout` | duration string | - | Fixed cache TTL in `"static"` mode (for example, `"2m"`). Still clamped by the exchanged token's expiry. | +| `safetyMargin` | duration string | `"30s"` | Duration subtracted from the computed TTL to avoid serving near-expired tokens. Applies in both modes. | + +--- + +## Per-operation override + +The `defaultTarget` on a provider applies to all requests routed to that provider. To request a different audience or scope set for a specific operation, add an `exchange` block under the matching operation in `middleware.operations`: + +```yaml +x-tyk-api-gateway: + middleware: + operations: + getInternalReport: + exchange: + enabled: true + audience: https://reporting.internal.example.com + scopes: + - reports:read +``` + +| Field | Type | Description | +|---|---|---| +| `enabled` | boolean | Activates this per-operation override. When `false` or absent, the provider's `defaultTarget` is used. | +| `audience` | string | Audience requested for this operation's exchanged token. | +| `scopes` | array of strings | Explicit scope list for this operation. When empty and `enabled` is `true`, scopes are inferred from the operation's `security:` declaration; see [Scope inference](#scope-inference). | + +--- + +## Per-MCP-primitive override + +For MCP Gateway deployments, you can override the exchange target per primitive using `middleware.mcpTools`, `middleware.mcpResources`, or `middleware.mcpPrompts`: + +```yaml +x-tyk-api-gateway: + middleware: + mcpTools: + create-report: + exchange: + enabled: true + audience: https://reporting.internal.example.com + scopes: + - reports:write +``` + +The `exchange` block on a primitive has the same fields as the per-operation block. This lets you route different primitives to different downstream audiences within a single provider. For example, read tools to a read-only service and write tools to an elevated-privilege service. + +--- + +## Scope inference + +When a per-operation or per-primitive `exchange` block has `enabled: true` but `scopes` is empty, Tyk infers the scope list from the operation's or primitive's `security:` declaration. The scopes required by the operation's security requirements are sent as the requested scope to the authorization server, aligning the exchanged token's scope with what the upstream is expected to require (RFC 8693 §4.5.5). + +If you prefer explicit control over the requested scopes, set `scopes` to a non-empty list. + + +--- + +## Known limitations + +The following limitations apply in Tyk 5.14.0: + +- **Azure Entra On-Behalf-Of**: Entra's On-Behalf-Of (OBO) flow does not conform to RFC 8693. Azure Entra is not supported in this release. + +--- + +## Token exchange and upstream auth + + +Do not configure `upstream.authentication.oauth` alongside token exchange. Token exchange replaces the `Authorization` header with the exchanged token before the reverse proxy; it is the upstream credential. The `upstream.authentication` block is for a separate scenario where Tyk authenticates to the upstream using its own static credentials, independent of any inbound token. Configuring both will produce a conflict. + diff --git a/api-management/automations.mdx b/api-management/automations.mdx new file mode 100644 index 0000000000..9835504d67 --- /dev/null +++ b/api-management/automations.mdx @@ -0,0 +1,66 @@ +--- +title: "Tyk Automations Tools" +description: "Tyk Tools that help with automating deployment and API Management operations" +keywords: "Tyk API Management, Tyk Sync, Tyk Operator, Github, Kubernetes, Automations" +sidebarTitle: "Overview" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; + +## Introduction + +Managing APIs across multiple environments can quickly become complex. Updating and overseeing multiple configurations, security policies, and deployments requires a significant amount of effort without the right tools. Tyk’s suite of automation tools simplifies this process by enabling automated control over API management tasks, helping teams ensure reliability, reduce manual errors, and maintain consistency across deployments. + +In this page, we’ll walk through the primary tools for automating API management with Tyk, including: + +* **Tyk Operator for Kubernetes**: Automate API deployments within Kubernetes environments. +* **Tyk Sync**: Sync configurations across environments for consistent API management. + +## Prerequisites + +Before diving into lifecycle automations with Tyk, ensure you have the following: + +- **A Tyk installation** (Self-Managed or Cloud) + - If you don't have Tyk installed, follow our [installation guide](/tyk-self-managed/install) + - For Tyk Cloud, sign up [here](https://tyk.io/sign-up/) + - Tyk Operator license key. Starting from Tyk Operator v1.0, a valid license key is required. + +- **Access to a Kubernetes cluster v1.19+** (for Tyk Operator sections) + - If you're new to Kubernetes, check out the official [Kubernetes documentation](https://kubernetes.io/docs/setup/) + +- **Helm 3+** (for installing Tyk Operator) + - If you don't have Helm installed, follow the [official Helm installation guide](https://helm.sh/docs/intro/install/) + - Verify your installation by running `helm version` in your terminal + +- **Tyk Dashboard v3+ access** (for Tyk Sync setup) + - Learn how to set up the Tyk Dashboard [here](/api-management/dashboard-configuration) + +- **Basic knowledge of Kubernetes, YAML** (important for Tyk Operator and Tyk Sync) + - For Kubernetes, visit the [official tutorials](https://kubernetes.io/docs/tutorials/) + - For YAML, check out this [YAML tutorial](https://yaml.org/spec/1.2/spec.html) + +If you're missing any of these prerequisites, please follow the provided links to set up the necessary components before proceeding with the lifecycle automation steps. + +## Automation Tools + + + + + +**Read time: 10 mins** + +Synchronize Tyk Environment With GitHub using Tyk Sync. + + + +**Read time: 10 mins** + +API Management in Kubernetes using Tyk Operator. + + + + + +## Conclusion + +With Tyk’s automation tools, you now have a set of options for streamlining API management, from handling deployments within Kubernetes to establishing consistency across multiple environments. By integrating these tools, you can simplify complex API workflows, maintain secure configurations, and save time through reduced manual intervention. \ No newline at end of file diff --git a/api-management/automations/operator.mdx b/api-management/automations/operator.mdx new file mode 100644 index 0000000000..e45016d4a8 --- /dev/null +++ b/api-management/automations/operator.mdx @@ -0,0 +1,987 @@ +--- +title: "Tyk Operator - API Management in Kubernetes" +description: "Kubernetes native API management using Tyk Operator" +keywords: "Tyk API Management, Tyk Sync, Tyk Operator, Github, Kubernetes, Automations" +sidebarTitle: "Overview" +--- + +## Introduction + +Using Tyk Operator within Kubernetes allows you to manage API lifecycles declaratively. This section provides instructions for setting up and configuring the Tyk Operator to automate API creation, updates, and security in Kubernetes clusters, ensuring your APIs align with Kubernetes management practices. + + +## What is Tyk Operator? +If you’re using Kubernetes, or if you’re building an API that operates within a Kubernetes environment, the Tyk Operator is a powerful tool for automating the API lifecycle. + +Tyk Operator is a native Kubernetes operator, allowing you to define and manage APIs as code. This means you can deploy, update, and secure APIs using the same declarative configuration approach Kubernetes uses for other application components. + +Tyk Operator + +## Key Concepts + +### GitOps With Tyk +With Tyk Operator, you can configure your APIs using Kubernetes native manifest files. You can use the manifest files in a GitOps workflow as the single source of truth for API deployment. + + + +If you use Tyk Operator to manage your APIs, you should set up RBAC such that human users cannot have the "write" permission on the API definition endpoints using Tyk Dashboard. + + + +#### What is GitOps? +“GitOps” refers to the operating model of using Git as the “single source of truth” to drive continuous delivery for infrastructure and software through automated CI/CD workflow. + +#### Tyk Operator in your GitOps workflow +You can install Argo CD, Flux CD or the GitOps tool of your choice in a cluster, and connect it to the Git repository where you version control your API manifests. The tool can synchronise changes from Git to your cluster. The API manifest updates in cluster would be detected by Tyk Operator, which has a Kubernetes controller to automatically reconcile the API configurations on your Tyk Gateway or Tyk Dashboard. + +**Kubernetes-Native Developer Experience** +API Developers enjoy a smoother Continuous Integration process as they can develop, test, and deploy the microservices. API configurations together use familiar development toolings and pipeline. + +**Reliability** +With declarative API configurations, you have a single source of truth to recover after any system failures, reducing the meantime to recovery from hours to minutes. + +#### Single Source of Truth for API Configurations +Tyk Operator will reconcile any divergence between the Kubernetes desired state and the actual state in [Tyk Gateway](/tyk-oss-gateway) or [Tyk Dashboard](/api-management/dashboard-configuration). Therefore, you should maintain the API definition manifests in Kubernetes as the single source of truth for your system. If you update your API configurations using Tyk Dashboard, those changes would be reverted by Tyk Operator eventually. + +To learn more about Gitops with Tyk, refer the following blog posts: +- [GitOps-enabled API management in Kubernetes](https://tyk.io/blog/gitops-enabled-api-management-in-kubernetes/) +- [A practical guide using Tyk Operator, ArgoCD, and Kustomize](https://tyk.io/blog/a-practical-guide-using-tyk-operator-argocd-and-kustomize/) + +### Custom Resources in Tyk + +In Kubernetes, a [Custom Resource (CR)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) is an extension of the Kubernetes API that allows you to introduce custom objects in your cluster. Custom Resources enable you to define and manage custom configurations and settings specific to your applications, making Kubernetes highly extensible. These custom objects are defined using Custom Resource Definitions (CRDs), which specify the schema and structure of the resource. + +Tyk Operator manages multiple custom resources to help users create and maintain their API configurations: + +**TykOasApiDefinition**: Available from Tyk Operator v1.0. It represents a [Tyk OAS API configuration](/api-management/gateway-config-tyk-oas). Tyk OAS API is based on the OpenAPI specification (OAS) and is the recommended format for standard HTTP APIs. + +**ApiDefinition**: Available on all versions of Tyk Operator. It represents a [Tyk Classic API configuration](/api-management/gateway-config-tyk-classic). Tyk Classic API is the traditional format used for defining all APIs in Tyk, and now the recommended format for non-HTTP APIs such as TCP, GraphQL, and Universal Data Graph (UDG). Tyk Operator supports the major features of Tyk Classic API and the feature support details can be tracked [here](/api-management/automations/operator#apidefinition-crd). + +**TykStreamsApiDefinition**: Available from Tyk Operator v1.1. It represents an [Async API configuration](/api-management/event-driven-apis#configuration-options) which is based on [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas). Tyk Operator supports all [Tyk Streams](/api-management/event-driven-apis#) features as they become available on the Gateway. + +**SecurityPolicy**: Available on all versions of Tyk Operator. It represents a [Tyk Security Policy configuration](/tyk-stack/tyk-operator/create-an-api#security-policy-example). Security Policies in Tyk provide a way to define and enforce security controls, including authentication, authorization, and rate limiting for APIs managed in Tyk. Tyk Operator supports essential features of Security Policies, allowing users to centrally manage access control and security enforcement for all APIs across clusters. + +**TykMcpProxyDefinition**: Available from Tyk Operator v1.4.0. It represents a [Model Context Protocol (MCP) proxy](/product-stack/tyk-operator/mcp-proxy) managed by Tyk. The Operator reads the MCP OAS document from a referenced `ConfigMap` and reconciles it with Tyk. + +These custom resources enable users to leverage Kubernetes' declarative configuration management to define, modify, and version their APIs, seamlessly integrating with other Kubernetes-based workflows and tools. + +#### Custom Resources for API and Policy Configuration + +The following custom resources can be used to configure APIs and policies at [Tyk Gateway](/tyk-oss-gateway) or [Tyk Dashboard](/api-management/dashboard-configuration). + +| Kind | Group | Version | Description | +| :-------------------- | :------------- | :----------- | :--------------------------------------------------------------------------------------------------- | +| TykOasApiDefinition| tyk.tyk.io | v1alpha1 | Defines configuration of [Tyk OAS API Definition object](/api-management/gateway-config-tyk-oas) | +| ApiDefinition | tyk.tyk.io | v1alpha1 | Defines configuration of [Tyk Classic API Definition object](/api-management/gateway-config-tyk-classic) | +| TykStreamsApiDefinition| tyk.tyk.io | v1alpha1 | Defines configuration of [Tyk Streams](/api-management/event-driven-apis#configuration-options) | +| SecurityPolicy | tyk.tyk.io | v1alpha1 | Defines configuration of [security policies](/api-management/policies). Operator supports linking ApiDefinition custom resources in SecurityPolicy's access list so that API IDs do not need to be hardcoded in the resource manifest. | +| TykMcpProxyDefinition | tyk.tyk.io | v1alpha1 | Defines a [Model Context Protocol (MCP) proxy](/product-stack/tyk-operator/mcp-proxy) sourced from a referenced `ConfigMap`. Available from Tyk Operator v1.4.0. | +| SubGraph | tyk.tyk.io | v1alpha1 | Defines a [GraphQL federation subgraph](/api-management/graphql#subgraphs-and-supergraphs). | +| SuperGraph | tyk.tyk.io | v1alpha1 | Defines a [GraphQL federation supergraph](/api-management/graphql#subgraphs-and-supergraphs). | +| OperatorContext | tyk.tyk.io | v1alpha1 | Manages the context in which the Tyk Operator operates, affecting its overall behavior and environment. See [Operator Context](/api-management/automations/operator#multi-tenancy-in-tyk) for details. | + +#### Tyk Classic Developer Portal + +The following custom resources can be used to configure [Tyk Classic Developer Portal](/tyk-developer-portal/tyk-portal-classic). + +| Kind | Group | Version | Description | +| :-------------------- | :------------- | :----------- | :--------------------------------------------------------------------------------------------------- | +| APIDescription | tyk.tyk.io | v1alpha1 | Configures [Portal Documentation](/tyk-apis/tyk-portal-api/portal-documentation). | +| PortalAPICatalogue | tyk.tyk.io | v1alpha1 | Configures [Portal API Catalogue](/getting-started/key-concepts/api-catalogue). | +| PortalConfig | tyk.tyk.io | v1alpha1 | Configures [Portal Configuration](/tyk-apis/tyk-portal-api/portal-configuration). | + + +### Reconciliation With Tyk Operator + +#### High Availability and Leader Election +Tyk Operator supports an active-passive model for High Availability (HA) using Kubernetes leader election. When leader election is enabled (which is the default behavior in the Tyk Operator Helm chart), only one Operator pod (the leader) actively reconciles Custom Resource Definitions (CRDs) at any given time. The remaining replicas stay on standby and will only take over the reconciliation process if the active leader pod becomes unavailable. + +#### Controllers & Operators +In Kubernetes, [controllers](https://kubernetes.io/docs/concepts/architecture/controller/) watch one or more Kubernetes resources, which can be built-in types like *Deployments* or custom resources like *ApiDefinition* - in this case, we refer to Controller as Operator. The purpose of a controller is to match the desired state by using Kubernetes APIs and external APIs. + +> A [Kubernetes operator](https://www.redhat.com/en/topics/containers/what-is-a-kubernetes-operator) is an application-specific controller that extends the functionality of the Kubernetes API to create, configure, and manage instances of complex applications on behalf of a Kubernetes user. + +#### Desired State vs Observed State +Let’s start with the *Desired State*. It is defined through Kubernetes Manifests, most likely YAML or JSON, to describe what you want your system to be in. Controllers will watch the resources and try to match the actual state (the observed state) with the desired state for Kubernetes Objects. For example, you may want to create a Deployment that is intended to run three replicas. So, you can define this desired state in the manifests, and Controllers will perform necessary operations to make it happen. + +How about *Observed State*? Although the details of the observed state may change controller by controller, usually controllers update the status field of Kubernetes objects to store the observed state. For example, in Tyk Operator, we update the status to include *api_id*, so that Tyk Operator can understand that the object was successfully created on Tyk. + +#### Reconciliation +Reconciliation is a special design paradigm used in Kubernetes controllers. Tyk Operator also uses the same paradigm, which is responsible for keeping our Kubernetes objects in sync with the underlying external APIs - which is Tyk in our case. + +**When would reconciliation happen?** +
+Before diving into Tyk Operator reconciliation, let's briefly mention some technical details about how and when reconciliation happens. Reconciliation only happens when certain events happen on your cluster or objects. Therefore, Reconciliation will **NOT** be triggered when there is an update or modification on Tyk’s side. It only watches certain Kubernetes events and is triggered based on them. Usually, the reconciliation happens when you modify a Kubernetes object or when the cache used by the controller expires - side note, controllers, in general, use cached objects to reduce the load in the Kube API server. Typically, caches expire in ~10 hours or so but the expiration time might change based on Operator configurations. + +So, in order to trigger Reconciliation, you can either +- modify an object, which will trigger reconciliation over this modified object or, +- restart Tyk Operator pod, which will trigger reconciliation over each of the objects watched by Tyk Operator. + +**What happens during Reconciliation?** +
+Tyk Operator will compare desired state of the Kubernetes object with the observed state in Tyk. If there is a drift, Tyk Operator will update the actual state on Tyk with the desired state. In the reconciliation, Tyk Operator mainly controls three operations; DELETE, CREATE, and UPDATE. + +- **CREATE** - an object is created in Kubernetes but not exists in Tyk +- **UPDATE** - an object is in different in Kubernetes and Tyk (we compare that by hash) +- **DELETE** - an object is deleted in Kubernetes but exists in Tyk + +**Drift Detection** +
+If human operators or any other system delete or modify API Definition from Tyk Gateway or Dashboard, Tyk Operator will restore the desired state back to Tyk during reconciliation. This is called Drift Detection. It can protect your systems from unauthorized or accidental modifications. It is a best practice to limit user access rights on production environment to read-only in order to prevent accidental updates through API Manager directly. + + +### CRD Versioning + +Tyk follows standard practices for naming and versioning custom resources as outlined by the Kubernetes Custom Resource Definition [versioning guidelines](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). Although we are currently on the `v1alpha1` version, no breaking changes will be introduced to existing Custom Resources without a version bump. This means that any significant changes or updates that could impact existing resources will result in a new version (e.g., `v1beta1` or `v1`) and Operator will continue supporting all CRD versions for a reasonable time before deprecating an older version. This ensures a smooth transition and compatibility, allowing you to upgrade without disrupting your current configurations and workflows. + +For more details on Kubernetes CRD versioning practices, refer to the Kubernetes Custom Resource Definition [Versioning documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/). + + +### Operator User +Tyk Operator is a Kubernetes Controller that manages Tyk Custom Resources (CRs) such as API Definitions and Security Policies. Developers define these resources as [Custom Resource (CRs)](#custom-resources-in-tyk), and Tyk Operator ensures that the desired state is reconciled with the Tyk Gateway or Dashboard. This involves creating, updating, or deleting API configurations until the target state matches the desired state. + +For the Tyk Dashboard, Tyk Operator functions as a system user, bound by Organization and RBAC rules. + +During start up, Tyk Operator looks for these keys from `tyk-operator-conf` secret or from the environment variables (listed in the table below). + +| Key or Environment Variable | Description | +|:-----|:-------------| +| `TYK_MODE` | "ce" for OSS or "pro" for licensed users | +| `TYK_URL` | URL of Tyk Gateway or Dashboard API | +| `TYK_ORG` | Organization ID of Operator user | +| `TYK_AUTH` | API key of Operator user | + +These would be the default credentials Tyk Operator uses to connect to Tyk. + + +### Multi-tenancy in Tyk + +Tyk Dashboard is multi-tenant capable, which means you can use a single Tyk Dashboard instance to host separate [organizations](/dashboard-admin-api#organizations) for each team or department. Each organization is a completely isolated unit with its own: + +- API Definitions +- API Keys +- Users +- Developers +- Domain +- Tyk Classic Portal + +This structure is ideal for businesses with a complex hierarchy, where distinct departments operate independently but within the same overall infrastructure. + +Multi-tenancy in Tyk Dashboard + +#### Define OperatorContext for Multi-Tenant API Management + +The `OperatorContext` in Tyk Operator allows you to create isolated management environments by defining specific access parameters for different teams or departments within a shared Tyk Operator instance. It helps you specify: + +- The Tyk Dashboard with which the Operator interacts +- The organization under which API management occurs +- The user identity utilized for requests +- The environment in which the Operator operates + +By setting different `OperatorContext` configurations, you can define unique access and management contexts for different teams. These contexts can then be referenced directly in your `ApiDefinition`, `TykOasApiDefinition` or `SecurityPolicy` custom resource definitions (CRDs) using the `contextRef` field, enabling precise control over API configurations. + +#### Example Scenarios Using OperatorContext + +1. **No OperatorContext Defined** + - If no `OperatorContext` is defined, Tyk Operator defaults to using credentials from the `tyk-operator-conf` secret or from environment variables. This means all API management actions are performed under the system’s default user credentials, with no specific contextual isolation. + +2. **OperatorContext Defined but Not Referenced** + - When an `OperatorContext` is defined but not referenced in an API configuration, Tyk Operator continues to use the default credentials from `tyk-operator-conf`. The specified `OperatorContext` is ignored, resulting in API operations being managed under default credentials. + +3. **OperatorContext Defined and Referenced** + - If a specific `OperatorContext` is both defined and referenced in an API or policy, Tyk Operator utilizes the credentials and parameters from the referenced `OperatorContext` to perform API operations. This allows each API or policy to be managed with isolated configurations, enabling team-based or department-specific API management within the same Kubernetes cluster. + +Using `OperatorContext` offers flexibility for multi-tenancy, helping organizations manage and isolate API configurations based on their specific team or departmental needs. + +Multi-tenancy in Kubernetes Tyk Operator + +### TLS Certificates + +Tyk Operator is designed to offer a seamless Kubernetes-native experience by managing TLS certificates stored within Kubernetes for your API needs. Traditionally, to use a certificate (e.g., as a client certificate, domain certificate, or certificate for accessing an upstream service), you would need to manually upload the certificate to Tyk and then reference it using a 'Certificate ID' in your API definitions. This process can become cumbersome, especially in a Kubernetes environment where certificates are often managed as secrets and may rotate frequently. + +To address this challenge, Tyk Operator allows you to directly reference certificates stored as Kubernetes secrets within your custom resource definitions (CRDs). This reduces operational overhead, minimizes the risk of API downtime due to certificate mismatches, and provides a more intuitive experience for API developers. + +#### Benefits of Managing Certificates with Tyk Operator +- **Reduced operational overhead**: Automates the process of updating certificates when they rotate. +- **Minimized risk of API downtime**: Ensures that APIs continue to function smoothly, even when certificates are updated. +- **Improved developer experience**: Removes the need for API developers to manage certificate IDs manually. + +#### Examples + +| Certificate Type | Supported in ApiDefinition | Supported in TykOasApiDefinition | Supported in TykStreamsApiDefinition | +| :------------------ | :------------- | :--------- | :--------- | +| Client certifates | ✅ [Client mTLS](/api-management/implement-tls#using-a-static-client-certificate-allow-list) | ✅ [Client mTLS](/api-management/implement-tls#using-a-static-client-certificate-allow-list) | Certificate ID can be set in the API Definition but configuring certificates from Secrets in CRD is not supported. | +| Custom domain certificates | ✅ [TLS and SSL](/api-management/implement-tls#configure-server-certificates-for-api-custom-domains) | ✅ [TLS and SSL](/api-management/implement-tls#configure-server-certificates-for-api-custom-domains) | Certificate ID can be set in the API Definition but configuring certificates from Secrets in CRD is not supported. | +| Public keys pinning | ✅ [Certificate pinning](/api-management/upstream-authentication/mtls#using-tyk-operator-to-configure-mtls-for-tyk-classic-apis) | ✅ [Certificate pinning](/api-management/upstream-authentication/mtls#certificate-pinning) | Certificate ID can be set in the API Definition but configuring certificates from Secrets in CRD is not supported. | +| Upstream mTLS | ✅ [Upstream mTLS via Operator](/api-management/upstream-authentication/mtls#using-tyk-operator-to-configure-mtls-for-tyk-classic-apis) | ✅ [Upstream mTLS via Operator](/api-management/upstream-authentication/mtls#using-tyk-operator-to-configure-mtls) | Certificate ID can be set in the API Definition but configuring certificates from Secrets in CRD is not supported. | + +## What Features Are Supported By Tyk Operator? + +### APIDefinition CRD +Tyk stores API configurations as JSON objects called API Definitions. If you are using Tyk Dashboard to manage Tyk, then these are stored in either Postgres or MongoDB, as specified in the database settings. On the other hand, if you are using Tyk OSS, these configurations are stored as files in the /apps directory of the Gateway which is located at the default path /opt/tyk-gateway. + +An API definition includes various settings and middleware that control how incoming requests are processed. + +#### API Types +Tyk supports various API types, including HTTP, HTTPS, TCP, TLS, and GraphQL. It also includes Universal Data Graph versions for unified data access and federation, allowing seamless querying across multiple services. + +| Type | Support | Supported From | Comments | +| :-------------------------------- | :--------- | :---------------- | :------------------------------ | +| HTTP | ✅ | v0.1 | Standard HTTP proxy for API requests. | +| HTTPS | ✅ | v0.4 | Secure HTTP proxy using SSL/TLS encryption. | +| TCP | ✅ | v0.1 | Handles raw TCP traffic, useful for non-HTTP APIs. | +| TLS | ✅ | v0.1 | Handles encrypted TLS traffic for secure communication. | +| GraphQL - Proxy | ✅ | v0.1 | Proxy for GraphQL APIs, routing queries to the appropriate service. | +| Universal Data Graph v1 | ✅ | v0.1 | Supports Universal Data Graph v1 for unified data access. | +| Universal Data Graph v2 | ✅ | v0.12 | Supports the newer Universal Data Graph v2 for more advanced data handling. | +| GraphQL - Federation | ✅ | v0.12 | Supports GraphQL Federation for querying multiple services as one API. | + +#### Management of APIs +Tyk offers flexible API management features such as setting active/inactive status, categorizing and naming APIs, versioning, and defining ownership within teams or organizations for streamlined administration. + +| Type | Support | Supported From | Comments | +| :-------------------------------- | :--------- | :---------------- | :------------------------------ | +| API Name | ✅ | v0.1 | Assign and manage names for your APIs. | +| API Status (inactive/active) | ✅ | v0.2 | Toggle API status between active and inactive. | +| API Categories | ✅ | v0.1 | Categorize APIs for easier management. | +| API ID | ✅ | v0.1 | Assign unique IDs to APIs for tracking and management. | +| API Ownership | ✅ | v0.12 | Define ownership of APIs within teams or organizations. Available for Tyk Classic APIs from v0.12. Currently not supported for Tyk OAS APIs (planned for v1.5.0). | +| API Versioning | ✅ | v0.1 | Enable version control for APIs. | + +#### Traffic Routing +Tyk enables traffic routing through path-based or host-based proxies and allows redirection to specific target URLs, providing control over how requests are directed to backend services. + +| Type | Supported | Supported From | Comments | +| :--------------------------- | :--------- | :-------------- | :---------------------------- | +| Path-Based Proxy | ✅ | v0.1 | Route traffic based on URL path. | +| Host-Based Proxy | ✅ | v0.1 | Route traffic based on the request host. | +| Target URL | ✅ | v0.1 | Redirect traffic to a specific target URL. | + +#### Client to Gateway Authentication and Authorization +Tyk provides multiple authentication options for client-to-gateway interactions, including keyless access, JWT, client mTLS, IP allow/block lists, and custom authentication plugins for enhanced security. + +| Type | Supported | Supported From | Comments | +| :----------------------------- | :--------- | :-------------- | :----------------------------------------------- | +| Keyless | ✅ | v0.1 | No authentication required, open access. | +| Auth Token | ✅ | v0.1 | Requires an authentication token (Bearer token).| +| JWT | ✅️ | v0.5 | Uses JSON Web Tokens for secure authentication. | +| OpenID Connect | ❌ | - | Recommended to use JWT for OIDC authentication. | +| OAuth2 | ❌ | - | OAuth2 not supported, JWT is recommended. | +| Client mTLS | ✅ | v0.11 | Supports static client mutual TLS authentication. | +| HMAC | ❌ | - | HMAC authentication is not implemented. | +| Basic Authentication | ✅ | v0.12 | Only supports enabling with default metadata. | +| Custom Authentication Plugin (Go) | ✅ | v0.11 | Custom authentication plugin written in Go. | +| Custom Authentication Plugin (gRPC) | ✅ | v0.1 | Custom authentication plugin using gRPC. | +| Multiple Authentication | ✅ | v0.14 | Chain multiple authentication methods. | +| IP Allowlist | ✅ | v0.5 | Allows access only from specific IP addresses. | +| IP Blocklist | ✅ | v0.5 | Blocks access from specific IP addresses. | + +#### Gateway to Upstream Authentication +Tyk supports secure upstream connections through mutual TLS, certificate pinning, and public key verification to ensure data integrity between the gateway and backend services. For full details, please see the [Upstream Authentication](/api-management/upstream-authentication) section. + +| Type | Supported | Supported From | +| :------------------------------------------------- | :----------- | :---------------- | +| Mutual TLS for upstream connectioons | ✅ | v0.9 | Mutual TLS authentication for upstream connections. | +| Public Key Certificate Pinning | ✅ | v0.9 | Ensures that the upstream certificate matches a known key. | +| Upstream Request Signing using HMAC | ✅ | v1.2.0 | Attach an encrypted signature to requests to verify the gateway as the sender. | + +#### API-level (Global) Features +Tyk offers global features for APIs, such as detailed traffic logging, CORS management, rate limiting, header transformations, and analytics plugins, with support for tagging, load balancing, and dynamic variables. + +| Feature | Supported | Supported From | Comments | +| :-------------------------------------- | :----------- | :---------------- | :------------------------------------------------------------------------ | +| Detailed recording (in Log Browser) | ✅ | v0.4.0 | Records detailed API traffic logs for analysis. | +| Config Data | ✅ | v0.8.2 | Stores additional configuration data for APIs. | +| Context Variables | ✅ | v0.1 | Enables dynamic context-based variables in APIs. | +| Cross Origin Resource Sharing (CORS) | ✅ | v0.2 | Manages CORS settings for cross-domain requests. | +| Service Discovery | ⚠️ | - | Service discovery is untested in this version. | +| Segment Tags | ✅ | v0.1 | Tags APIs for segmentation across environments. | +| Internal API (not exposed by Gateway)| ✅ | v0.6.0 | Internal APIs are not exposed via the Gateway. | +| Global (API-level) Header Transform | ✅ | v0.1.0 | Transforms request and response headers at the API level. | +| Global (API-level) Rate Limit | ✅ | v0.10 | Sets rate limits globally for APIs. | +| Custom Plugins | ✅ | v0.1 | Supports the use of custom plugins for API processing. | +| Analytics Plugin | ✅ | v0.16.0 | Integrates analytics plugins for API monitoring. | +| Batch Requests | ❌ | - | Batch requests are not supported. | +| Custom Analytics Tags (Tag Headers) | ✅ | v0.10.0 | Custom tags for API analytics data. | +| Expire Analytics After | ❌ | - | Not supported in this version. | +| Do not track Analytics (per API) | ✅ | v0.1.0 | Disable analytics tracking on specific APIs. | +| Webhooks | ❌ | - | Webhook support is not available. | +| Looping | ✅ | v0.6 | Enables internal looping of API requests. | +| Round Robin Load Balancing | ✅ | - | Supports round-robin load balancing across upstream servers. See [Load Balancing](/tyk-stack/tyk-operator/create-an-api#load-balancing). | + +#### Endpoint-level Features +For specific API endpoints, Tyk includes features like caching, circuit breaking, request validation, URL rewriting, and response transformations, allowing for precise control over request processing and response handling at an endpoint level. + +| Endpoint Middleware | Supported | Supported From | Comments | +| :----------------------------------- | :----------- | :---------------- | :------------------------------------------------ | +| Allow list | ✅️ | v0.8.2 | Allows requests only from approved sources. | +| Block list | ✅️ | v0.8.2 | Blocks requests from disapproved sources. | +| Cache | ✅ | v0.1 | Caches responses to reduce latency. | +| Advance Cache | ✅ | v0.1 | Provides advanced caching capabilities. | +| Circuit Breaker | ✅ | v0.5 | Prevents service overload by breaking circuits. | +| Track Endpoint | ✅ | v0.1 | Tracks API endpoint usage for analysis. | +| Do Not Track Endpoint | ✅ | v0.1 | Disables tracking for specific endpoints. | +| Enforced Timeouts | ✅ | v0.1 | Ensures timeouts for long-running requests. | +| Ignore Authentication | ✅ | v0.8.2 | Bypasses authentication for selected endpoints.| +| Internal Endpoint | ✅ | v0.1 | Restricts access to internal services. | +| URL Rewrite | ✅️ | v0.1 | Modifies request URLs before processing. | +| Validate Request | ✅ | v0.8.2 | Validates incoming requests before forwarding. | +| Rate Limit | ❌ | - | Rate limiting is not supported per endpoint. | +| Request Size Limit | ✅️ | v0.1 | Limits the size of requests to prevent overload.| +| Request Method Transform | ✅ | v0.5 | Modifies HTTP methods for incoming requests. | +| Request Header Transform | ✅ | v0.1 | Transforms request headers. | +| Request Body Transform | ✅ | v0.1 | Transforms request bodies for processing. | +| Request Body JQ Transform | ⚠️ | v0.1 | Requires JQ support on the Gateway Docker image.| +| Response Header Transform | ✅ | v0.1 | Transforms response headers. | +| Response Body Transform | ✅ | v0.1 | Transforms response bodies. | +| Response Body JQ Transform | ⚠️ | v0.1 | Requires JQ support on the Gateway Docker image.| +| Mock Response | ✅ | v0.1 | Simulates API responses for testing. | +| Virtual Endpoint | ✅ | v0.1 | Allows creation of dynamic virtual endpoints. | +| Per-Endpoint Plugin | ❌ | - | Plugin support per endpoint is not available. | +| Persist Graphql | ❌ | - | Not supported in this version. | + + +### TykOasAPIDefinition CRD +The TykOasApiDefinition Custom Resource Definition (CRD) manages [Tyk OAS API Definition objects](/api-management/gateway-config-tyk-oas) within a Kubernetes environment. This CRD enables the integration and management of Tyk API definitions using Kubernetes-native tools, simplifying the process of deploying and managing OAS APIs on the Tyk Dashboard. + +#### TykOasApiDefinition Features + +`TykOasApiDefinition` can support all features of the Tyk OAS API definition. You just need to provide the Tyk OAS API definition via a ConfigMap. In addition to managing the CRUD (Create, Read, Update, Delete) of Tyk OAS API resources, the Tyk Operator helps you better manage resources through object linking to Ingress, Security Policies, and certificates stored as Kubernetes secrets. See below for a list of Operator features and examples: + +| Features | Support | Supported From | Comments | Example | +| :---------- | :--------- | :----------------- | :---------- | :-------- | +| API Category | ✅ | v1.0 | - | [Manage API Categories](#api-categories) | +| API Version | ✅ | v1.0 | - | [Manage API versioning](#api-versioning) | +| API Ownership via OperatorContext | ❌ | - | Currently not supported for Tyk OAS APIs (planned for v1.5.0). | - | +| Client Certificates | ✅ | v1.0 | - | [Manage TLS certificate](#tls-certificates) | +| Custom Domain Certificates | ✅ | v1.0 | - | [Manage TLS certificate](#tls-certificates) | +| Public keys pinning | ✅ | v1.0 | - | [Manage TLS certificate](#tls-certificates) | +| Upstream mTLS | ✅ | v1.0 | - | [Manage TLS certificate](#tls-certificates) | +| Kubernetes Ingress | ✅ | v1.0 | - | [Kubernetes Ingress Controller](/product-stack/tyk-operator/tyk-ingress-controller) | +| Link with SecurityPolicy | ✅ | v1.0 | - | [Protect an API](/tyk-stack/tyk-operator/create-an-api#add-a-security-policy-to-your-api) | + +### TykStreamsApiDefinition CRD +The TykStreamsApiDefinition Custom Resource Definition (CRD) manages [Async API configuration](/api-management/event-driven-apis#configuration-options) within a Kubernetes environment. + +#### TykStreamsApiDefinition Features + +`TykStreamsApiDefinition` can support all features of [Tyk Streams](/api-management/event-driven-apis#). You just need to provide the Tyk Streams API definition via a ConfigMap. In addition to managing the CRUD (Create, Read, Update, Delete) of Tyk Streams API resources, the Tyk Operator helps you better manage resources through object linking to Security Policies. See below for a list of Operator features and examples: + +| Features | Support | Supported From | Comments | Example | +| :---------- | :--------- | :----------------- | :---------- | :-------- | +| Link with SecurityPolicy | ✅ | v1.0 | - | [Protect an API](/tyk-stack/tyk-operator/create-an-api#add-a-security-policy-to-your-api) | + +### Version Compatability +Ensuring compatibility between different versions is crucial for maintaining stable and efficient operations. This document provides a comprehensive compatibility matrix for Tyk Operator with various versions of Tyk and Kubernetes. By understanding these compatibility details, you can make informed decisions about which versions to deploy in your environment, ensuring that you leverage the latest features and maintain backward compatibility where necessary. + +#### Compatibility with Tyk +Tyk Operator can work with all version of Tyk beyond Tyk 3.x+. Since Tyk is backward compatible, you can safely use the +latest version of Tyk Operator to work with any version of Tyk. +However, if you're using a feature that was not yet available on an earlier version of Tyk, e.g. Defining a Subgraph with Tyk 3.x, you'll see error in Tyk Operator controller manager logs. + +See [Release notes](/developer-support/release-notes/operator) to check for each Tyk Operator release, +which version of Tyk it is tested against. + +| Tyk Version | 3.2 | 4.0 | 4.1 | 4.2 | 4.3 | 5.0 | 5.2 | 5.3 | 5.4 | 5.5 | 5.6 | 5.7 | +| :-------------------- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Tyk Operator v0.13 | Y | | | | Y | | | | | | | | +| Tyk Operator v0.14 | Y | Y | | | Y | Y | | | | | | | +| Tyk Operator v0.14.1 | Y | Y | | | Y | Y | | | | | | | +| Tyk Operator v0.15.0 | Y | Y | | | Y | Y | | | | | | | +| Tyk Operator v0.15.1 | Y | Y | | | Y | Y | | | | | | | +| Tyk Operator v0.16.0 | Y | Y | | | Y | Y | Y | | | | | | +| Tyk Operator v0.17.0 | Y | Y | | | Y | Y | Y | Y | | | | | +| Tyk Operator v0.17.1 | Y | Y | | | | Y | Y | Y | | | | | +| Tyk Operator v0.18.0 | Y | Y | | | | Y | Y | Y | Y | | | | +| Tyk Operator v1.0.0 | Y | Y | | | | Y | | Y | | Y | Y | | +| Tyk Operator v1.1.0 | Y | Y | | | | Y | | Y | | Y | Y | Y | + +#### Compatibility with Kubernetes Version + +See [Release notes](https://github.com/TykTechnologies/tyk-operator/releases) to check for each Tyk Operator release, +which version of Kubernetes it is tested against. + +| Kubernetes Version | 1.19 | 1.20 | 1.21 | 1.22 | 1.23 | 1.24 | 1.25 | 1.26 | 1.27 | 1.28 | 1.29 | 1.30 | +| :-------------------- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | +| Tyk Operator v0.13 | Y | Y | Y | Y | Y | Y | Y | | | | | | +| Tyk Operator v0.14 | Y | Y | Y | Y | Y | Y | Y | | | | | | +| Tyk Operator v0.14.1 | | Y | Y | Y | Y | Y | Y | Y | | | | | +| Tyk Operator v0.15.0 | | Y | Y | Y | Y | Y | Y | Y | | | | | +| Tyk Operator v0.15.1 | | Y | Y | Y | Y | Y | Y | Y | | | | | +| Tyk Operator v0.16.0 | | Y | Y | Y | Y | Y | Y | Y | | | | | +| Tyk Operator v0.17.0 | | | | | | | Y | Y | Y | Y | Y | | +| Tyk Operator v0.17.1 | | | | | | | Y | Y | Y | Y | Y | | +| Tyk Operator v0.18.0 | | | | | | | Y | Y | Y | Y | Y | | +| Tyk Operator v1.0.0 | | | | | | | Y | Y | Y | Y | Y | Y | +| Tyk Operator v1.1.0 | | | | | | | Y | Y | Y | Y | Y | Y | + + +### Security Policy CRD +The SecurityPolicy custom resource defines configuration of [Tyk Policies](/api-management/policies). + +Here are the supported features: + +| Features | Support | Supported From | Example | +| :-------------------------------- | :----------- | :---------------- | :--------- | +| API Access | ✅ | v0.1 | [API Access](/tyk-stack/tyk-operator/create-an-api#define-the-security-policy-manifest) | +| Rate Limit, Throttling, Quotas | ✅ | v0.1 | [Rate Limit, Throttling, Quotas](/tyk-stack/tyk-operator/create-an-api#define-the-security-policy-manifest) | +| Meta Data & Tags | ✅ | v0.1 | [Tags and Meta-data](/tyk-stack/tyk-operator/create-an-api#define-the-security-policy-manifest) | +| Path and Method based permissions | ✅ | v0.1 | [Path based permission](/tyk-stack/tyk-operator/create-an-api#security-policy-example) | +| Partitions | ✅ | v0.1 | [Partitioned policies](/tyk-stack/tyk-operator/create-an-api#security-policy-example) | +| Per API limit | ✅ | v1.0 | [Per API Limit](/tyk-stack/tyk-operator/create-an-api#security-policy-example) | +| Per-Endpoint limit | ✅ | v1.0 | [Per Endpoint Limit](/tyk-stack/tyk-operator/create-an-api#security-policy-example) | + +## Manage API MetaData + + +### API Name + +#### Tyk OAS API and Tyk Streams API + +API name can be set through `x-tyk-api-gateway.info.name` field in [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas) object. + +#### Tyk Classic API + +To set the name of an API in the `ApiDefinition`, use the `spec.name` string field. This name is displayed on the Tyk Dashboard and should concisely describe what the API represents. + +Example: + +```yaml {linenos=true, linenostart=1, hl_lines=["6-6"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: example-api # This is the metadata name of the Kubernetes resource +spec: + name: Example API # This is the "API NAME" in Tyk + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://example.com + listen_path: /example + strip_listen_path: true +``` + +### API Status + +#### API Active Status + +An active API will be loaded to the Gateway, while an inactive API will not, resulting in a 404 response when called. + +#### Tyk OAS API and Tyk Streams API + +API active state can be set through `x-tyk-api-gateway.info.state.active` field in [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas) object. + +#### Tyk Classic API + +The active status of an API can be set by modifying the `spec.active` configuration parameter. When set to `true`, this enables the API so that Tyk will listen for and process requests made to the `listenPath`. + +```yaml {linenos=true, linenostart=1, hl_lines=["9-9"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: inactive-api +spec: + name: Inactive API + use_keyless: true + protocol: http + active: false + proxy: + target_url: http://inactive.example.com + listen_path: /inactive + strip_listen_path: true +``` + +### API Accessibility + +An API can be configured as internal so that external requests are not processed. + +#### Tyk OAS API and Tyk Streams API + +API accessibility can be set through `x-tyk-api-gateway.info.state.internal` field in [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas) object. + +#### Tyk Classic API + +API accessibility can be set through the `spec.internal` configuration parameter as shown in the example below. + +```yaml {linenos=true, linenostart=1, hl_lines=["10-10"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: inactive-api +spec: + name: Inactive API + use_keyless: true + protocol: http + active: true + internal: true + proxy: + target_url: http://inactive.example.com + listen_path: /inactive + strip_listen_path: true +``` + +### API ID + +#### Creating a new API + +If you're creating a new API using Tyk Operator, you don't need to specify the ID. The API ID will be generated in a deterministic way. + +#### Tyk OAS API and Tyk Streams API + +The generated ID is stored in `status.id` field. Run the following command to inspect generated API ID of a Tyk OAS API. + +```bash +% kubectl get tykoasapidefinition [API_NAME] --namespace [NAMESPACE] -o jsonpath='{.status.id}' +ZGVmYXVsdC9wZXRzdG9yZQ +``` + +In this example, the generated API ID is `ZGVmYXVsdC9wZXRzdG9yZQ`. + +#### Tyk Classic API + +The generated ID is stored in `status.api_id` field. Run the following command to inspect generated API ID of a Tyk Classic API. + +```bash +% kubectl get apidefinition [API_NAME] --namespace [NAMESPACE] -o jsonpath='{.status.api_id}' +ZGVmYXVsdC90ZXN0 +``` + +In this example, the generated API ID is `ZGVmYXVsdC90ZXN0`. + +### Updating an existing API + +#### Tyk OAS API and Tyk Streams API + +If you already have API configurations created in the Tyk Dashboard and want to start using Tyk Operator to manage these APIs, you can include the existing API ID in the manifest under the `x-tyk-api-gateway.info.id` field in [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas) object. + +#### Tyk Classic API + +If you already have API configurations created in the Tyk Dashboard and want to start using Tyk Operator to manage these APIs, you can include the existing API ID in the manifest under the `spec.api_id` field. This way, when you apply the manifest, Tyk Operator will not create a new API in the Dashboard. Instead, it will update the original API with the Kubernetes spec. + +Example + +```yaml {linenos=true, linenostart=1, hl_lines=["8-8"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: existing-api + namespace: default +spec: + name: Existing API + api_id: 12345 + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://existing.example.com + listen_path: /existing + strip_listen_path: true +``` + +In this example, the API with ID `12345` will be updated according to the provided spec instead of creating a new API. + + +### API Categories +[API categories](/platform-management/api-categories) are configured differently for Tyk OAS APIs and Tyk Classic APIs. Please see below for examples. + +#### Tyk OAS API + +API categories can be specified through `categories` field in `TykOasApiDefinition` CRD. + +Here's an example: + +```yaml {linenos=true, linenostart=1, hl_lines=["7-9"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: oas-api-with-categories + namespace: tyk +spec: + categories: + - category 1 + - category 2 + tykOAS: + configmapRef: + keyName: oas-api-definition.json + name: tyk-oas-api-config + namespace: tyk +``` + +#### Tyk Streams API + +As of Tyk Operator v1.1, API categories is not supported in `TykStreamsApiDefinition` CRD. + +#### Tyk Classic API + +For a Tyk Classic API, you can specify the category name using the `name` field with a `#` qualifier. This will categorize the API in the Tyk Dashboard. See [How API categories work](/platform-management/api-categories#tyk-classic-apis) to learn about limitations on API names. + +Example + +```yaml {linenos=true, linenostart=1, hl_lines=["6-6"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: categorized-api +spec: + name: "my-classic-api #global #staging" + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://categorized.example.com + listen_path: /categorized + strip_listen_path: true +``` + +### API Versioning +[API versioning](/api-management/api-versioning) are configured differently for [Tyk OAS APIs](#tyk-oas-api) and [Tyk Classic APIs](#tyk-classic-api). Please see below for examples. + +#### Configuring API Version in Tyk OAS API Definition + +In the [Tyk OAS API Definition](/api-management/api-versioning), versioning can be configured via `x-tyk-api-gateway.versioning` object of the Base API, where the child API's IDs are specified. In the Kubernetes environment with Tyk Operator, where we reference API resources through its Kubernetes name and namespace, this is not desired. Therefore, we add support for versioning configurations through the field `versioning` in `TykOasApiDefinition` custom resource definition (CRD). + +Here's an example: + +```yaml{linenos=true, linenostart=1, hl_lines=["12-24"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: order-api + namespace: default +spec: + tykOAS: + configmapRef: + namespace: default + name: order-api + keyName: order-api-definition-v1.json + versioning: + enabled: true + location: header + key: x-api-version + name: v1 + default: v1 + fallbackToDefault: true + stripVersioningData: true + versions: + - name: v2 + tykOasApiDefinitionRef: + name: order-api-v2 + namespace: default +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: order-api-v2 + namespace: default +spec: + tykOAS: + configmapRef: + namespace: default + name: order-api-v2 + keyName: order-api-definition-v2.json +``` + +In this example, two different versions of an API are defined: `order-api` (v1) and `order-api-v2` (v2). + +`versioning` is configured at `order-api` (v1), the Base API, and it has similiar structure as [Tyk OAS API Definition](/api-management/api-versioning): + +- `versioning`: This object configures API versioning for the `order-api`. + - `enabled`: Set to true to enable versioning. + - `name`: an identifier for this version of the API (v1). + - `default`: Specifies the default version (v1), which will be used if no version is specified in the request. + - `location`: Specifies where the version key is expected (in this case, in the header). It can be set to `header` or `url-param`. + - `key`: Specifies the versioning identifier key (`x-api-version`) to identify the version. In this example, the version is determined by an HTTP header named `x-api-version`. + - `fallbackToDefault`: When set to true, if an unspecified or invalid version is requested, the default version (v1) will be used. + - `stripVersioningData`: When true, removes versioning identifier (like headers or query parameters) from the upstream request to avoid exposing internal versioning details. + - `urlVersioningPattern`: Specifies a regex that matches the format that you use for the versioning identifier (name) if you are using stripVersioningData and fallBackToDefault with location=url with Tyk 5.5.0 or later + - `versions`: Defines the list of API versions available: + - `name`: an identifier for this version of the API (v2). + - `tykOasApiDefinitionRef`: Refers to a separate TykOasApiDefinition resource that represent a new API version. + - `name`: Kubernetes metadata name of the resource (`order-api-v2`). + - `namespace`: Kubernetes metadata namespace of the resource (`default`). + +With Tyk Operator, you can easily associate different versions of your APIs using their Kubernetes names. This eliminates the need to include versioning information directly within the base API's definition (`x-tyk-api-gateway.versioning` object), which typically requires referencing specific API IDs. Instead, the Operator allows you to manage versioning declaratively in the `TykOasApiDefinition` CRD, using the `versioning` field to specify versions and their Kubernetes references (names and namespaces). + +When using the CRD for versioning configuration, you don't have to worry about knowing or managing the unique API IDs within Tyk. The Tyk Operator handles the actual API definition configuration behind the scenes, reducing the complexity of version management. + +In case if there is original versioning information in the base API Definition, the versioning information will be kept and be merged with what is specified in CRD. If there are conflicts between the Tyk OAS API Definition and CRD, we will make use of CRD values as the final configuration. + +Tyk Operator would also protect you from accidentally deleting a version of an API that is being referenced by another API, maintaining your API integrity. + +#### Configuring API Version in Tyk Streams API Definition + +As of Tyk Operator v1.1, API versioning is not supported in `TykStreamsApiDefinition` CRD. This can be configured natively in the Tyk Streams API Definition. + +#### Configuring API Version in Tyk Classic API Definition + +For Tyk Classic API, versioning can be configured via `ApiDefinition` custom resource definition (CRD). See [Tyk Classic versioning](/api-management/gateway-config-tyk-classic#tyk-classic-api-versioning) for a comprehensive example of configuring API versioning for Tyk Classic API with Tyk Operator. + +### API Ownership + +Please consult the [API Ownership](/platform-management/api-ownership) documentation for the fundamental concepts of API Ownership in Tyk and [Operator Context](/api-management/automations/operator#multi-tenancy-in-tyk) documentation for an overview of the use of OperatorContext to manage resources for different teams effectively. + +The guide includes practical examples for managing API ownership via OperatorContext. Key topics include defining user owners and user group owners in OperatorContext for connecting and authenticating with a Tyk Dashboard, and using `contextRef` in `TykOasApiDefinition` or `ApiDefinition` objects to ensure configurations are applied within specific organizations. The provided YAML examples illustrate how to set up these configurations. + +#### How API Ownership works in Tyk Operator + +In Tyk Dashboard, API Ownership ensures that only designated 'users' who own an API can modify it. This security model is crucial for maintaining control over API configurations, especially in a multi-tenant environment where multiple teams or departments may have different responsibilities and permissions. + +Tyk Operator is designed to interact with Tyk Dashboard as a system user. For the Tyk Dashboard, Tyk Operator is just another user that must adhere to the same access controls and permissions as any other user. This means: + +- Tyk Operator needs the correct access rights to modify any APIs. +- It must be capable of managing APIs according to the ownership rules set in Tyk Dashboard. + +To facilitate API ownership and ensure secure operations, Tyk Operator must be able to 'impersonate' different users for API operations. This is where `OperatorContext` comes into play. Users can define different `OperatorContext` objects that act as different agents to connect to Tyk Dashboard. Each `OperatorContext` can specify different access parameters, including the user access key and organization it belongs to. Within `OperatorContext`, users can specify the IDs of owner users or owner user groups. All APIs managed through that `OperatorContext` will be owned by the specified users and user groups, ensuring compliance with Tyk Dashboard's API ownership model. + +Enabling API ownership with OperatorContext + +#### OperatorContext + +Here's how `OperatorContext` allows Tyk Operator to manage APIs under different ownerships: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: OperatorContext +metadata: + name: team-alpha + namespace: default +spec: + env: + # The mode of the admin api + # ce - community edition (open source gateway) + # pro - dashboard (requires a license) + mode: pro + # Org ID to use + org: *YOUR_ORGANIZATION_ID* + # The authorization token this will be set in x-tyk-authorization header on the + # client while talking to the admin api + auth: *YOUR_API_ACCESS_KEY* + # The url to the Tyk Dashboard API + url: http://dashboard.tyk.svc.cluster.local:3000 + # Set this to true if you want to skip tls certificate and host name verification + # this should only be used in testing + insecureSkipVerify: true + # For ingress the operator creates and manages ApiDefinition resources, use this to configure + # which ports the ApiDefinition resources managed by the ingress controller binds to. + # Use this to override default ingress http and https port + ingress: + httpPort: 8000 + httpsPort: 8443 + # Optional - The list of users who are authorized to update/delete the API. + # The user pointed by auth needs to be in this list, if not empty. + user_owners: + - a1b2c3d4e5f6 + # Optional - The list of groups of users who are authorized to update/delete the API. + # The user pointed by auth needs to be a member of one of the groups in this list, if not empty. + user_group_owners: + - 1a2b3c4d5e6f +``` + +#### Tyk OAS API + + +API ownership is currently not supported for Tyk OAS APIs (planned for Tyk Operator v1.5.0). + + +Once an `OperatorContext` is defined, you can reference it in your Tyk OAS API Definition objects using `contextRef`. Below is an example with TykOasApiDefinition: +```yaml {hl_lines=["40-43"],linenos=true} +apiVersion: v1 +data: + test_oas.json: |- + { + "info": { + "title": "Petstore", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Petstore", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore.swagger.io/v2" + }, + "server": { + "listenPath": { + "value": "/petstore/", + "strip": true + } + } + } + } +kind: ConfigMap +metadata: + name: cm + namespace: default +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: petstore +spec: + contextRef: + name: team-alpha + namespace: default + tykOAS: + configmapRef: + name: cm + namespace: default + keyName: test_oas.json +``` + +In this example, the `TykOasApiDefinition` object references the `team-alpha` context, ensuring that it is managed under the ownership of the specified users and user groups. + +#### Tyk Classic API + +Similarly, if you are using Tyk Classic API, you can reference it in your API Definition objects using `contextRef`. Below is an example: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin + namespace: alpha +spec: + contextRef: + name: team-alpha + namespace: default + name: httpbin + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +In this example, the `ApiDefinition` object references the `team-alpha` context, ensuring that it is managed under the ownership of the specified users and user groups. + +## Troubleshooting and FAQ + + + +While Tyk Operator is designed to work within a Kubernetes environment, you can still use it to manage non-Kubernetes Tyk installations. You'll need to: + +1. Run Tyk Operator in a Kubernetes cluster. +2. Configure Tyk Operator to point to your external Tyk installation, e.g. via `tyk-operator-conf`, environment variable, or OperatorContext: +```yaml + TYK_MODE: pro + TYK_URL: http://external-tyk-dashboard + TYK_AUTH: api-access-key + TYK_ORG: org-id +``` + +This allows you to manage your external Tyk installation using Kubernetes resources. + + + +From [Tyk Operator v0.15.0](https://github.com/TykTechnologies/tyk-operator/releases/tag/v0.15.0), we introduce a new status [subresource](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#subresources) in APIDefinition CRD, called _latestTransaction_ which holds information about reconciliation status. + +> The [Status subresource](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#status-subresource) in Kubernetes is a specialized endpoint that allows developers and operators to retrieve the real-time status of a specific Kubernetes resource. By querying this subresource, users can efficiently access essential information about a resource's current state, conditions, and other relevant details without fetching the entire resource, simplifying monitoring and aiding in prompt decision-making and issue resolution. + +The new status subresource _latestTransaction_ consists of a couple of fields that show the latest result of the reconciliation: +- `.status.latestTransaction.status`: shows the status of the latest reconciliation, either Successful or Failed; +- `.status.latestTransaction.time`: shows the time of the latest reconciliation; +- `.status.latestTransaction.error`: shows the message of an error if observed in the latest transaction. + +**Example: Find out why an APIDefinition resource cannot be deleted** + +Consider the scenario when APIDefinition and SecurityPolicy are connected. Usually, APIDefinition cannot be deleted directly since it is protected by SecurityPolicy. The proper approach to remove an APIDefinition is to first remove the reference to the SecurityPolicy (either by deleting the SecurityPolicy CR or updating SecurityPolicy CR’s specification), and then remove the APIDefinition itself. However, if we directly delete this APIDefinition, Tyk Operator won’t delete the APIDefinition unless the link between SecurityPolicy and APIDefinition is removed. It is to protect the referential integrity between your resources. + +```console +$ kubectl delete tykapis httpbin +apidefinition.tyk.tyk.io "httpbin" deleted +^C% +``` + +After deleting APIDefinition, the operation hangs, and we suspect that something is wrong. +Users might still look through the logs to comprehend the issue, as they did in the past, but they can now examine their APIDefinition’s status subresource to make their initial, speedy issue diagnosis. + +```console +$ kubectl get tykapis httpbin +NAME DOMAIN LISTENPATH PROXY.TARGETURL ENABLED STATUS +httpbin /httpbin http://httpbin.org true Failed +``` +As seen in the STATUS column, something went wrong, and the STATUS is Failed. + +To get more information about the APIDefinition resource, we can use `kubectl describe` or `kubectl get`: +```console +$ kubectl describe tykapis httpbin +Name: httpbin +Namespace: default +API Version: tyk.tyk.io/v1alpha1 +Kind: ApiDefinition +Metadata: + ... +Spec: + ... +Status: + api_id: ZGVmYXVsdC9odHRwYmlu + Latest CRD Spec Hash: 9169537376206027578 + Latest Transaction: + Error: unable to delete api due to security policy dependency=default/httpbin + Status: Failed + Time: 2023-07-18T07:26:45Z + Latest Tyk Spec Hash: 14558493065514264307 + linked_by_policies: + Name: httpbin + Namespace: default +``` +or +```console +$ kubectl get tykapis httpbin -o json | jq .status.latestTransaction +{ + "error": "unable to delete api due to security policy dependency=default/httpbin", + "status": "Failed", + "time": "2023-07-18T07:26:45Z" +} +``` +Instead of digging into Tyk Operator's logs, we can now diagnose this issue simply by looking at the `.status.latestTransaction` field. As `.status.latestTransaction.error` implies, the error is related to *SecurityPolicy* dependency. + + + +Yes, you can use Tyk Operator to manage multiple Tyk installations. You'll need to create separate `OperatorContext` resources for each installation: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: OperatorContext +metadata: + name: prod-context +spec: + env: + TYK_MODE: pro + TYK_URL: http://tyk-dashboard-staging + TYK_AUTH: prod-secret +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: OperatorContext +metadata: + name: staging-context +spec: + env: + TYK_MODE: pro + TYK_URL: http://tyk-dashboard-staging + TYK_AUTH: staging-secret +``` + +Then, you can specify which context to use in your API and Policy resources: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: my-api +spec: + name: My API + context: prod-context + # ... other API configuration +``` + + diff --git a/api-management/automations/sync.mdx b/api-management/automations/sync.mdx new file mode 100644 index 0000000000..bc20799f2e --- /dev/null +++ b/api-management/automations/sync.mdx @@ -0,0 +1,155 @@ +--- +title: "Tyk Sync - Synchronize Tyk Environment With GitHub" +description: "Learn how to synchronize your Tyk configuration with GitHub using Tyk Sync" +keywords: "Tyk API Management, Tyk Sync, Tyk Operator, Github, Kubernetes, Automations" +sidebarTitle: "Overview" +--- + +## Introduction + +Tyk Sync enables you to export and import Tyk configurations directly from Git, keeping environments aligned without manual configuration updates. This section covers the setup and use of Tyk Sync, providing steps to ensure consistent configurations across different environments. + + +## Tyk Sync Features +Tyk Sync works with *Tyk Dashboard* installation. With Tyk Dashboard, Tyk Sync supports managing API definitions, security policies, and API templates. + +| Tyk Sync Feature | Tyk Dashboard (Licensed) | +| :--------------------------------------------------------------------------- | :-------------------------- | +|

Backup objects from Tyk to a directory

If you want to backup your API definitions, policies and templates in Tyk, you can use the `dump` command. It allows you to save the objects in transportable files. You can use this command to backup important API configurations before upgrading Tyk, or to save API configurations from one Dashboard instance and then use `update`, `publish`, or `sync` commands to update the API configurations to another Dashboard instance. | ✅ | +|

Synchronise objects from Git (or any VCS) to Tyk

To implement GitOps for API management, store your API definitions, policies and templates in Git or any version control system. Use the `sync` command to synchronise those objects to Tyk. During this operation, Tyk Sync will delete any objects in the Dashboard that cannot be found in the VCS, and update those that can be found and create those that are missing. | ✅ | +|

Update objects

The `update` command will read from VCS or file system and will attempt to identify matching API definitions, policies and templates in the target Dashboard, and update them. Unmatched objects will not be created. | ✅ | +|

Publish objects

The `publish` command will read from VCS or file system and create API definitions, policies, and templates in target Dashboard. This will not update any existing objects. If it detects a collision, the command will stop. | ✅ | +|

Show and import Tyk examples

The `examples` command allow you to show and import [Tyk examples](https://github.com/TykTechnologies/tyk-examples). An easy way to load up your Tyk installation with some interesting examples!| ✅ | + +### Working with OAS APIs + +Starting with Sync v1.5+ and Dashboard v5.3.2+, Tyk Sync supports both [Tyk OAS APIs](/api-management/gateway-config-tyk-oas) and [Tyk Classic APIs](/api-management/gateway-config-tyk-classic) when working with the Tyk Dashboard, without requiring special flags or configurations. + +For Sync versions v1.4.1 to v1.4.3, enabling Tyk Sync for Tyk OAS APIs requires the [allow-unsafe-oas](/tyk-dashboard/configuration#allow_unsafe_oas) configuration in the Dashboard, along with the `--allow-unsafe-oas` flag when invoking Tyk Sync. Note that Tyk Sync versions v1.4.1 to 1.4.3 do not support API Category for Tyk OAS APIs. + +### Working with Tyk Streams APIs + +Tyk Streams API support was introduced in Tyk Dashboard v5.7.0. Tyk Sync v2.0 and later is compatible with Tyk Streams APIs and manages them similarly to Tyk OAS APIs. With Tyk Sync, you can seamlessly sync, publish, update, and dump Tyk Streams APIs just like OAS APIs. + +Note: The Streams API validator is not applied during these operations. + +### Working with Open Source Gateway + +From Sync v2.0, compatibility with the Open Source Tyk Gateway has been removed, making Tyk Sync v2.0 compatible exclusively with licensed Tyk Dashboard. As a result, Tyk Sync is no longer usable with the Open Source (OSS) version of the Tyk Gateway. + +## Installation + +To install Tyk Sync, follow the instructions in the [Tyk Sync installation guide](/product-stack/tyk-sync/installing-tyk-sync). You can install Tyk Sync using Docker or download the binary directly. + +## Quick Start Guide + +For a quick start guide to using Tyk Sync, refer to the [Tyk Sync Quick Start Guide](/api-management/sync/quick-start). This guide will help you set up Tyk Sync, dump API configurations, and synchronize them with your Tyk Dashboard. + +## Glossary + +### Tyk Sync +A command line tool and library designed to manage and synchronize Tyk API Gateway configurations with version control systems. Originally called "tyk-git," it was renamed to "tyk-sync" as its capabilities expanded beyond Git to support synchronization with any file system. + +### Synchronization +The process of ensuring that API and policy configurations in your Tyk Gateway match those stored in your version control system. Tyk Sync performs one-way synchronization, where definitions are written from the VCS to the Tyk Dashboard. + +### Spec File (.tyk.json) +A metadata file created by Tyk Sync that contains information about the APIs and policies in a directory. This file is used during synchronization to determine what needs to be created, updated, or deleted. + +## FAQ + + + +Tyk Sync is designed to dump API configurations from a Tyk Dashboard, not directly from a Tyk Gateway. + +Tyk Sync's `dump` command is specifically designed to work with the Tyk Dashboard. The command requires a dashboard URL and API secret: + +```bash +tyk-sync dump -d="http://dashboard-url" -s="dashboard-secret" -t="./output-directory" +``` + +There is no equivalent flag or functionality to dump configurations directly from a standalone Gateway. This is because: + +1. The Dashboard serves as the central configuration repository in the Tyk architecture +2. The Gateway is primarily focused on runtime execution of those configurations +3. While Gateways can operate standalone, they don't expose the same management APIs as the Dashboard + + + +The three commands in Tyk Sync have distinct purposes and behaviors when managing API configurations: + +**sync** +- **Purpose**: Comprehensive synchronization from a source (Git repo or file system) to Tyk Dashboard +- **Behavior**: + - Creates new APIs, policies, and assets that exist in the source but not in the Dashboard + - Updates existing APIs, policies, and assets that exist in both places + - Deletes APIs, policies, and assets that exist in the Dashboard but not in the source (unless `--no-delete` flag is used) +- **Use case**: When you want to make the Dashboard exactly match your source repository + +**publish** +- **Purpose**: Only adds new API configurations to Tyk Dashboard +- **Behavior**: + - Creates new APIs, policies, and assets that don't already exist in the Dashboard + - Will not update existing items + - Stops if it detects a collision (an API that already exists) + - Will not delete anything +- **Use case**: When you want to add new APIs without affecting existing ones + +**update** +- **Purpose**: Only updates existing API configurations in Tyk Dashboard +- **Behavior**: + - Updates APIs, policies, and assets that already exist in the Dashboard + - Will not create new items + - Will not delete anything +- **Use case**: When you want to update existing APIs without adding new ones or removing any + +In summary, "sync" is the most comprehensive operation (create + update + delete), "publish" only creates new items, and "update" only modifies existing items. + + + +Tyk Sync allows you to dump configurations to a local directory, which can then be committed to a Git repository. This enables version control and easy synchronization across environments. + +For example: +1. Dump configurations: `tyk-sync dump -d http://dashboard:3000 -s secret -t ./configs` +2. Commit to Git: + ``` + cd configs + git add . + git commit -m "Update Tyk configurations" + git push + ``` + + + +Yes, you can store multiple API definitions, policies, and other Tyk resources in a single Git repository. Tyk Sync and Tyk Operator can work with multiple resources in the same directory. + +Your repository structure might look like this: +``` +tyk-configs/ +├── apis/ +│ ├── api1.yaml +│ └── api2.yaml +├── policies/ +│ ├── policy1.yaml +│ └── policy2.yaml +└── tyk-operator/ + └── operator-context.yaml +``` + + + +To roll back changes made with Tyk Sync: + +1. If you're using Git, check out the previous version of your configurations: + ```bash + git checkout + ``` + +2. Use Tyk Sync to publish the previous version: + ```bash + tyk-sync sync -d http://dashboard:3000 -s -p ./ + ``` + +It's a good practice to maintain separate branches or tags for different environments to make rollbacks easier. + + diff --git a/api-management/batch-processing.mdx b/api-management/batch-processing.mdx new file mode 100644 index 0000000000..25e0dc8b00 --- /dev/null +++ b/api-management/batch-processing.mdx @@ -0,0 +1,234 @@ +--- +title: "Batch Processing" +description: "Make multiple API requests in a single HTTP call using Batch Requests" +keywords: "Request Optimization, Optimization, Batched Requests, Batch, Batch Processing" +sidebarTitle: "Batch Processing" +--- + +## Overview + +Batch Requests is a powerful Tyk Gateway feature that allows clients to make multiple requests to an API in a single HTTP call. Instead of sending numerous individual requests to the API, clients can bundle these requests together, reducing network overhead and improving performance. + +### What are Batch Requests? + +Batch Requests act as an aggregator for multiple API calls. When a client sends a batch request to Tyk, the Gateway processes each request in the batch individually (applying all relevant middleware, authentication, and rate limiting) and returns a combined response containing the results of all requests. The scope of a batch request is limited to a single API deployed on Tyk, though can comprise requests to different endpoints (method and path) defined for that API. + +### Key Benefits + +- Reduced Network Overhead: Minimize the number of HTTP connections required for multiple related API operations +- Improved Client Performance: Decrease latency by eliminating multiple round-trips to the server +- Simplified Error Handling: Process success and failure responses for multiple operations in a single place +- Maintained Security: Each individual request within a batch still goes through Tyk's full security pipeline +- Flexible Execution: Choose between parallel or sequential execution of requests + +### When to Use Batch Requests + +Batch Requests are ideal for scenarios such as: + +- Mobile applications that need to fetch data from multiple endpoints during startup +- Dashboard applications that need to populate multiple widgets with different API data +- Complex workflows that require data from several API endpoints to complete a single user action +- Integration scenarios where you need to synchronize operations across multiple services + +### How Batch Requests Work + +When Tyk receives a batch request, it: + +- Validates the batch request format +- Processes each request in the batch individually (applying all middleware, authentication, and quotas) +- Collects all responses +- Returns a single combined response to the client + +This process ensures that security is maintained while providing the performance benefits of batching. + +## Using Batch Requests + +### Configuration + +Batch Requests are disabled by default, so you need to enable batch request support in your API definition by setting `server.batchProcessing.enabled` in the Tyk Vendor Extension (Tyk Classic: `enable_batch_request_support`). + +### Batch Request Endpoint + +When batch requests are enabled, Tyk automatically creates an additional logical endpoint on the subrouter for the API. This won't appear in the API definition and so will not be added to the OpenAPI description. This `/tyk/batch/` endpoint accepts requests in a specific "batch" format and processes them as described in the next section. + +For example, if your API's listen path is `/myapi/` the batch request endpoint would be `/myapi/tyk/batch/`. + +Note that the trailing slash `/` at the end of the URL is required when calling this endpoint. + +### Batch Request Format + +Batch requests must be sent as HTTP `POST` requests with a JSON payload that follows this structure: + +```json +{ + "requests": [ + { + "method": "GET", + "headers": { + "x-header-1": "value-1", + "authorization": "your-auth-token" + }, + "body": "", + "relative_url": "resource/123" + }, + { + "method": "POST", + "headers": { + "x-header-2": "value-2", + "authorization": "your-auth-token" + }, + "body": "{\"property\": \"value\"}", + "relative_url": "resource/create" + }, + { + "method": "GET", + "headers": { + "x-header-3": "value-3", + "authorization": "your-auth-token" + }, + "body": "", + "relative_url": "resource/invalid" + } + ], + "suppress_parallel_execution": false +} +``` + +Where: + +- `requests`: An array of individual requests to be processed + - `method`: The HTTP method for the individual request (`GET`, `POST`, `PUT`, `DELETE`, etc.) + - `headers`: Any HTTP headers to include with the request + - `body`: The request body (for `POST`, `PUT` requests) in the format prescribed by the API (e.g. JSON string) + - `relative_url`: The endpoint for the request, which can include query parameters +- `suppress_parallel_execution`: A boolean flag to control whether requests should be processed in parallel (`false`) or sequentially in the order that they appear in the array (`true`) + +In the example above, on receipt of a request to `POST /my-api/tyk/batch` with this payload, Tyk would process three requests in parallel: + +- `GET /my-api/resource/123` passing `x-header-1` and `Authorization` headers +- `POST /my-api/resource/create` passing `x-header-2` and `Authorization` headers and the payload descrbied in `body` +- `GET /my-api/resource/invalid` passing `x-header-3` and `Authorization` headers + +### Execution Order + +Tyk will work through the requests in the batch in the order that they are declared in the `requests` array. The `suppress_parallel_execution` setting is used to determine whether Tyk should wait for each request to complete before starting the next (`true`), or if it should issue all of the requests in parallel (`false`). + +If sequential execution is in use, Tyk will work through the entire `requests` array regardless of whether any requests return errors. All responses (success and failure) will be logged and returned to the client as described [below](/api-management/batch-processing#batch-response-format). + +### Batch Response Format + +When you send a batch request to Tyk, each individual request within the batch is processed independently. This means that some requests in a batch may succeed while others fail. Tyk provides detailed response information for each request in the batch to help you identify and handle errors appropriately. + +The response from a batch request is an array of response objects, each corresponding to one of the requests in the batch in the order that they appeared in the `requests` array: + +```json +[ + { + "relative_url": "resource/123", + "code": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Wed, 15 Mar 2023 12:34:56 GMT"] + }, + "body": "{\"id\":\"123\",\"name\":\"Example Resource\"}" + }, + { + "relative_url": "resource/create", + "code": 201, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Wed, 15 Mar 2023 12:34:56 GMT"] + }, + "body": "{\"id\":\"456\",\"name\":\"New Resource\",\"status\":\"created\"}" + }, + { + "relative_url": "resource/invalid", + "code": 404, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Wed, 15 Mar 2023 12:34:56 GMT"] + }, + "body": "{\"error\":\"Resource not found\"}" + } +] +``` + +Each response object contains: + +- `relative_url`: The URL of the endpoint targeted by the request +- `code`: The HTTP status code returned from the individual request +- `headers`: The response headers +- `body`: The response body as a string + +### Response Status Codes + +The batch endpoint itself returns an `HTTP 200 OK` status code as long as the batch request was properly formatted and processed, regardless of whether individual requests within the batch succeeded or failed. + +To determine the success or failure of individual requests, you need to examine the status code for each request in the response array. + +In the previous example, we can see that the first two requests were successful, returning `HTTP 200 OK` and `HTTP 201 Created`, whereas the third failed returning `HTTP 404 Not found`. + +## Invoking Batch Requests from Custom JavaScript Middleware + +You can make requests to the logical batch request endpoint from within [custom JavaScript middleware](/api-management/plugins/javascript) via the `TykBatchRequest` function that is included in Tyk's [JavaScript API](/api-management/plugins/javascript#javascript-api). + +This integration enables you to: + +- Create batch requests programmatically +- Process batch responses with custom logic +- Implement advanced error handling specific to your use case + +## Security Considerations + +Requests to the `/tyk/batch/` endpoint do not require any authentication, however the requests within the batch (declared in the payload) do not bypass any security mechanisms. + +As this endpoint is keyless, no rate limiting is applied to the requests to `/tyk/batch/`. + +Each request in a batch is processed through Tyk's full security pipeline, including authentication and rate limiting, so API keys or other authentication credentials must be included in each individual request within the batch. + +Rate limiting and quotas are applied to each request in the batch individually - so a batch containing three requests using the same API key will add three to their rate limit and quota counts. This could lead to one or more of the batched requests being rejected. + +This means that, whilst anyone can make a request to the batch endpoint, they can only successfully execute requests within the batch by providing valid authentication credentials in those requests. + +This means that the batch endpoint could potentially be used for reconnaissance, as attackers might determine which APIs exist based on responses. If this is a concern then you could consider: + +- using IP allowlists to restrict access to your API +- using [Internal Routing](/advanced-configuration/transform-traffic/looping) to put the batch request API behind a protected API +- disabling batch requests entirely if you don't need this feature + +## Performance Considerations + +- Setting `suppress_parallel_execution` to `false` provides better performance but doesn't guarantee response order. +- For large batches, consider the impact on your upstream services +- Tyk applies rate limiting to each request in the batch, which may cause some requests to be rejected if limits are exceeded + +## Best Practices when using Tyk's Batch Request feature + +We recommend that you consider the following best practice guidelines when using batch requests: + +- Validate Before Sending: Perform client-side validation before including requests in a batch to minimize predictable errors. +- Implement Timeouts: Set appropriate timeouts for batch requests to prevent long-running operations from blocking your application. +- Log Detailed Errors: Log detailed error information for failed requests to facilitate debugging. +- Group Similar Requests: Group requests with similar authentication requirements and rate limits to minimize errors. +- Implement Circuit Breakers: Use circuit breaker patterns to prevent repeated failures when upstream services are experiencing issues. + +## Troubleshooting + +There are some common issues that can be encountered when using Tyk's batch requests feature. + +### Missed trailing slash + +When an API client makes a request to the logical `tyk/batch/` endpoint, it is essential that the trailing slash is included in the request, otherwise Tyk will return an `HTTP 404` error. + +### Custom domains + +Several specific issues can arise when using batch requests with custom domains: + +**DNS Resolution**: The Tyk Gateway needs to be able to resolve the custom domain internally. If the Gateway can't resolve the custom domain name, batch requests will fail with connection errors, even though external requests to the same API work fine. +**Solution**: Ensure that the Tyk Gateway host can resolve the custom domain, either through proper DNS configuration or by adding entries to the host's `/etc/hosts` file. + +**Internal vs. External Routing**: When a batch request is made to a custom domain, Tyk needs to route the individual requests within the batch correctly. If the custom domain is only configured for external access but not for internal routing, the batch requests may fail. +**Solution**: Configure your custom domain to work with both external and internal routing. + +**Certificate Validation**: If your custom domain uses HTTPS, certificate validation issues can occur during the internal processing of batch requests. +**Solution**: Ensure that the certificates for your custom domain are properly configured and trusted by the Tyk Gateway. \ No newline at end of file diff --git a/api-management/certificates.mdx b/api-management/certificates.mdx new file mode 100644 index 0000000000..3b0582ba25 --- /dev/null +++ b/api-management/certificates.mdx @@ -0,0 +1,286 @@ +--- +title: "TLS and Certificate Management" +description: "Transport Layer Security and Tyk Certificate Store" +keywords: "Transport Layer Security, TLS, SSL, mTLS, mutual TLS, Security, Certificate, Pinning, Certificate Authority, Tyk Certificate Store" +order: 2 +sidebarTitle: "TLS and Certificate Management" +--- + +## Introduction + +Modern web applications require secure communication to protect sensitive data from eavesdropping and tampering. Transport Layer Security (TLS) is the foundation of secure internet communication, and Tyk provides comprehensive TLS support for both client-facing APIs and backend service communication. + +## Understanding Secure Communication + +Insecure and Secure Communication + +### The Problem: Insecure Communication + +Without encryption, network communication faces several risks: + +- **Eavesdropping**: Anyone can read your data as it travels across networks. +- **Tampering**: Attackers can modify data in transit without detection. +- **Impersonation**: Malicious actors can pretend to be legitimate servers or clients. + +### The Solution: Transport Layer Security (TLS) + +**TLS (Transport Layer Security)** is a cryptographic protocol that solves these problems by providing: + +- **Confidentiality**: Encrypts data so only intended recipients can read it. +- **Integrity**: Ensures data hasn't been modified during transmission. +- **Authentication**: Verifies the identity of communicating parties. + +TLS is the "S" in HTTPS and secures billions of web transactions daily. + +#### TLS or SSL + +SSL (Secure Sockets Layer) is the older version of TLS, which is now the standard for secure web communication. TLS uses stronger encryption and better key management. All SSL and earlier TLS versions (1.0, 1.1) are deprecated. Today, "SSL" usually refers to TLS, especially versions 1.2 and 1.3, which are secure. + +## Fundamentals of Public Key Cryptography + +Transport Layer Security uses public key cryptography. This is an asymmetric method in which each communicating party possesses a unique key pair: a private key for decryption and a public key for encryption, enabling secure communication. + +### Encryption Keys + +A key is a specific value used within a cryptographic algorithm to encrypt data into an unreadable format and decrypt it back to its original state with the correct key using a defined algorithm. + +With public key cryptography, there are two different keys: +- the *private key* is held by the originator of the message and is kept private +- the *public key* is shared with the recipient of the message + +A key exchange algorithm, such as RSA, uses the public-private key pair to agree upon session keys, which are used for symmetric encryption once the handshake is complete. + +{/* [DIAGRAM: Public Key Encryption] Visual representation of asymmetric encryption with Party A using Party B's public key to encrypt, Party B using their private key to decrypt. Visual flow arrows showing the data encryption/decryption process */} + +### Digital Certificates + +X.509 Digital Certificate + +The public key is usually shared in the form of an [X.509](https://datatracker.ietf.org/doc/html/rfc5280) *digital certificate*. This is like a digital passport or driver's license. It's an electronic document that proves identity and contains the elements shown above. The certificate is itself *signed* using the private key owned by an entity that is trusted by both parties, known as a *Certificate Authority*. + + + Various signature algorithms are available for signing certificates. The chosen algorithm can affect performance, as some are easier for Tyk to verify than others. Generally, elliptic curve algorithms offer better performance on average, but actual results may vary depending on CPU architecture. It is recommended to test and determine the most suitable balance between security and performance for specific needs. + + +### Certificate Authorities (CAs) + +Certificate Authorities and Trust Store + +Certificate Authorities are organizations trusted to issue digital certificates. Their role is analogous to that of government agencies that issue official identification documents. + +Popular Certificate Authorities include: +- Let's Encrypt: Free, automated certificates +- DigiCert: Enterprise-grade certificates +- GlobalSign: International CA with global presence +- Internal CAs: Organizations can run their own CAs for internal use (self-signed certificates) + +### Certificate Chains and Trust + +Certificate Trust Chain + +The recipient checks the certificate by following a chain of trust up to a recognized root CA, verifying each signature along the way. + + +## Fundamentals of Transport Layer Security + +### Standard TLS: Server Authentication + +TLS Handshake + +In typical HTTPS connections, only the **server** proves its identity through this one-way authentication process. + +### Mutual TLS (mTLS): Two-Way Authentication + +Mutual TLS Handshake + +Mutual TLS (mTLS) extends TLS with two-way authentication, meaning both sides verify their identities by exchanging certificates. + +This provides stronger security because: + +- The server knows that the client is who they claim to be. +- The client knows that the server is legitimate. +- Both parties are cryptographically verified. + + +## Certificate Management + +Wherever Tyk accepts a certificate, the value is a **certificate reference** which can take one of three forms: + +- The recommended approach for most deployments is the [Tyk Certificate Store](#tyk-certificate-store), which centralizes certificate management and is available in both licensed and OSS installations. +- [Inline PEM content](#inline-pem-content) (from **Tyk Gateway 5.14.0**) is the preferred approach for Kubernetes deployments where certificates are managed externally. +- A legacy [local file path](#local-file-path) approach is also supported for backwards compatibility. + +Any of these forms can be used in the certificate fields in the API definition; the inline PEM content form is not supported in the `cert_file, key_file, ca_file` fields in the [External Services mTSL Config](/configure/external-service#mutual-tls-mtls-configuration) in the Gateway config file (`tyk.conf`). + +### Tyk Certificate Store + +The Tyk Certificate Store centralizes certificates within Tyk’s data layer for use by multiple APIs. + +#### Licensed Deployments + +In licensed Tyk installations, the Tyk Dashboard manages certificates. Certificates are stored in the Control Plane Redis and synced to the Data Plane Redis for Tyk Gateway to consume. + +#### Tyk OSS Deployments + +In a Tyk OSS installation, there is no Tyk Dashboard or Control Plane. Tyk Gateway manages the certificates directly, storing them in Redis. Tyk Gateway also exposes a Certificate Management API so that OSS users can manage certificates without a Tyk Dashboard. + +#### Certificate ID and Redis Key + +When a certificate is added, it gets a unique **certificate ID**, used to refer to it, such as when linking to an API. This ID combines the `orgID` and the certificate’s SHA-256 hash. In Redis, it is stored under `cert-raw-` plus the certificate ID. + +This means that certificate IDs are predictable and can be calculated if you know the Organisation ID and have the certificate file. You can generate the SHA256 fingerprint using the following command: + +``` +openssl x509 -noout -fingerprint -sha256 -inform pem -in +``` + +Tyk Certificate ID + + + Notice that you can’t retrieve the raw certificate from just the certificate ID and that this is a unique identifier. + + +#### Certificate Data + +The data stored against a certificateID is the [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) certificate chain data, comprising: + +1. The Certificate(s): + - The primary X.509 certificate + - Any intermediate certificates in the chain +2. Encrypted Private Key (if provided): + - If a private key was included with the certificate, it’s encrypted using AES-256 + - The encryption uses the `private_certificate_encoding_secret` from your Tyk configuration + - The encrypted key is appended to the certificate chain with the header `ENCRYPTED PRIVATE KEY` + +A typical certificate with a private key would be stored as: +``` +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUJhdB... +... (certificate data) ... +-----END CERTIFICATE----- + +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0w... +... (encrypted private key data) ... +-----END ENCRYPTED PRIVATE KEY----- +``` + +##### Encryption of the Private Key + +If the private key was provided in the imported PEM file, it will be encrypted using the AES-256 algorithm with an encryption key that you must set in the Gateway, Dashboard, and MDCB configurations (if used). + +- **Gateway**: [`TYK_GW_SECURITY_PRIVATECERTIFICATEENCODINGSECRET`](/tyk-oss-gateway/configuration#security-private_certificate_encoding_secret) +- **Dashboard**: [`TYK_DB_SECURITY_PRIVATECERTIFICATEENCODINGSECRET`](/tyk-dashboard/configuration#security-private_certificate_encoding_secret) +- **MDCB**: [`TYK_MDCB_SECURITY_PRIVATECERTIFICATEENCODINGSECRET`](/tyk-multi-data-centre/mdcb-configuration-options#security-private_certificate_encoding_secret) + +It is essential that these secrets match so that the Gateway can decrypt the key when required during a TLS handshake. + + + It is important to keep the shared secret secure. + + +**Standalone Public Key** + +Stand-alone public keys (not contained within a certificate) are sometimes used for upstream certificate pinning or JWT verification. + +If only the public key were provided, rather than the certificate containing that key, then just the PEM-encoded public key would be stored against the certificateID, for example: + +``` +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... +-----END PUBLIC KEY----- +``` + +#### Monitoring Certificate Expiry + +X.509 certificates have a validity period with both start and end timestamps outside which they will not be accepted in the TLS handshake. + +Tyk Gateway checks the validity of each certificate when it is used in an API request and will generate both an error log and a [Gateway event](/api-management/gateway-events#certificate-expiry-events) if an attempt is made to use an expired certificate (after its `notAfter` timestamp). + +An optional warning can be configured to generate a log and Gateway event if a certificate is used within a configurable window before the `notAfter` timestamp. + +- `CertificateExpired`: an expired certificate has been used in a request +- `CertificateExpiringSoon`: a certificate has been used within the expiry threshold window + +The behavior of this Certificate Expiry Monitor is configured at the Gateway level using the following fields in the [`security.certificate_expiry_monitor`](/tyk-oss-gateway/configuration#security-certificate_expiry_monitor) section of the Gateway configuration (or equivalent environment variables): + +| Field | Effect | +|-------|--------| +| `warning_threshold_days` | The number of days before certificate expiry that the Gateway will start generating CertificateExpiringSoon events when the certificate is used | +| `check_cooldown_seconds` | A cool-off period after checking a certificate's expiry before another check is made, to avoid unnecessary log generation when an API is called repeatedly | +| `event_cooldown_seconds` | A cool-off period after generating `CertificateExpired` or `CertificateExpiringSoon` for a certificate, to avoid unnecessary event generation when an API is called repeatedly | + +Note that the certificate expiry monitor is reactive and only checks for certificate expiry when the certificate is used; it will not flag impending expiry or expired certificates if they are not actively used. + +[Event handlers](/api-management/gateway-events#handling-events-with-tyk) can be configured at the API level to respond to Gateway event generation. + +#### Tyk Certificate Store API + +The Tyk Dashboard API exposes endpoints to manage the Tyk Certificate Store, allowing you to: + +- [Register](https://tyk.io/docs/api-reference/certificates/create-a-certificate-in-tyk-org-cert-store) a new certificate with the store + - Provide either a PEM-encoded certificate (optionally concatenated with PEM-encoded private key) or a PEM-encoded public key. + - This returns the assigned certificate ID. +- [Retrieve](https://tyk.io/docs/api-reference/certificates/get-single-certificate-with-id) details of a specific entry in the store + - Provide a certificate ID. + - This returns metadata including the certificate issuer, whether there is a private key, and the certificate fingerprint (SHA256 hash of the certificate). +- [Retrieve](https://tyk.io/docs/api-reference/certificates/list-certificates) a list of all certificates in the store +- [Delete](https://tyk.io/docs/api-reference/certificates/delete-certificate) a certificate from the store +- [Retrieve](https://tyk.io/docs/api-reference/certificates/list-apis-lined-to-a-certificate) a list of APIs that are using a certificate + +There is a similar API exposed by [Tyk Gateway](https://tyk.io/docs/api-reference/certs/list-certificates), for use in Tyk OSS installations to manage the certificates stored in Redis. This does not include the advanced functionality for identifying the APIs that use each certificate. + +#### Managing Certificates Using the Dashboard UI + +The Tyk Dashboard provides a visual interface to the Tyk Certificate Store via the **API Security > TLS/SSL Certificates** screen. + +Dashboard listing content of Tyk Certificate Store + +This screen displays a list of certificates registered in the store; any for which the [certificate expiry monitor](/api-management/certificates#monitoring-certificate-expiry) has generated an event will be highlighted with a warning or error symbol; a banner will be displayed to highlight these symbols. + +Click on the certificate ID or select **View** from the **Actions** menu to view details of the certificate, including a list of APIs currently using the certificate. + +Dashboard showing details of a certificate in the store + +You can click on the name of an API in this list to go directly to that API in the API Designer. Note that this will list all APIs, even those for which the logged in user does not have [access](/platform-management/api-ownership). This is to mitigate against the scenario where a certificate is deleted because the user believes it is unused. Users are unable to click through to APIs to which they do not have access. + +You can upload a new certificate to the Tyk Certificate Store using the **+ Add Certificate** button. + +Dashboard showing the certificate upload screen + +Select either a valid PEM-encoded file containing a certificate, or a file containing PEM-encoded certificate concatenated with the PEM-encoded private key, and select **Upload**. If successful, the certificate will be loaded into the Certificate Store and assigned a certificate ID that you can use to associate it with a TLS interaction via API definition or Gateway configuration. + +### Inline PEM Content + +From **Tyk Gateway 5.14.0**, certificate fields in API definitions accept inline PEM-encoded content directly, in addition to a certificate ID or file path. The PEM content follows the same format as the Tyk Certificate Store: a certificate chain optionally followed by a PEM-encoded private key. + +The primary use case is supplying certificates from a secret management system without storing them in the Control Plane. Any supported [KV reference](/tyk-configuration-reference/kv-store) can supply the PEM content at load time, for example, a `file://` reference can read a certificate mounted from a Kubernetes secret volume without the Control Plane ever storing the private key. See [Using Local Files as a KV Store](/tyk-configuration-reference/kv-store#local-files) for configuration details. + +For example: + +```yaml +server: + clientCertificates: + enabled: true + allowlist: + # Inline PEM content — suitable for testing; avoid embedding private keys directly + - | + -----BEGIN CERTIFICATE----- + MIIDazCCAlOgAwIBAgIUJhdB... + -----END CERTIFICATE----- + # file:// KV reference — recommended for Kubernetes deployments + - "file:///etc/tyk/certs/client-ca.pem" +``` + + +Embedding a private key directly in an API definition is a security risk. The intended pattern is to supply PEM content via a KV reference, such as a `file://` reference pointing to a Kubernetes secret volume, so the private key never appears in the API definition itself. + + +### Local File Path + +A legacy approach allows certificate fields to contain the path to a `.pem` file on the local filesystem. Tyk Gateway reads the certificate from that path when the API is loaded. The Gateway process must have read access to the file. + +This is distinct from a `file://` KV reference (see [Inline PEM Content](#inline-pem-content)): a local file path is used directly as the certificate's location on disk, whereas a `file://` reference is resolved to the file's contents and treated as inline PEM content. + +This approach is still supported but is not recommended for new deployments. It couples the Gateway to the local filesystem and does not benefit from the centralized management or access controls provided by the Tyk Certificate Store. + diff --git a/api-management/client-authentication.mdx b/api-management/client-authentication.mdx new file mode 100644 index 0000000000..f33410f06c --- /dev/null +++ b/api-management/client-authentication.mdx @@ -0,0 +1,374 @@ +--- +title: "Client Authentication and Authorization" +description: "Learn how to apply the most appropriate authentication method to secure access your APIs with Tyk. Here you will find everything there is to know about authenticating and authorizing API clients with Tyk." +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Secure APIs, client" +sidebarTitle: "Overview" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; + +## Introduction + +Tyk Gateway sits between your clients and your services, securely routing requests and responses. For each API proxy that you expose on Tyk, you can configure a range of different methods that clients must use to identify (authenticate) themselves to Tyk Gateway when making a request to access the API. + +*Authentication* and *Authorization* are the processes that you use to control access to your APIs and protect your upstream services. Each serves a distinct purpose: + +* **Authentication** (or **AuthN**) is the process of confirming the identity of the user or system making the API request. This step validates "who" is attempting to access the API, commonly using credentials such as tokens, passwords, or certificates. + +* **Authorization** (or **AuthZ**) is the process that determines if the user or system has the right permissions to perform the requested action. This step defines "what" they are allowed to do based on assigned roles, scopes, or policies. + +Whilst AuthN and AuthZ are separate actions with different standards, they are often considered together under the topic of *Securing the API*. Together, these processes allow API providers to control access, safeguard data integrity, and meet security and compliance standards, making them vital for any API management strategy. + +--- + +## How does Tyk Implement Authentication and Authorization? + +The API request processing flow within Tyk Gateway consists of a [chain of middleware](/api-management/traffic-transformation#request-middleware-chain) that perform different checks and transformations on the request (headers, parameters and payload). Several dedicated **authentication middleware** are provided and there is also support for user-provided **custom authentication plugins**. Multiple authentication middleware can be chained together if required by the API's access security needs. *Note that it is not possible to set the order of chained auth methods.* + +The OpenAPI description can contain a list of [securitySchemes](https://spec.openapis.org/oas/v3.0.3.html#security-scheme-object) which define the authentication methods to be used for the API; the detailed configuration of the Tyk authentication middleware is set in the [server.authentication](/api-management/gateway-config-tyk-oas#authentication) section of the Tyk Vendor Extension. + +You must enable client authentication using the `server.authentication.enabled` flag and then configure the appropriate authentication method as indicated in the relevant section of this document. When creating a Tyk OAS API from an OpenAPI description, Tyk can automatically enable authentication based upon the content of the OpenAPI description as described [here](/api-management/gateway-config-managing-oas#importing-an-openapi-description-to-create-an-api). + +When using Tyk Classic APIs, each authentication middleware has its own fields within the API definition + +### Managing authorization data + +The data that the client provides with the API request used to authenticate with Tyk and confirm that it is authorized to access the API is often of no use to the upstream service and, depending on your security governance, may even be prohibited from being made available to the upstream. + +Tyk offers a simple option, separately configurable for each API to remove, or "strip", the authentication/authorization date from the incoming request before proxying to the upstream. + +This is controlled using the [server.authentication.stripAuthorizationData](/api-management/gateway-config-tyk-oas#authentication) field in the Tyk Vendor Extension (Tyk Classic: `strip_auth_data`). + +## What does Tyk Support? + +Tyk includes support for various industry-standard methods to secure your APIs. This page provides an overview of the options available, helping you to choose and implement what works best for you. + +Use Ctrl+F or the sidebar to find specific topics, for example “JWT” for JSON Web Tokens or “mTLS” for mutual TLS. + +You can also use the links below to jump directly to the appropriate sections to learn how to secure your APIs using Tyk. + + + + +Use Tyk Gateway as a built-in OAuth 2.0 authorization server to issue and manage access tokens. + + + +Validate tokens issued by an external OAuth 2.0 / OIDC provider, with scope enforcement and Protected Resource Metadata. + + + +Securely transmit information between parties. + + + +Secure APIs with username and password credentials. + + + +Implement token-based authentication for API access. + + + +Establish secure channels with two-way certificate verification. + + + +Verify message integrity using shared secret keys. + + +{/* To be added + +Verify message integrity using shared secret certificates. + */} + + +Create custom plugins to implement specific authentication requirements. + + + +Allow unrestricted access for public APIs. + + + + + +--- + +## Other Authentication Methods + +### Integrate with External Authorization Server (deprecated) + + + +Tyk has previously offered two types of OAuth authentication flow; [Tyk as the authorization server]() and Tyk connecting to an external *auth server* via a dedicated *External OAuth* option. The dedicated external *auth server* option was deprecated in Tyk 5.7.0. +
+ +From Tyk 5.14.0, the recommended replacement for third-party OAuth integration is the [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication) scheme, which provides token validation, scope enforcement, Protected Resource Metadata, and token exchange in a single declaration. If you are on an earlier version, use the [JSON Web Token (JWT)](/basic-config-and-security/security/authentication-authorization/json-web-tokens) middleware as an interim replacement. +
+ +The remainder of this section is left for reference and is not maintained. +
+ + +To call an API that is protected by OAuth, you need to have an access token from the third party IDP (it could be an opaque token or a JWT). + +For subsequent calls the access token is provided alongside the API call and needs to be validated. With JWT, Tyk can confirm the validity of the JWT with the secret provided in your config. The secret signs the JWT when created and confirms that none of its contents has changed. + +For this reason, information like the expiry date which are often set within the JWT cannot be changed after the JWT has been initially created and signed. This means you are not able to revoke a token before the expiry set in the JWT with the standard JWT flow. With OAuth you can use [OAuth introspection](https://www.rfc-editor.org/rfc/rfc7662) to overcome this. With introspection, you can validate the access token via an introspection endpoint that validates the token. + +Let’s see how external OAuth middleware is configured. + +#### OAS contract + +```yaml +externalOAuthServer: + enabled: true, + providers: # only one item in the array for now (we're going to support just one IDP config in the first iteration) + - jwt: #validate JWTs generated by 3rd party Oauth servers (like Okta) + enabled: true + signingMethod: HMAC/RSA/ECDSA # to verify signing method used in jwt + source: key # secret to verify signature + issuedAtValidationSkew: 0 + notBeforeValidationSkew: 0 + expiresAtValidationSkew: 0 + identityBaseField: # identity claimName + introspection: # array for introspection details + enabled: true/false + clientID: # for introspection request + clientSecret: # for introspection request, if empty will use oAuth.secret + url: # token introspection endpoint + cache: # Tyk will cache the introspection response when `cache.enabled` is set to `true` + enabled: true/false, + timeout: 0 # The duration (in seconds) for which Tyk will retain the introspection outcome in its cache. If the value is "0", it indicates that the introspection outcome will be stored in the cache until the token's expiration. + identityBaseField: # identity claimName +``` + +#### Tyk Classic API definition contract + +```yaml +"external_oauth": { + "enabled": true, + "providers": [ + { + "jwt": { + "enabled": false, + "signing_method": rsa/ecdsa/hmac, + "source": # jwk url/ base64 encoded static secret / base64 encoded jwk url + "identity_base_field": # identity claim name + "expires_at_validation_skew": # validation skew config for exp + "not_before_validation_skew": # validation skew config for nbf + "issued_at_validation_skew" : # validation skew config for iat + }, + "introspection": { + "enabled": true, + "url": # introspection endpoint url + "client_id": # client Id used for introspection + "client_secret": # client secret to be filled here (plain text for now, TODO: decide on a more secure mechanism) + "identity_base_field": # identity claim name + "cache": { + "enabled": true, + "timeout": # timeout in seconds + } + } + } + ] +} +``` +- `externalOAuthServer` set `enabled` to `true` to enable the middleware. +- `providers` is an array of multiple IDP configurations, with each IDP config being an element in the `providers` array. +- You can use this config to use JWT self validation using `jwt` or use introspection via `instropection` in the `providers` section . + + + + + For now, you’ll be limiting `providers` to have only one element, ie one IDP configured. + + + +#### JWT + +There could be cases when you don’t need to introspect a JWT access token from a third party IDP, and instead you can just validate the JWT. This is similar to existing JWT middleware, adding it in External OAuth middleware for semantic reasons. + +- `enabled` - enables JWT validation. +- `signingMethod` - specifies the signing method used to sign the JWT. +- `source` - the secret source, it can be one of: + - a base64 encoded static secret + - a valid JWK url in plain text + - a valid JWK url in base64 encoded format +- `issuedAtValidationSkew` , `notBeforeValidationSkew`, `expiresAtValidationSkew` can be used to [configure clock skew](/api-management/authentication/jwt-claim-validation#clock-skew-configuration) for json web token validation. +- `identityBaseField` - the identity key name for claims. If empty it will default to `sub`. + +##### Example: Tyk OAS API definition with JWT validation enabled + +```json +"securitySchemes": { + "external_jwt": { + "enabled": true, + "header": { + "enabled": true, + "name": "Authorization" + }, + "providers": [ + { + "jwt": { + "enabled": true, + "signingMethod": "hmac", + "source": "dHlrLTEyMw==", + "identityBaseField": "sub" + } + } + ] + } +} +``` + +##### Example: Tyk Classic API definition with JWT validation enabled + +```json +"external_oauth": { + "enabled": true, + "providers": [ + { + "jwt": { + "enabled": true, + "signing_method": "hmac", + "source": "dHlrLTEyMw==", + "issued_at_validation_skew": 0, + "not_before_validation_skew": 0, + "expires_at_validation_skew": 0, + "identity_base_field": "sub" + }, + "introspection": { + "enabled": false, + "url": "", + "client_id": "", + "client_secret": "", + "identity_base_field": "", + "cache": { + "enabled": false, + "timeout": 0 + } + } + } + ] +} +``` +#### Introspection + +For cases where you need to introspect the OAuth access token, Tyk uses the information in the `provider.introspection` section of the contract. This makes a network call to the configured introspection endpoint with the provided `clientID` and `clientSecret` to introspect the access token. + +- `enabled` - enables OAuth introspection +- `clientID` - clientID used for OAuth introspection, available from IDP +- `clientSecret` - secret used to authenticate introspection call, available from IDP +- `url` - endpoint URL to make the introspection call +- `identityBaseField` - the identity key name for claims. If empty it will default to `sub`. + +##### Caching + +Introspection via a third party IdP is a network call. Sometimes it may be inefficient to call the introspection endpoint every time an API is called. Caching is the solution for this situation. Tyk caches the introspection response when `enabled` is set to `true` inside the `cache` configuration of `introspection`. Then it retrieves the value from the cache until the `timeout` value finishes. However, there is a trade-off here. When the timeout is long, it may result in accessing the upstream with a revoked access token. When it is short, the cache is not used as much resulting in more network calls. + +The recommended way to handle this balance is to never set the `timeout` value beyond the expiration time of the token, which would have been returned in the `exp` parameter of the introspection response. + +See the example introspection cache configuration: + +```yaml +"introspection": { + ... + "cache": { + "enabled": true, + "timeout": 60 // in seconds + } +} +``` +##### Example: Tyk OAS API definition external OAuth introspection enabled + +```json +"securitySchemes": { + "keycloak_oauth": { + "enabled": true, + "header": { + "enabled": true, + "name": "Authorization" + }, + "providers": [ + { + "introspection": { + "enabled": true, + "url": "http://localhost:8080/realms/tyk/protocol/openid-connect/token/introspect", + "clientId": "introspection-client", + "clientSecret": "DKyFN0WXu7IXWzR05QZOnnSnK8uAAZ3U", + "identityBaseField": "sub", + "cache": { + "enabled": true, + "timeout": 3 + } + } + } + ] + } +} +``` +##### Example: Tyk Classic API definition with external OAuth introspection enabled + +```json +"external_oauth": { + "enabled": true, + "providers": [ + { + "jwt": { + "enabled": false, + "signing_method": "", + "source": "", + "issued_at_validation_skew": 0, + "not_before_validation_skew": 0, + "expires_at_validation_skew": 0, + "identity_base_field": "" + }, + "introspection": { + "enabled": true, + "url": "http://localhost:8080/realms/tyk/protocol/openid-connect/token/introspect", + "client_id": "introspection-client", + "client_secret": "DKyFN0WXu7IXWzR05QZOnnSnK8uAAZ3U", + "identity_base_field": "sub", + "cache": { + "enabled": true, + "timeout": 3 + } + } + } + ] +} +``` + +### Integrate with OpenID Connect (deprecated) + + + +Tyk has previously offered a dedicated OpenID Connect option for client authentication, but this was not straightforward to use and was deprecated in Tyk 5.7.0. +
+ +For integration with a third-party OIDC provider we recommend using the JSON Web Token (JWT) middleware which is described [above](/basic-config-and-security/security/authentication-authorization/json-web-tokens), which offers the same functionality with a more streamlined setup and reduced risk of misconfiguration. +
+ +The remainder of this section is left for reference and is not maintained. +
+ + + +[OpenID Connect](https://openid.net/developers/how-connect-works) (OIDC) builds on top of OAuth 2.0, adding authentication. You can secure your APIs on Tyk by integrating with any standards compliant OIDC provider using [JSON Web Tokens](/basic-config-and-security/security/authentication-authorization/json-web-tokens) (JWTs). +JWTs offer a simple way to use the third-party Identity Provider (IdP) without needing any direct integration between the Tyk and 3rd-party systems. + +To integrate a 3rd party OAuth2/OIDC IdP with Tyk, all you will need to do is ensure that your IdP can issue OAuth2 JWT access tokens as opposed to opaque tokens. + +The client application authenticates with the IdP which then provides an access token that is accepted by Tyk. Tyk will take care of the rest, ensuring that the rate limits and quotas of the underlying identity of the bearer are maintained across JWT token re-issues, so long as the "sub" (or whichever identity claim you chose to use) is available and consistent throughout and the policy that underpins the security clearance of the token exists too. + + + +## Conclusion + +Securing your APIs is a foundational step toward managing data integrity and access control effectively. Now that you've configured authentication and authorization, the next steps in your API journey with Tyk should involve: + +Defining Access Policies: Use Tyk’s Policies to refine API access controls, rate limits, and quotas. This lets you align your security model with business needs and enhance user experience through granular permissions. You can learn more about Policies [here](/api-management/policies). + +Exploring API Analytics: Leverage Tyk’s analytics to monitor access patterns, track usage, and gain insights into potential security risks or high-demand endpoints. Understanding usage data can help in optimizing API performance and enhancing security measures. You can learn more about analytics [here](/api-management/dashboard-analytics#analyzing-api-traffic-activity). \ No newline at end of file diff --git a/api-management/client-idp-registry.mdx b/api-management/client-idp-registry.mdx new file mode 100644 index 0000000000..6ebf77877e --- /dev/null +++ b/api-management/client-idp-registry.mdx @@ -0,0 +1,115 @@ +--- +title: "Identity Provider Registry" +description: "Manage Identity Providers centrally with the Tyk Identity Provider Registry for JWT authentication and Dynamic Client Registration" +keywords: "IdP, Identity Provider, JWKS, JWT, Dynamic Client Registration, DCR, scope-to-policy, identity provider registry" +sidebarTitle: "Identity Provider Registry" +--- + +## Availability + +| Component | Version | Edition | +| :-------- | :------ | :------ | +| Tyk Gateway | Available since [v5.14.0](/developer-support/release-notes/gateway#5-14-0-release-notes) | Enterprise | +| Tyk Dashboard | Available since [v5.14.0](/developer-support/release-notes/dashboard#5-14-0-release-notes) | Enterprise | +| Tyk Sync | Available since [v2.2.0](/developer-support/release-notes/sync#2-2-0-release-notes) | Enterprise | + +## Introduction + +When using JWT authentication, Tyk needs to know which Identity Providers (IdPs) are trusted to issue tokens for an API. Specifically, it needs to know where to fetch the JWKS to validate signatures, and how to map token scopes to Tyk policies. Traditionally this configuration was stored directly inside the API definition. + +Embedding IdP config in the API definition causes problems at scale: + +- The Tyk Dashboard and Tyk Developer Portal can overwrite each other's changes when both write to the same API definition concurrently. +- Deleting an [API Product using Dynamic Client Registration (DCR)](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration) from the Developer Portal does not reliably clean up the IdP config it added, leaving orphaned JWKS URLs and scope mappings. +- Changes to an IdP's configuration must be separately applied to all APIs accessed with tokens issued by that provider. + +The Identity Provider Registry solves these problems by managing IdP records as a standalone resource in the Tyk Dashboard, separate from API definitions. Tyk Gateway loads the registry at startup alongside API definitions and applies it during JWT validation. + + +The Identity Provider Registry manages IdPs used to authenticate *clients accessing your APIs* (inbound JWT validation). It is not related to the configuration of upstream services that the Gateway authenticates to, which is covered in [Upstream Authentication](/api-management/upstream-authentication). + + +## What Is the Identity Provider Registry + +The Identity Provider Registry is a collection of IdP records managed by the Tyk Dashboard. Each record describes a single Identity Provider and its relationship to one or more APIs: + +| Field | Description | +|-------|-------------| +| `name` | A human-readable label for the IdP | +| `issuer` | The issuer claim (`iss`) expected in tokens from this IdP | +| `jwks_uri` | The URL of the IdP's JSON Web Key Set endpoint, used to fetch public keys for JWT signature validation | +| `scope_claim_name` | The JWT claim that contains the token scopes, such as `scope` or `scp` | +| `api_mappings` | A map of API IDs to scope-to-policy mappings, defining which Tyk policies should be applied when a token from this IdP is presented to each API | + +Adopting the registry is a non-breaking change. Any JWKS or scope configuration already present in an API definition continues to take precedence over registry-sourced config, which means existing API definitions keep working correctly on Gateways that predate 5.14.0. APIs that have no registry entries are unaffected. The registry is supported by both Tyk OAS and Tyk Classic API definitions. + +The Gateway caches JWKS fetched via registry entries the same way as those configured directly in an API definition. + + +Registry JWKS URIs always use the gateway-level `jwks.cache.timeout` setting (defaulting to 240 seconds) and cannot currently be individually tuned per IdP. + + +In distributed deployments, the registry is served to Data Plane Gateways by MDCB alongside API definitions. No additional configuration is required. Registry entries reach the Gateways that load the APIs they are mapped to, and are automatically pruned to include only the relevant `api_mappings` for each Data Plane. + +## When to Use the Identity Provider Registry + +The registry is the recommended approach when: + +- The Tyk Developer Portal manages API Product access via [Dynamic Client Registration](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration), where multiple systems write IdP config for the same API. +- Multiple IdPs need to be trusted for a single API (the registry supports many-to-many relationships between IdPs and APIs). +- IdP lifecycle (creation, update, and deletion) needs to be decoupled from API definition management. + +If you configure a single JWKS URI directly in the API definition and do not use DCR, the registry is not required. + +## Managing IdPs + +The IdP registry is managed exclusively via the Tyk Dashboard API or the [Tyk Developer Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration). There is no Dashboard UI for the registry at this time. + +The Dashboard API exposes the following operations on the `/api/clientidps` endpoint: + +- **Create** an IdP record with a name, issuer, and JWKS URI. +- **Retrieve** a single IdP or list all IdPs for the organisation. +- **Update** an IdP record. +- **Delete** an IdP record, which removes all its API mappings. +- **Manage API mappings** in bulk using a `PATCH` request with JSON Merge Patch (RFC 7386) semantics: present keys upsert the mapping, null-valued keys remove it. + +Full endpoint reference and request/response schemas are available in the [Tyk Dashboard API reference](/tyk-dashboard-api). + +### API Mappings + +The `api_mappings` field is a map of API IDs to scope-to-policy configuration. Each entry defines how token scopes from this IdP are translated into Tyk policy IDs when the token is presented to that API. + +A single IdP can be mapped to multiple APIs, and a single API can have mappings from multiple IdPs. + +To add or update the mapping for a specific API without affecting other mappings on the same IdP, use the `PATCH /api/clientidps/{id}/mappings` endpoint with a JSON Merge Patch body: + +```json +{ + "": { + "scope_to_policy": { + "read": "policy-id-for-read", + "write": "policy-id-for-write" + } + } +} +``` + +To remove a mapping for a specific API, set its value to `null`: + +```json +{ + "": null +} +``` + +## Tyk Sync + +From **Tyk Sync 2.2.0** the full IdP lifecycle is supported alongside APIs and policies: + +- `tyk-sync dump` exports each IdP to a `clientidp-{id}.json` file and adds a `client_idps` array to `.tyk.json`. + - Use the `--idps` flag to export a specific subset. +- `tyk-sync publish` and `tyk-sync update` push IdP files to the registry in Tyk Dashboard. +- `tyk-sync sync` reconciles the full create, update, and delete lifecycle for IdPs. + +Referential integrity warnings are printed when an IdP's `api_mappings` references an API ID or policy ID not included in the current dump. + diff --git a/api-management/cloud/audit-logs.mdx b/api-management/cloud/audit-logs.mdx new file mode 100644 index 0000000000..23d0ada1d6 --- /dev/null +++ b/api-management/cloud/audit-logs.mdx @@ -0,0 +1,54 @@ +--- +title: "Configure Audit Logs in Tyk Cloud" +description: "Learn how to set up and manage audit logs in Tyk Cloud Control Plane deployments." +keywords: "Audit Logs, Tyk Cloud, Control Plane, Data Plane" +sidebarTitle: "Audit Logs" +--- + +## Introduction + +Tyk Cloud provides comprehensive audit logging capabilities to track and monitor all administrative actions performed within your Tyk Dashboard. This feature is essential for compliance and security. + +## What are Audit Logs? + +Audit logs capture detailed records of all requests made to endpoints under the `/api` route in your Tyk Dashboard. These logs include information about: + +- User actions and administrative operations +- API changes and configurations +- Authentication and authorisation events +- System access and modifications +- Response status codes and timestamps + +## Enabling Audit Logs for Control Plane Deployments + + + +The audit log feature is available for Control Plane versions v5.7.0 or later. + + + +### How to Enable Audit Logging + +1. **Contact Your Account Manager**: Audit logging must be enabled at the subscription level. Reach out to your Tyk account manager to add this feature to your plan. + +2. **Enable via Tyk Cloud UI**: Once the feature is available in your subscription, you can enable audit logging directly from the Tyk Cloud console: + - Navigate to your Control Plane deployment + - Select **Edit** from the deployment options + - Enable the **Audit Logging** option + - Save and redeploy your Control Plane + +Audit logs will be stored in your Control Plane's database for easy access and management. + +### Viewing and Accessing Audit Logs + +Once audit logging is enabled, you can retrieve the logs via the Tyk Dashboard API. + +For details on the API endpoints and usage, see [Viewing and Retrieving Audit Logs](/api-management/logs/audit-logs#viewing-and-retrieving-audit-logs). + +## Storage Size Caps + +Tyk Cloud enforces audit log storage size caps based on your subscription terms: + +- **Storage Limits**: A size cap is applied to audit logs based on your subscription plan +- **Automatic Cleanup**: When the storage limit is reached, the oldest logs are automatically removed to make space for new entries. + diff --git a/api-management/collecting-gateway-logs-otel-kubernetes.mdx b/api-management/collecting-gateway-logs-otel-kubernetes.mdx new file mode 100644 index 0000000000..f575ff22e3 --- /dev/null +++ b/api-management/collecting-gateway-logs-otel-kubernetes.mdx @@ -0,0 +1,205 @@ +--- +title: "Collecting Tyk Gateway Logs with OpenTelemetry Collector on Kubernetes" +description: "Step-by-step guide for platform engineers to collect Tyk Gateway logs using the OpenTelemetry Collector Filelog Receiver and ship them to Elasticsearch on Kubernetes." +keywords: "OpenTelemetry, Logs, Kubernetes, Filelog Receiver, Elasticsearch, OTel Collector, Observability, Log Collection, DaemonSet, Sidecar" +sidebarTitle: "Gateway Logs with OTel Collector" +--- + +## Introduction + +Tyk Gateway produces logs that capture internal events, errors, warnings, and details of request processing. In Kubernetes environments, these logs are written to `stdout`/`stderr` and captured by the container runtime, but they are ephemeral by default. Without a log collection strategy, critical operational data is lost when pods are restarted or evicted. + +The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) provides a vendor-neutral way to collect, process, and export logs from your Tyk Gateway pods to any supported backend. By using the [Filelog Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver), the Collector can tail container log files on each Kubernetes node and forward them to a log analytics backend such as Elasticsearch. + +This guide walks you through deploying the OpenTelemetry Collector alongside Tyk on Kubernetes, configuring it to collect Gateway logs, and shipping those logs to Elasticsearch. + +### Architecture Overview + +The following diagram illustrates how logs flow from Tyk Gateway containers through the OpenTelemetry Collector to Elasticsearch: + +```mermaid +flowchart LR + subgraph Kubernetes Node + A[Tyk Gateway Pod] -->|stdout/stderr| B[Container Runtime] + B -->|writes to| C["/var/log/containers/*.log"] + D[OTel Collector DaemonSet] -->|tails| C + end + D -->|exports logs via HTTP| E[Elasticsearch] + E --> F[Kibana Dashboard] +``` + +## Prerequisites + +Before getting started, ensure you have the following: + +- [Kubernetes](https://kubernetes.io/docs/setup/) with `kubectl` configured +- [Helm 3+](https://helm.sh/docs/intro/install/) +- [Elasticsearch Cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html) +- [Enterprise Edition License](/apim#licensing) +- Basic familiarity with [Otel Collector](https://opentelemetry.io/docs/collector/configuration/) concepts + +## Instructions Overview + +### 1. Install Tyk Stack on Kubernetes + +For installing Tyk on Kubernetes, follow the [Tyk Helm Charts installation guide](/tyk-self-managed/install/kubernetes#instructions). + + +When installing Tyk stack, add this flag `--set tyk-gateway.gateway.log.format=json` to configure the Gateway to output logs in JSON format. + + +You should see Tyk Gateway, Dashboard, and Pump pods running. + +``` +dashboard-tyk-tyk-dashboard-85bf686b86-xhhm9 1/1 Running 0 4h25m +gateway-tyk-tyk-gateway-6957669779-5tknn 1/1 Running 1 (4h24m ago) 4h25m +otel-collector-opentelemetry-collector-agent-tr8zf 1/1 Running 0 93m +pump-tyk-tyk-pump-5c9d94787f-vxdxs 1/1 Running 0 4h25m +tyk-postgres-postgresql-0 1/1 Running 0 4h28m +tyk-redis-master-0 1/1 Running 0 4h29m +tyk-redis-replicas-0 1/1 Running 0 4h29m +tyk-redis-replicas-1 1/1 Running 0 4h28m +tyk-redis-replicas-2 1/1 Running 0 4h28m +``` + +### 2. Deploy OpenTelemetry Collector + +Add the OpenTelemetry Helm repository and install the Collector as a DaemonSet: + +```bash +helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts +helm repo update +``` + +Create an `otel-collector-values.yaml` file. This configures the Collector in DaemonSet mode with the Filelog Receiver, and exports logs to Elasticsearch. + + +In the configuration below, replace the Elasticsearch host and password with your actual values. + + +```yaml Expandable +mode: daemonset + +image: + repository: otel/opentelemetry-collector-contrib + tag: latest + +presets: + logsCollection: + enabled: false + +extraVolumes: + - name: varlog + hostPath: + path: /var/log + - name: dockercontainers + hostPath: + # This is where the actual .log files usually reside + path: /var/lib/docker/containers + +extraVolumeMounts: + - name: varlog + mountPath: /var/log + readOnly: true + - name: dockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + +config: + receivers: + filelog: + # To target all tyk components, you use the following pattern "/var/log/pods/*/*/*.log" + include: + - /var/log/pods/*tyk-gateway*/*/*.log + start_at: end + include_file_path: true + operators: + - type: container + id: container-parser + + - type: json_parser + parse_from: body + parse_to: attributes + if: 'body matches "^\\{"' + + processors: + batch: {} + k8sattributes: + auth_type: "serviceAccount" + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.pod.uid + - k8s.namespace.name + - k8s.node.name + - k8s.container.name + labels: + - tag_name: $$1 + key_regex: (.*) + + exporters: + elasticsearch: + endpoints: ["http://:9200"] + logs_index: k8s-logs + mapping: + mode: none + user: elastic + password: + tls: + insecure_skip_verify: true + + service: + pipelines: + logs: + receivers: [filelog] + processors: [k8sattributes, batch] + exporters: [elasticsearch] +``` + +#### Pipeline Overview + +This **logs pipeline** reads container logs directly from the node filesystem using the `filelog` receiver, targeting Gateway pod log files. + +The `json_parser` operator parses JSON-formatted log entries, extracting structured fields from the log body into attributes for richer filtering and analysis. + +Each log entry is then enriched by the `k8sattributes` processor, which adds pod, namespace, node, container metadata, and all Kubernetes labels for better filtering and correlation. + +The `batch` processor groups logs efficiently to reduce export overhead. Finally, the processed logs are sent to Elasticsearch, where they are indexed under `k8s-logs` for centralized search and analysis. + + + + +The above configuration sends logs to the `k8s-logs` index in Elasticsearch. Before installing the collector, ensure the `k8s-logs` index is created in your Elasticsearch cluster. + +`curl -u elastic: -X PUT ":9200/k8s-logs" -H 'Content-Type: application/json' -d'{"settings": {"index": {}}}'` + + +Install the Collector: + +```bash +helm install otel-collector open-telemetry/opentelemetry-collector \ + -n tyk \ + -f otel-collector-values.yaml +``` + +Verify the Collector DaemonSet is running: + +```bash +kubectl get pods -n tyk -l app.kubernetes.io/name=opentelemetry-collector +``` + +You should see a pod running on each node. + +``` +NAME READY STATUS RESTARTS AGE +otel-collector-opentelemetry-collector-agent-tr8zf 1/1 Running 0 125m +``` + +### 3. Verify Logs in Elasticsearch + +To view logs in Elasticsearch, you can use Kibana to create a [data view](https://www.elastic.co/docs/explore-analyze/find-and-organize/data-views) for the `k8s-logs` index to visualize the logs. + +Make some test requests to your Tyk Gateway to generate logs, then check Kibana for incoming log entries. + +Gateway logs in Elasticsearch diff --git a/api-management/custom-auth-with-proxy-identity-provider.mdx b/api-management/custom-auth-with-proxy-identity-provider.mdx new file mode 100644 index 0000000000..dd4d0e8d12 --- /dev/null +++ b/api-management/custom-auth-with-proxy-identity-provider.mdx @@ -0,0 +1,237 @@ +--- +title: "SSO with Proxy Provider" +description: "Use Tyk Identity Broker's Proxy Provider to authenticate users against a custom or legacy HTTP endpoint." +keywords: "Tyk Identity Broker, TIB, SSO, Proxy Provider, Custom Authentication, Legacy Authentication, Basic Auth" +sidebarTitle: "Proxy Provider" +--- + +## Introduction + +The Proxy Provider (`ProxyProvider`) is a passthrough authentication method that forwards the user's request to a custom or legacy HTTP endpoint and evaluates the response to determine whether authentication succeeded. No browser redirect to an external IdP is involved. + +This is useful for integrating with systems that do not support standard protocols such as OIDC, SAML, or LDAP, for example a legacy authentication service that accepts Basic Auth and returns a JSON response. + +Before configuring your TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +## How It Works + +TIB proxies the incoming authentication request to the configured `TargetHost` and evaluates the response: + +```mermaid +sequenceDiagram + actor User + participant TIB as Tyk Identity Broker + participant Upstream as Target Host + + User->>TIB: POST /auth/{profile-id}/ProxyProvider + TIB->>Upstream: Proxy request + Upstream->>TIB: Response + TIB->>TIB: Evaluate success (OKCode / OKResponse / OKRegex) + TIB->>User: Redirect to ReturnURL or return token +``` + +1. The user submits credentials to the TIB endpoint (typically via a form `POST` or HTTP Basic Auth header). +2. TIB proxies the request to `TargetHost`. +3. TIB evaluates the response against the configured success criteria. +4. If successful, TIB extracts the user identity and executes the configured action. + +## Evaluating Success + +TIB evaluates the upstream response in order. At least one of `OKCode`, `OKResponse`, or `OKRegex` must be configured. + +1. **Hard failure**: if the upstream returns HTTP `400` or above, authentication fails immediately. +2. **`OKCode`**: if set (non-zero), the response status code must exactly match this value. +3. **`OKResponse`**: if set, TIB base64-encodes the raw response body and compares it to this value. The configured value must therefore be a base64-encoded string. +4. **`OKRegex`**: if set, TIB applies this regular expression against the raw response body. + +All configured criteria must pass for authentication to succeed. + +## Extracting User Identity + +If authentication succeeds, TIB extracts the user identity to pass to the identity handler: + +- If `ResponseIsJson` is `true`, TIB parses the response body as JSON and extracts values using `AccessTokenField` and `UsernameField` as JSON field names. +- If `ExrtactUserNameFromBasicAuthHeader` is `true`, TIB extracts the username from the incoming request's Basic Auth header. +- If no username can be extracted, TIB generates a random identifier and appends `@soSession.com` to form a placeholder email address. + + +The field name `ExrtactUserNameFromBasicAuthHeader` contains a known typo that is preserved in the TIB codebase to avoid a breaking change. You must spell it exactly as shown. + + +## TIB Profile + +The Proxy Provider configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `ProxyProvider` and `Type` to `passthrough`. + +```json expandable +{ + "ProviderName": "ProxyProvider", + "Type": "passthrough", + "ProviderConfig": { + "TargetHost": "http://{upstream-host}/{path}", + "OKCode": 200, + "OKResponse": "", + "OKRegex": "", + "ResponseIsJson": false, + "AccessTokenField": "", + "UsernameField": "", + "ExrtactUserNameFromBasicAuthHeader": false + } +} +``` + +The `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `TargetHost` | URL of the upstream authentication endpoint. | +| `OKCode` | HTTP status code that indicates a successful response. Set to `0` to disable this check. | +| `OKResponse` | Base64-encoded string that the response body must exactly match. Leave empty to disable. | +| `OKRegex` | Regular expression that must match the raw response body. Leave empty to disable. | +| `ResponseIsJson` | Set to `true` if the upstream response body is JSON, enabling field extraction via `AccessTokenField` and `UsernameField`. | +| `AccessTokenField` | JSON field name in the upstream response containing an access token. | +| `UsernameField` | JSON field name in the upstream response containing the username. | +| `ExrtactUserNameFromBasicAuthHeader` | Set to `true` to extract the username from the incoming request's Basic Auth header. See the warning above for the intentional field name typo. | + +## Login Page + +Since `ProxyProvider` is a passthrough flow, users submit credentials directly to TIB. Create a login page with a form that posts to the TIB authentication endpoint: + +```html +
+ + + +
+``` + +Alternatively, credentials can be passed via an HTTP Basic Auth header if `ExrtactUserNameFromBasicAuthHeader` is set to `true`. + +## Worked Example + +This example proxies a Basic Auth request to an upstream service. TIB evaluates the HTTP `200` response code and extracts the username from the JSON response body. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "proxy-dashboard", + "Name": "Proxy Provider Dashboard SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "passthrough", + "ProviderName": "ProxyProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "TargetHost": "http://{upstream-host}/{auth-path}", + "OKCode": 200, + "OKResponse": "", + "OKRegex": "", + "ResponseIsJson": true, + "AccessTokenField": "access_token", + "UsernameField": "username", + "ExrtactUserNameFromBasicAuthHeader": false + } +} +``` + +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Login page form action** + +Your login page form should `POST` to: + +``` +http://dashboard.example.com:3000/auth/proxy-dashboard/ProxyProvider +``` + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "proxy-portal", + "Name": "Proxy Provider Portal SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "passthrough", + "ProviderName": "ProxyProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "TargetHost": "http://{upstream-host}/{auth-path}", + "OKCode": 200, + "OKResponse": "", + "OKRegex": "", + "ResponseIsJson": true, + "AccessTokenField": "access_token", + "UsernameField": "username", + "ExrtactUserNameFromBasicAuthHeader": false + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `DashboardCredential` to the [`PortalAPISecret`](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Login page form action** + +Your login page form should `POST` to: + +``` +http://portal.example.com:3001/tib/auth/proxy-portal/ProxyProvider +``` + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + diff --git a/api-management/custom-error-responses.mdx b/api-management/custom-error-responses.mdx new file mode 100644 index 0000000000..eb0e1b6b6a --- /dev/null +++ b/api-management/custom-error-responses.mdx @@ -0,0 +1,482 @@ +--- +title: "Customizing Error Responses" +description: "Learn how to customize the HTTP error responses returned by Tyk Gateway to API clients, using Error Overrides, Response Body Overrides, and Authentication Error Configuration" +keywords: "custom error response, error override, error template, response body override, override_messages, authentication error, HTTP error, status code, error message, Tyk Gateway" +sidebarTitle: "Customize Error Responses" +--- + +## Availability + +| Component | Version | Edition | +| :-------- | :------ | :------ | +| Tyk Gateway (Error Overrides) | Available since [v5.13.0](/developer-support/release-notes/gateway#5-13-0-release-notes) | Community & Enterprise | +| Tyk Gateway (Response Body Overrides, `override_messages`) | All versions | Community & Enterprise | + + +This document focuses exclusively on customizing errors returned by API proxies deployed on the Gateway (data plane traffic). It does not apply to errors returned by the Tyk Dashboard API nor the Tyk Gateway API (management traffic). + + +## Tyk Gateway Error Handling + +When an error occurs during request processing, Tyk Gateway generates an HTTP error response automatically. Error conditions include authentication failures, rate limit rejections, request validation failures, and upstream connection problems, as well as error status codes (`HTTP 4xx` and `HTTP 5xx`) returned by upstream services. + +The default response comprises: +- the HTTP status code defined by the triggering middleware +- a `Content-Type: application/json` header +- an `X-Generator: tyk.io` header +- a JSON response body + +```http +HTTP/1.1 403 Forbidden +Content-Type: application/json +X-Generator: tyk.io + +{"error": "Access to this API has been disallowed"} +``` + +Notes on the default error response: +- the response is provided in JSON format regardless of the `Content-Type` of the request. +- the `X-Generator` header can be suppressed by setting [`hide_generator_header: true`](/tyk-oss-gateway/configuration#hide_generator_header) in `tyk.conf` (or using the equivalent environment variable). + +This default behavior is often insufficient in production for various reasons such as: + +- **Standardizing Format:** clients expect a specific error structure, such as [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807), that the default response does not produce. +- **Gateway Migration:** clients may depend on error response formats from a previous API gateway that differ from Tyk's defaults. +- **Hiding Internal Detail:** default messages might expose information about your authentication mechanism, key structure, or upstream topology. +- **Consistency Across APIs:** upstream services may return different error formats; normalizing them at the Gateway improves the client experience. +- **Per-API Requirements:** different APIs published on the same Gateway may need to return different error formats or status codes for the same underlying error condition. + +### Customization Mechanisms + +Tyk provides three mechanisms for customizing error responses. The first two are long-standing features with narrow, specific purposes. The third, introduced in Tyk v5.13.0, is a comprehensive replacement for both. + +[**Authentication Error Configuration**](#authentication-error-configuration) targets specific scenarios: the HTTP status codes and message strings that Tyk returns for errors reported by the [client authentication](/api-management/client-authentication) middleware, which may not suit your API contract. Its scope is intentionally narrow: it covers only the 11 built-in auth and OAuth error conditions and applies identically to all APIs on the Gateway. + +[**Response Body Overrides**](#response-body-overrides) solve a different problem: the format of the response body. By placing Go template files in the Gateway's `templates/` directory, you can replace the body structure (as JSON or XML) for any HTTP error status code. Templates are resolved globally based on status code and the request's `Content-Type`. This applies identically to all APIs; it cannot intercept or modify error responses that originated from upstream services, and cannot differentiate between different errors that generate the same status code. + +[**Error Overrides**](#error-overrides), introduced in Tyk v5.13.0, supersedes both. You define a set of rules against which each error is tested. Separate rules can be configured for different status codes, error type, message pattern, or upstream response content. When a rule matches, Tyk can modify any part of the response before it is returned to the client. Error Overrides works with errors generated by both Tyk and upstream services, which neither legacy mechanism can intercept. + +The table below summarizes the key differences: + +| | Authentication Error Configuration | Response Body Overrides | Error Overrides | +|---|:--:|:--:|:--:| +| Available since | Early versions | v2.2 | v5.13.0 | +| Configuration location | `tyk.conf` | Template files in `templates/` | `tyk.conf` and/or API definition | +| Scope | All APIs | All APIs | All APIs or per-API | +| Customize status code | ✅ | ❌ | ✅ | +| Customize message string | ✅ | ❌ | ✅ | +| Customize body format | ❌ | ✅ | ✅ | +| Customize upstream errors | ❌ | ❌ | ✅ | +| Customize by error type | ❌ | ❌ | ✅ | + +For new API deployments, we strongly recommend that you use **Error Overrides** to customize error responses. Authentication Error Configuration and Response Body Overrides remain fully supported for backward compatibility. + +### How the Mechanisms Interact + +The three error customization mechanisms can be implemented together. **We don't recommend this**, now that Error Overrides provides full customization, however it is important to understand how they will interact if more than one is deployed. + +{/* DIAGRAM PLACEHOLDER */} +{/* +Suggested diagram: flowchart with these steps: +1. Gateway starts up -> Authentication Error Configuration remaps default auth/OAuth status codes and messages +2. Request arrives and error occurs +3. Error Overrides evaluated -> if a matching rule provides a complete body, return response immediately (end) +4. If no complete body from Error Overrides -> Response Body Overrides format the body +5. HTTP response sent to client +*/} + +{/* Three points about the sequence may not be obvious from the diagram: */} + +- **Authentication Error Configuration runs at Gateway startup, not at request time.** Error Overrides and Response Body Overrides see the already-modified status codes and messages, not the original Tyk defaults. +- **Error Overrides only bypasses Response Body Overrides when it provides a complete body.** A rule that changes only the status code or message still falls through to the template for body formatting. +- **The mechanisms can coexist.** Error Overrides can be introduced incrementally: existing template files and `override_messages` settings continue to function for any error condition not covered by an override rule. + +## Error Overrides + +Error Overrides is a powerful, declarative system that allows you to intercept errors and rewrite their HTTP status codes, headers, and bodies. It can handle both Gateway-generated errors (e.g., rate limits, auth failures) and Upstream errors (e.g., `HTTP 500 Internal Server Error` returned from your backend service). + +It works using a rule matching approach where each error response generated by Tyk middleware (including the proxy stage which receives the response from the upstream) is checked against a set of [rules](#rule-structure) that determine the [modification](#response-customization) to be made to matching responses. If no rule matches, the response is returned to the client unaltered. + +### Rule Structure + +Rules are organized by status code key, which can be either an exact code such as `400` or a wildcard pattern such as `4xx` or `5xx`. Multiple rules can share the same key; they are evaluated in order and the first match wins. + +Each rule has two objects: +- an optional `match` object to specify which errors within that status code trigger the rule. +- a `response` object that defines what Tyk returns when the rule fires (in place of the original error response). + +Omitting `match` means that the transformation in the `response` is applied to **any error with that status code**. + +```json +{ + "error_overrides": { + "403": [ // key: exact code ("403") or wildcard ("4xx") + { + "match": { // optional: specifies which errors trigger this rule + "flag": "AKI" // match by error classification flag + }, + "response": { // what Tyk returns when this rule fires + "status_code": 401, // new HTTP status code + "headers": { "WWW-Authenticate": "Bearer" }, // headers to set in the response + "body": "{\"error\": \"Authentication required\"}" // response body + } + } + ] + } +} +``` + +This structure is used for [Gateway-level](#gateway-level) overrides; for API-level overrides see the [relevant section](#api-level). + +### Matching Criteria + +After the initial match on the status code key, you can refine which errors trigger the rule using the `match` object. The fields available depend on whether the error was generated by Tyk Gateway or returned by an upstream service. + +| Field | Gateway-generated errors | Upstream errors | +|---|---|---| +| `flag` | Matches any flag from the [Flag Reference](#flag-reference). | `URS` is supported for `5xx` errors; leave blank for `4xx` errors. Any other value prevents the rule from matching. | +| `messagePattern` | Regex matched against the error message string. | Regex matched against the response body. | +| `bodyField` + `bodyValue` | Not evaluated. | A [gjson](https://github.com/tidwall/gjson) path and expected value matched against a JSON response body. | + +#### Matching Gateway-generated errors + +- If `flag` is set and matches, the rule fires. +- Otherwise, if `messagePattern` is set and matches the error message, the rule fires. +- If a match field is set but does not match, the rule does not fire. +- If **neither** field is set, the rule **matches any error with that status code**. + +#### Matching Upstream-generated errors + +`bodyField` and `bodyValue` work together to match a specific field and value in the upstream JSON response body. `bodyField` is a [gjson](https://github.com/tidwall/gjson) path expression identifying a field in the response; `bodyValue` is the expected string value for that field. + +For example, given an upstream error response: + +```http +HTTP/1.1 503 Service Unavailable +Content-Type: application/json + +{"error": {"code": "OUT_OF_STOCK"}} +``` + +The following match configuration fires only when the upstream returns that specific error code: + +```json +"match": { + "flag": "URS", + "bodyField": "error.code", + "bodyValue": "OUT_OF_STOCK" +} +``` + +**`5xx` Upstream Errors:** +- Set `flag=URS` to match upstream `5xx` responses only, excluding Gateway-generated errors with the same code (such as a circuit-breaker `503`). +- To differentiate between different upstream `5xx` errors, set `bodyField`/`bodyValue` or `messagePattern` to match on the response body. Note that `messagePattern` matches on the response body for upstream errors. + +**`4xx` Upstream Errors:** +- `flag=URS` is `5xx`-only. There is no flag that isolates upstream `4xx` responses from Gateway-generated ones with the same status code. +- Use `messagePattern` to differentiate by content: for Gateway errors it matches against the error message string; for upstream errors it matches against the response body. A pattern that matches the upstream response format but not Tyk's fixed error messages effectively targets upstream responses only. + + +`bodyField`/`bodyValue` is not evaluated when matching Gateway-generated errors. A `4xx` rule that sets only body criteria, with no `flag` or `messagePattern`, will match all Gateway-generated errors of that status code, not just upstream responses. + + + +When `messagePattern` or `bodyField`/`bodyValue` are used to match upstream responses, Tyk reads up to 4 KB of the response body. Content beyond 4 KB is not evaluated. If no match is found, the original response body is passed through unchanged. + + +### Response Customization + +The response object defines what Tyk returns to the client when a rule matches (or fires). *All fields are optional*: provide any combination to achieve the override you need. + +**`statusCode`**: (`status_code` in Tyk Classic and `tyk.conf`) +- This is the HTTP status code to return to the client. +- Any valid HTTP status code is accepted. +- If omitted, the original error status code is preserved. +- Available as `{{.statusCode}}` in [Go Template Syntax](#go-template-syntax) used in `body` or `template`. + +**`headers`**: +- A JSON object of key-value string pairs to set on the response. These are merged into the existing response headers; any key you specify sets or overrides that header while all other headers remain intact. + - If omitted, no additional headers are set. +- To remove a header, include it in `headers` with an empty value. +- Note that the `X-Generator` header cannot be removed via override rules; suppress it globally using [`hide_generator_header`](/tyk-oss-gateway/configuration#hide_generator_header). + +**`message`**: +- The error message text. +- Available as `{{.Message}}` in [Go Template Syntax](#go-template-syntax) used in `body` or `template`. +- If omitted, `{{.Message}}` resolves to an empty string in the `body` or `template`. + +**`body`**: +- The response body, written verbatim to the client. +- Can be a static string or a Go template string (see [Go Template Syntax](#go-template-syntax)). +- When `body` is provided, the legacy template system is bypassed entirely. +- If `body` is set, `template` is ignored. + +**`template`**: +- The base filename (without extension) of a file in the Gateway's `templates/` directory. +- The file content is used as the response body, written verbatim to the client. +- Tyk appends `.json` or `.xml` to the base filename based on the `Content-Type` of the original request. + - For example, `"my_error"` resolves to `templates/my_error.json` for JSON requests and `templates/my_error.xml` for XML requests. +- Use `template` as an alternative to `body` when the template body is large or shared across multiple rules. + +#### Partial Override + +If neither `body` nor `template` is set, the rule is a partial override as it does not contain any response body content. Tyk applies the `statusCode` and `message` from the matching rule. Use this when you want to change the status code or message while retaining the existing body format. + + +A partial override will pass through the legacy template system, which may format the response body. Be careful if you have both Error Overrides and [Response Body Overrides](#response-body-overrides) configured to avoid unintended behavior. + + +#### XML Clients + +Inline `body` overrides are written verbatim **regardless of the `Content-Type` of the original request**. To serve XML to XML clients using an inline body, ensure that the `body` contains XML formatted content. If using `template`, then ensure you provide a `.xml` file variant, as Tyk will select that given `Content-Type: "application/xml"` in the request. + +#### Go Template Syntax + + +The files referenced by the `template` field are distinct from the [Response Body Overrides](#response-body-overrides) legacy system. Both reside in the Gateway's `templates/` directory, but Error Override template files are referenced explicitly by name in a rule and have access to the variables listed below. Legacy template files are selected automatically by filename convention and only have access to `{{.Message}}`. + + +The `body` field and the files referenced by the `template` field support [Go template syntax](/api-management/traffic-transformation/go-templates). The variables available in error override templates are specific to the error context and are listed below. + +The following variables are always available: + +- `{{.StatusCode}}`: The HTTP status code being returned. +- `{{.Message}}`: The error message text. + +Note that for both variables, if `response.statusCode` or `response.message` are declared in the override rule, these will be applied prior to the template being resolved and so the override value will be applied to the template, not the value from the original error message. + +Some errors inject additional variables: + +- `{{.InvalidParams}}`: A string describing the specific validation failure. Only available when `flag=BIV` (see [Request Validation](/api-management/traffic-transformation/request-validation)). Example: `"body.age must be a positive integer"`. + +Example of an RFC 7807 compliant inline template: + +```json +"response": { + "statusCode": 400, + "headers": { "Content-Type": "application/problem+json" }, + "body": "{\"type\": \"https://example.com/probs/validation\", \"title\": \"Invalid Request\", \"status\": {{.StatusCode}}, \"detail\": \"{{.Message}}\"}" +} +``` + +### Configuration + +Error Overrides can be configured in two layers: + +- [Gateway-level](#gateway-level) configuration is applied to all APIs deployed on the Gateway +- [API-level](#api-level) configuration applies to a specific API and is evaluated first + +If no API-level rule matches, Tyk checks the error response against the Gateway-level rules. + +#### Gateway Level + +Configure overrides that apply to all APIs deployed on the Gateway using the [`error_overrides`](/tyk-oss-gateway/configuration#error_overrides) object in the Gateway config (`tyk.conf` or equivalent environment variables): + +```json +"error_overrides": { + "403": [{ + "match": { + "flag": "AKI" + }, + "response": { + "status_code": 401, + "headers": { "WWW-Authenticate": "Bearer" }, + "body": "{\"error\": \"Authentication required\"}" + } + }] +} +``` + +#### API Level + +Configure overrides for a specific API in the [`errorOverrides`](/api-management/gateway-config-tyk-oas#erroroverrides) section of the Tyk Vendor Extension: + +```json +"x-tyk-api-gateway": { + "errorOverrides": { + "enabled": true, + "value": { + "403": [{ + "match": { + "flag": "AKI" + }, + "response": { + "statusCode": 401, + "headers": { "WWW-Authenticate": "Bearer" }, + "body": "{\"error\": \"Authentication required\"}" + } + }] + } + } +} +``` + +Set `"enabled": false` to disable API-level overrides without removing the configuration. + +If using Tyk Classic APIs, configure overrides at the top level of the API definition: + +```json +{ + "error_overrides_disabled": false, + "error_overrides": { + "403": [{ + "match": { + "flag": "AKI" + }, + "response": { + "status_code": 401, + "headers": { "WWW-Authenticate": "Bearer" }, + "body": "{\"error\": \"Authentication required\"}" + } + }] + } +} +``` + +Set `"error_overrides_disabled": true` to disable API-level overrides without removing the configuration. + +### Flag Reference + +The following flags classify errors generated by Tyk Gateway and can be used in the `flag` field of a match object. For intercepting upstream error responses, see [Upstream Error Responses](#upstream-error-responses). + +#### Authentication and Authorization Errors + +| Flag | Name | Description | Typical HTTP Status | +|:----:|------|-------------|:-------------------:| +| `AMF` | Auth Field Missing | Authorization field missing from request | `401` (auth token) / `400` (OAuth) | +| `AKI` | API Key Invalid | API key invalid | `403` | +| `TKE` | Token Expired | Token or certificate has expired | `403` | +| `TKI` | Token Invalid | Token is invalid | `403` | +| `TCV` | Token Claims Invalid | Token claims invalid (JWT) | `401` | +| `EAD` | External Auth Denied | External authentication denied | `403` | +| `CRQ` | Client Certificate Required | Mutual TLS client certificate required | `401` | +| `CMM` | Client Certificate Mismatch | Mutual TLS client certificate mismatch | `401` | + +#### Request Validation Errors + +| Flag | Name | Description | Typical HTTP Status | +|:----:|------|-------------|:-------------------:| +| `BTL` | Body Too Large | Request body too large | `400` | +| `CLM` | Content-Length Missing | Content-Length header missing | `411` | +| `BIV` | Body Invalid | [Request validation](/api-management/traffic-transformation/request-validation) failed | `400` or `422` | +| `IHD` | Invalid Header | Invalid request header | `400` | + +#### Rate Limiting and Quota Errors + +| Flag | Name | Description | Typical HTTP Status | +|:----:|------|-------------|:-------------------:| +| `RLT` | Rate Limited | Request rate limited | `429` | +| `QEX` | Quota Exceeded | Quota exceeded | `403` | + +#### Upstream Connectivity Errors + +| Flag | Name | Description | Typical HTTP Status | +|:----:|------|-------------|:-------------------:| +| `UCF` | Upstream Connection Refused | Upstream connection refused | `500` | +| `UCT` | Upstream Connection Timeout | Upstream connection timed out (TCP connect phase) | `500` | +| `URR` | Connection Reset | Upstream connection reset by peer | `500` | +| `URT` | Response Timeout | Upstream request timed out (no response headers received) | `504` | +| `EPI` | Broken Pipe | Broken pipe (EPIPE) writing to upstream | `500` | +| `CAB` | Connection Aborted | Connection aborted | `500` | +| `NRS` | Network Reset | Network reset (ENETRESET) | `500` | +| `DNS` | DNS Resolution Failure | DNS resolution failure | `500` | +| `NRH` | No Route to Host | No route to host | `500` | +| `NHU` | No Healthy Upstreams | No healthy upstreams available | `503` | +| `CBO` | Circuit Breaker Open | Circuit breaker open | `503` | +| `CDC` | Client Disconnected | Client disconnected before response was sent | `499` | +| `UPE` | Upstream Protocol Error | Upstream protocol error (generic fallback) | `500` | + +#### TLS Errors + +| Flag | Name | Description | Typical HTTP Status | +|:----:|------|-------------|:-------------------:| +| `TLE` | TLS Certificate Expired | TLS certificate on the upstream has expired | `500` | +| `TLI` | TLS Certificate Invalid | TLS certificate on the upstream is invalid | `500` | +| `TLM` | TLS Hostname Mismatch | TLS certificate hostname mismatch | `500` | +| `TLN` | TLS Not Trusted | TLS certificate not trusted (unknown authority) | `500` | +| `TLH` | TLS Handshake Failure | TLS handshake failed | `500` | +| `TLP` | TLS Protocol Error | TLS protocol error | `500` | +| `TLA` | TLS Alert | TLS alert (handshake failure, version mismatch) | `500` | +| `TLC` | TLS Certificate Chain Incomplete | TLS certificate chain incomplete | `500` | + +#### Upstream Error Responses + +Upstream `5xx` error responses carry the `URS` flag. Use `flag=URS` in a match rule to restrict it to upstream responses only, preventing it from also matching gateway-generated errors with the same status code. + +| Flag | Name | Description | Typical HTTP Status | +|:----:|------|-------------|:-------------------:| +| `URS` | Upstream Response 5XX | Upstream returned a 5xx error response | Varies (`500` to `599`) | + +## Response Body Overrides + + +This is a legacy feature. For new implementations, we recommend using [Error Overrides](#error-overrides) instead. + + +The Response Body Overrides system works by the Gateway reading text files from the `templates/` directory in its local filesystem. Tyk selects a file based on the HTTP status code and the request's `Content-Type` (for example, `error_500.json` or `error_500.xml`), falling back to a generic `error.json` or `error.xml` if no status code-specific file exists. + +The content of the selected file is used to replace the body of the error response. Go template syntax is available, but only the `{{.Message}}` variable is supported, which contains the error message text. + +Note the following limitations on the scope of this feature: +- **Gateway-Level Scope:** The same customized response bodies are applied for all APIs on the Gateway. +- **Upstream Errors:** This method cannot intercept upstream errors. +- **Status Code Scope:** There is no way to differentiate between different errors that share the same status code. +- **`HTTP 404` Limitation:** Response Body Overrides cannot be used to customize the body of `HTTP 404` responses. +- **Response Body Only:** This method does not enable customization of status code, error message or headers. + + +The files used by Response Body Overrides share the same `templates/` directory as the files referenced by the Error Overrides [`template`](#go-template-syntax) field. If using both override methods, be careful to take account of the specific naming required by Response Body Overrides and the fact that this cannot be disabled, so a file named `error.json` will be used to override any error response without a specific [rule](#rule-structure). + + +## Authentication Error Configuration + + +This is a legacy feature. For new implementations, we recommend using [Error Overrides](#error-overrides) instead. + + +Authentication Error Configuration allows you to change the default HTTP status codes and error messages that Tyk returns for its built-in authentication and OAuth error conditions, using the `override_messages` configuration in `tyk.conf`. It is provided for cases where your API contract requires specific status codes or messages for auth failures that differ from Tyk's defaults. + +Specify the message ID in the `override_messages` object in `tyk.conf` and provide a `code`, `message`, or both: + +```json +"override_messages": { + "auth.key_not_found": { + "code": 401, + "message": "Invalid or missing API key" + } +} +``` + +Note the following limitations on the scope of this feature: +- **Limited Scope:** This method only works for specific auth-related errors. +- **Gateway-Level Scope:** The same responses are applied for all APIs on the Gateway. +- **Limited Customization:** This method does not enable customization of response body or headers. + + +Authentication Error Configuration takes effect at Gateway startup, before any requests are processed. If configured alongside Error Overrides, the Error Overrides rules see the values modified by Authentication Error Configuration, not the original Tyk defaults. For example, if you use `override_messages` to remap an error to a different status code, any Error Overrides rules must match the new code, not the original. + + +**Supported Message IDs and Their Defaults:** + +| Message ID | Default Message | Default HTTP Status | +|------------|-----------------|:-------------------:| +| `auth.auth_field_missing` | "Authorization field missing" | `401` | +| `auth.key_not_found` | "Access to this API has been disallowed" | `403` | +| `auth.cert_not_found` | "Access to this API has been disallowed" | `403` | +| `auth.key_is_invalid` | "Access to this API has been disallowed" | `403` | +| `auth.cert_expired` | "Certificate has expired" | `403` | +| `auth.cert_required` | "Client certificate required" | `401` | +| `auth.cert_mismatch` | "Access to this API has been disallowed" | `401` | +| `oauth.auth_field_missing` | "Authorization field missing" | `400` | +| `oauth.auth_field_malformed` | "Bearer token malformed" | `400` | +| `oauth.key_not_found` | "Key not authorised" | `403` | +| `oauth.client_deleted` | "Key not authorised. OAuth client access was revoked" | `403` | + +## Other Error Configurations + +Tyk offers error configurations within certain middleware: + +- [**Request Validation**](/api-management/traffic-transformation/request-validation#configuring-the-request-validation-middleware): When using Request Validation middleware, the HTTP status code returned in case of validation failure can be configured in the API definition. +- [**HMAC Signature Validation**](/basic-config-and-security/security/authentication-authorization/hmac-signatures): The HMAC signature authentication middleware exposes fields that set the HTTP status code and error message returned when signature validation fails. +- **Custom Plugins**: [JavaScript plugins](/api-management/plugins/javascript#using-returnoverrides) and [Rich Plugins](/api-management/plugins/rich-plugins#returnoverrides) can use the *Return Overrides* mechanism to halt middleware execution and return a custom HTTP response directly to the client. [Go plugins](/api-management/plugins/golang#terminating-the-request) can write directly to the `http.ResponseWriter` to terminate the request and return a fully custom status code, headers, and body. +- [**Virtual Endpoints**](/api-management/traffic-transformation/virtual-endpoints#working): Virtual endpoints execute a JavaScript function that returns a fully custom response (status code, headers, and body) via the `TykJsResponse()` function. \ No newline at end of file diff --git a/api-management/dashboard-analytics.mdx b/api-management/dashboard-analytics.mdx new file mode 100644 index 0000000000..317d351444 --- /dev/null +++ b/api-management/dashboard-analytics.mdx @@ -0,0 +1,360 @@ +--- +title: "Dashboard Analytics" +description: "Learn how Tyk Dashboard's Analytics UI and Log Browser get their data." +keywords: "Dashboard Analytics, Tyk Pump, Log Browser, Traffic Analytics, MDCB, Hybrid Pump" +sidebarTitle: "Overview" +--- + +## What Is Dashboard Analytics + +Tyk Dashboard presents [built-in analytics](#traffic-analytics) giving visibility of the traffic passing through your APIs. It covers two levels of visibility, each gated by its own [Dashboard permission](/platform-management/user-permissions#user-permissions-in-the-tyk-dashboard-api): + +- **Traffic Analytics** (`analytics:read`): graphs and breakdowns of request volume, error rates, and latency, sliced by dimensions such as API, access key, or OAuth client. +- **Log Browser** (`log:read`): inspect individual requests and responses. + +This data comes from traffic logs generated by Tyk Gateway for every request, delivered to Tyk Dashboard's persistent storage by [Tyk Pump](/api-management/tyk-pump), not by OpenTelemetry (OTel). This is true regardless of how far you've adopted OTel elsewhere: OTel traces and metrics export to external observability backends, but they don't feed Tyk Dashboard's own UI. + +### Two Kinds of Data + +Traffic Analytics and the Log Browser need different data, and can be enabled independently of each other: + +| Dashboard Feature | Needs | +| :-- | :-- | +| Traffic Analytics | Aggregated analytics: hourly summaries computed from traffic logs | +| Log Browser | Traffic logs: the full detail of each request | + +Aggregated analytics is cheaper to store and query, since it's summarized, while traffic logs preserve full request detail at the cost of storage volume. Many deployments enable both. + +### How Aggregation Works + +Aggregation calculates hourly analytics from traffic logs, grouped into a fixed set of dimensions, offloading this processing from Tyk Dashboard and reducing storage compared to keeping every traffic log: + +| Dashboard Screen | Aggregated By | Field | +| :-- | :-- | :-- | +| [Activity by API](#activity-by-api) | API proxy | [`api_id`](/api-management/logs/traffic-logs#param-api-id) | +| [Activity by Endpoint](#activity-by-endpoint) | API endpoint | [`track_path`](/api-management/logs/traffic-logs#param-track-path) | +| [Activity by Errors](#activity-by-error) | HTTP status code | [`response_code`](/api-management/logs/traffic-logs#param-response-code) | +| [Activity by Key](#activity-by-key) | Client access key or token | [`api_key`](/api-management/logs/traffic-logs#param-api-key) | +| [Traffic per OAuth Client](#activity-by-oauth-client) | OAuth client | [`oauth_id`](/api-management/logs/traffic-logs#param-oauth-id) | +| [Activity by Location](#activity-by-location) | Client geographic location | [`geo`](/api-management/logs/traffic-logs#param-geo) | +| n/a | API version | [`api_version`](/api-management/logs/traffic-logs#param-api-version) | + +`track_path` decides whether an endpoint is broken out individually in the Activity by Endpoint breakdown above; see [Controlling Which Endpoints Are Tracked](#controlling-which-endpoints-are-tracked) below for what sets it. + +Additional [custom aggregation](#custom-aggregation-tags) is also supported for users requiring data aggregated by different dimensions. + +### Pre-Computed vs Live Aggregation + +An Aggregate Pump is not the only way to get this aggregated data. Tyk Dashboard can also compute it itself: on every request to a Traffic Analytics screen, live, it runs the equivalent aggregation directly against the traffic log collection or table (a MongoDB aggregation pipeline, or a SQL query), and discards the result once the screen has rendered. + +Which of the two Tyk Dashboard uses is controlled by a single setting, [`enable_aggregate_lookups`](/tyk-dashboard/configuration#enable_aggregate_lookups): + +- `true`: Tyk Dashboard reads pre-computed aggregated analytics, written by an [Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#choosing-a-pump-type). +- `false` (the default): Tyk Dashboard computes the aggregation live instead, regardless of whether an Aggregate Pump is deployed and running; any aggregated analytics it computes and stores go unread. + +Live computation is a legitimate way to run Tyk Dashboard, and needs no extra pump configuration. But it has a cost: + +- It runs inside Tyk Dashboard itself, competing for the same CPU and database resources, rather than on a dedicated process built for this job (Tyk Pump). This can make Traffic Analytics screens feel slower to load, especially at higher traffic volumes or with several users viewing the Dashboard UI at once. +- It repeats the same aggregation on every screen load, rather than once per hour, so the cost grows with the volume of traffic logs it has to scan. + +Deploying an Aggregate Pump and setting `enable_aggregate_lookups: true` is the recommended approach. It also means you can [cap or evict old traffic logs](/api-management/dashboard-analytics/analytics-storage-management) without losing the historical data behind these screens, since the aggregated summaries are preserved separately. + + +For MCP proxy traffic, there's no working live fallback: live computation has no MCP-specific aggregation logic. If you're using [MCP Analytics](/ai-management/mcp-gateway/mcp-analytics), running an MCP Aggregate Pump with `enable_aggregate_lookups: true` is required, not just recommended. + + + +`enable_aggregate_lookups` doesn't apply to the **Activity by Graph** screen. That screen is fed by a dedicated `tyk_graph_aggregated` table, written by the [SQL GraphQL Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-graphql-aggregate-pump), which Tyk Dashboard's Postgres driver always queries directly. There's no live-computation fallback and no config flag for this screen, and it's PostgreSQL-only. + + +## Where the Data Comes From + +Tyk Gateway generates a traffic log for every request and writes it to Redis. See [Traffic Logs](/api-management/logs/traffic-logs) for how and when these are generated, and the full field reference. + +## What Data Is Captured + +The [Traffic Log Field Reference](/api-management/logs/traffic-logs#traffic-log-field-reference) documents every field. The rest of this section covers what specifically affects Dashboard Analytics: per-endpoint tracking, detailed recording, and custom tags. + +### Controlling Which Endpoints Are Tracked + +By default, only some endpoints are broken out individually in the per-endpoint aggregates: Activity by Endpoint, and the per-endpoint breakdowns nested within Activity by Key and Traffic per OAuth Client. Which ones depends on the `track_path` field; see [Controlling Which Endpoints Are Tracked](/api-management/logs/traffic-logs#controlling-which-endpoints-are-tracked) on the Traffic Logs page for what sets it. + +- Endpoints with Track Endpoint enabled get `track_path: true`, and appear individually in these breakdowns. +- Endpoints without it get `track_path: false`. Their traffic is excluded from these per-endpoint breakdowns specifically, but still counted in every other aggregate dimension: API totals, error codes, API versions, key/OAuth-client counts, geo, and tags. + +Set `track_all_paths: true` on an Aggregate Pump to override this and include every endpoint in these breakdowns, regardless of whether Track Endpoint is enabled on it. + + +Track Endpoint only affects this aggregated per-endpoint breakdown. It has no effect on traffic logs or the Log Browser. + + +### Detailed Recording + +Detailed recording, [configured at the Gateway](/api-management/logs/traffic-logs#detailed-recording), includes the full request and response, in wire format and base64-encoded, in the traffic log's `raw_request` and `raw_response` fields. Enabling it significantly increases record size and storage requirements; Tyk Cloud users are subject to the subscription's storage quota. + +### Custom Aggregation Tags + +Aggregation groups traffic logs by a fixed set of [standard fields](#how-aggregation-works). When those don't capture the dimension you care about, for example when several sub-accounts or environments share a single API key so the standard `api_key` aggregation can't separate them, Tyk Gateway can [tag traffic logs](/api-management/logs/traffic-logs#custom-tags) with the value of any HTTP request header, such as `X-Account-ID`. Tyk Pump's aggregate pumps then compute an hourly aggregate for each distinct tag value observed, the same way they do for the standard fields. + +Because every distinct tag value gets its own aggregate bucket, tagging a header whose value is unique per request, such as a timestamp or request ID, creates one bucket per request: no aggregation benefit, just storage growth. Tyk Pump logs a warning if it detects this happening. + +You might also tag a header you never intended to aggregate at all, for example a request ID useful for finding one specific transaction in the Log Browser, but meaningless as an aggregate dimension. + +In both cases, you can add the tag, or its prefix, to the aggregate pump type's `ignore_tag_prefix_list` setting. This only affects aggregation: the tag itself is still recorded on the traffic log and remains visible in the Log Browser either way. + +**Viewing the Aggregated Data** + +In the Tyk Dashboard UI, use the **filter by tag** option on the [API Activity Dashboard](#api-activity-dashboard) to see the aggregate graphs for a specific tag value. Programmatically, pass the tag as a `tags` parameter to the [Dashboard API](/tyk-dashboard-api)'s analytics endpoints. + +### GraphQL-Specific Detail + +Traffic logs for GraphQL APIs carry additional fields; see [GraphQL Fields](/api-management/logs/traffic-logs#graphql-fields) on the Traffic Logs page. This data is not currently surfaced in Tyk Dashboard; it's stored for export to external tools. See the Mongo GraphQL Pump and SQL GraphQL Pump sections of [Control Plane Pumps](/api-management/dashboard-analytics/control-plane-pumps#mongodb) for configuration. + +### MCP-Specific Detail + +Traffic logs for MCP proxy requests carry additional fields; see [MCP Fields](/api-management/logs/traffic-logs#mcp-fields) on the Traffic Logs page. See [MCP Analytics](/ai-management/mcp-gateway/mcp-analytics) for how Tyk Dashboard surfaces this data. + +## How Data Reaches Tyk Dashboard + +How that data actually reaches Tyk Dashboard depends on your deployment topology. Tyk Gateway's side of the job, generating the traffic log and writing it to Redis, is identical either way; what differs is what reads it next. + +In a combined control and data plane (the `tyk-stack` chart, no Tyk MDCB), Tyk Pump reads directly from that Redis instance and writes straight to the persistent storage (MongoDB or SQL), forwarding traffic logs, aggregated analytics, or both, in parallel: + +```mermaid +graph TD + A[Client Request] -->|Request received| B[Tyk Gateway] + B -->|Forward request| U[Upstream Service] + B -->|Generate Traffic Log| C[Redis] + C -->|Tyk Pump reads traffic logs| D[Tyk Pump] + D -->|Write traffic logs| E[Persistent Storage] + D -->|Compute and write aggregated analytics| E +``` + +In a distributed deployment, with separate control and data planes connected via Tyk MDCB, the Hybrid Pump (the pump type that runs on each data plane) reads from its own local Redis and forwards the data to Tyk MDCB instead, which then writes it to the persistent storage in the control plane, either using its own built-in writer or by forwarding it via a Redis queue to a Control Plane Pump: + +```mermaid +graph TD + A[Client Request] -->|Request received| B[Tyk Gateway] + B -->|Forward request| U[Upstream Service] + B -->|Generate Traffic Log| C[Local Redis] + C -->|Hybrid Pump reads traffic logs| D[Hybrid Pump] + D -->|Forward traffic logs or aggregated data| M[Tyk MDCB] + M -->|Write directly| E[Persistent Storage] + M -->|Forward via Redis queue| F[Control Plane Pump] + F -->|Write| E +``` + +See [Data Plane Pump](/api-management/dashboard-analytics/data-plane-pump) for that mechanism in full, including which of the two paths applies to traffic logs versus aggregated analytics. + +Both paths write to the same MongoDB or SQL collections that Tyk Dashboard reads from. + +## Storage Backends + +Whichever topology you use, Tyk Pump ultimately writes traffic logs and aggregated analytics into either MongoDB or SQL (PostgreSQL or MySQL), and Tyk Dashboard reads from that same database. + +- MongoDB stores each kind of data, traffic logs and aggregated analytics, in its own collection, and can optionally split traffic logs into a separate collection per Organisation. +- SQL also stores each kind of data in its own table, but has no per-Organisation split: every Organisation's rows share the same table, distinguished by an indexed `org_id` column. + +See [Control Plane Pumps](/api-management/dashboard-analytics/control-plane-pumps#choosing-a-pump-type) for the specific pump types and configuration for each (or [Data Plane Pump](/api-management/dashboard-analytics/data-plane-pump) if your control and data planes are separate). + +Tyk Dashboard's own configuration file has a `storage` section, with `analytics` and `logs` sub-sections used to connect it to those same databases; see the [Tyk Dashboard configuration reference](/tyk-dashboard/configuration#storage) for the full field list, and [Database Management](/planning-for-production/database-settings) for production sizing guidance. + +For guidance on managing the size of that stored data over time, see [Analytics Storage Management](/api-management/dashboard-analytics/analytics-storage-management). + +## Traffic Analytics + +The Tyk Dashboard provides a full set of analytics functions and graphs that you can use to segment and view your API traffic and activity. The Dashboard offers a great way for you to debug your APIs and quickly pin down where errors might be cropping up and for which clients. + +[User Owned Analytics](/platform-management/user-permissions), introduced in Tyk v5.1, can be used to limit the visibility of aggregate statistics to users when API Ownership is enabled. Due to the way that the analytics data are aggregated, not all statistics can be filtered by API and so may be inaccessible to users with the Owned Analytics permission. + + + +For the Tyk Dashboard's analytics functionality to work, you must configure both per-request and aggregated pumps for the database platform that you are using. For more details see the [Control Plane Pumps](/api-management/dashboard-analytics/control-plane-pumps#choosing-a-pump-type) section. + + + + +## Analyzing API Traffic Activity + +### API Activity Dashboard + +The first screen (and main view) of the Tyk Dashboard will show you an overview of the aggregate usage of your APIs, this view includes the number of hits, the number of errors and the average latency over time for all of your APIs as an average: + +API Activity Dashboard + +You can toggle the graphs by clicking the circular toggles above the graph to isolate only the stats you want to see. + +Use the Start and End dates to set the range of the graph, and the version drop-down to select the API and version you wish to see traffic for. + +You can change the granularity of the data by selecting the granularity drop down (in the above screenshot: it is set to “Day”). + +The filter by tag option, in a graph view, will enable you to see the graph filtered by any tags you add to the search. + +Below the aggregate graph, you’ll see an error breakdown and endpoint popularity chart. These charts will show you the overall error type (and code) for your APIs as an aggregate and the popularity of the endpoints that are being targeted by your clients: + +Error Breakdown and Endpoints + + + +From Tyk v5.1 (and LTS patches v4.0.14 and v5.0.3) the Error Breakdown and Endpoint Popularity charts will not be visible to a user if they are assigned the [Owned Analytics](/platform-management/user-permissions) permission. + + + +### Activity Logs + +When you look through your Dashboard and your error breakdown statistics, you'll find that you will want to drill down to the root cause of the errors. This is what the Log Browser is for. + +The Log Browser will isolate individual log lines in your analytics data set and allow you to filter them by: + +* API Name +* Token ID (hashed) +* Errors Only +* By Status Code + +You will be presented with a list of requests, and their metadata: + +Log Viewer + +Click a request to view its details. + +Log Viewer Details + +#### Self-Managed Installations Option + +In an Self-Managed installation, if you have request and response logging enabled, then you can also view the request payload and the response if it is available. +To enable request and response logging, please take a look at [useful debug modes](/api-management/troubleshooting-debugging#capturing-detailed-logs) . + +**A warning on detailed logging:** This mode generates a very large amount of data, and that data exponentially increases the size of your log data set, and may cause problems with delivering analytics in bulk to your MongoDB instances. This mode should only be used to debug your APIs for short periods of time. + + + +### Activity by API + +To get a tabular view of how your API traffic is performing, you can select the **Activity by API** option in the navigation and see a tabular view of your APIs. This table will list out your APIs by their traffic volume and you'll be able to see when they were last accessed: + +Activity per API + +You can use the same range selectors as with the Dashboard view to modify how you see the data. However, granularity and tag views will not work since they do not apply to a tabulated view. + +If you select an API name, you will be taken to the drill-down view for that specific API, here you will have a similar Dashboard as you do with the aggregate API Dashboard that you first visit on log in, but the whole view will be constrained to just the single API in question: + +Traffic per API: CLosed graph + +You will also see an error breakdown and the endpoint popularity stats for the API: + +API error breakdown pie chart + +Tyk will try to normalize endpoint metrics by identifying IDs and UUIDs in a URL string and replacing them with normalized tags, this can help make your analytics more useful. It is possible to configure custom tags in the configuration file of your Tyk Self-Managed or Multi-Cloud installation. + + + +From Tyk v5.1 (and LTS patches v4.0.14 and v5.0.3) the Error Breakdown and Endpoint Popularity charts will not be visible to a user if they are assigned the [Owned Analytics](/platform-management/user-permissions) permission. + + + +### Activity by Key + +You will often want to see what individual keys are up to in Tyk, and you can do this with the **Activity per Key** section of your analytics Dashboard. This view will show a tabular layout of all keys that Tyk has seen in the range period and provide analytics for them: + +Activity per Token + +You'll notice in the screenshot above that the keys look completely different to the ones you can generate in the key designer (or via the API), this is because, by default, Tyk will hash all keys once they are created in order for them to not be snooped should your key-store be breached. + +This poses a problem though, and that is that the keys also no longer have any meaning as analytics entries. You'll notice in the screenshot above, one of the keys is appended by the text **TEST_ALIAS_KEY**. This is what we call an Alias, and you can add an alias to any key you generate and that information will be transposed into your analytics to make the information more human-readable. + +The key `00000000` is an empty token, or an open-request. If you have an API that is open, or a request generates an error before we can identify the API key, then it will be automatically assigned this nil value. + +If you select a key, you can get a drill down view of the activity of that key, and the errors and codes that the token has generated: + +Traffic activity by key graph + +Errors by Key + +(The filters in this view will not be of any use except to filter by API Version). + + + +From Tyk v5.1 (and LTS patches v4.0.14 and v5.0.3) the Traffic per Key screen will not be visible to a user if they are assigned the [Owned Analytics](/platform-management/user-permissions) permission. + + + +### Activity by Endpoint + +To get a tabular view of how your API traffic is performing at the endpoint level, you can select the Activity by Endpoint option in the navigation and see a tabular view of your API endpoints. This table will list your API endpoints by their traffic volume and you’ll be able to see when they were last accessed: + +Activity by endpoint + +Not every endpoint necessarily appears here: see [Controlling Which Endpoints Are Tracked](#controlling-which-endpoints-are-tracked) for what controls that. + +### Activity by Graph + +The **Activity by Graph** page provides analytics for your [GraphQL APIs](/api-management/graphql) (Universal Data Graph / UDG). It allows you to monitor and analyze GraphQL-specific traffic through the following charts and tables: + +* **Popularity by Graph API**: Displays the most popular GraphQL APIs based on request volume. +* **Errors by Graph API**: Shows the error rates and distribution across your GraphQL APIs. +* **All Graph APIs**: A comprehensive table listing all configured GraphQL APIs and their key metrics. + +{/* TODO: Add screenshots of the Activity by Graph dashboard */} + +### Activity by Location + +Tyk will attempt to record GeoIP based information based on your inbound traffic. This requires a MaxMind IP database to be available to Tyk and is limited to the accuracy of that database. + +You can view the overview of what the traffic breakdown looks like per country, and then drill down into the per-country traffic view by selecting a country code from the list: + +Geographic Distribution + + + +From Tyk v5.1 (and LTS patches v4.0.14 and v5.0.3) the Geographic Distribution screen will not be visible to a user if they are assigned the [Owned Analytics](/platform-management/user-permissions) permission. + + + +**MaxMind Settings** + +To use a MaxMind database, see [MaxMind Database Settings](/tyk-oss-gateway/configuration#analytics_config-enable_geo_ip) in the Tyk Gateway Configuration Options. + +### Activity by MCP + +The **Activity by MCP** page provides analytics for your Model Context Protocol (MCP) [proxies and primitives](/ai-management/mcp-gateway/managing-proxies). It allows you to track and monitor MCP-specific traffic through the following charts and tables: + +* **Activity per MCP**: Displays request volumes and traffic trends across your configured MCP servers. +* **Errors by MCP**: Tracks error rates and distribution across different MCP servers. +* **Primitives Traffic**: Shows the overall traffic volume handled by your MCP primitives (tools, resources, and prompts). +* **Most Used Primitives**: Identifies the most frequently invoked primitives. +* **Most Failing Primitives**: Highlights the primitives with the highest failure rates. +* **Slowest Primitives**: Measures latency and identifies the slowest-performing primitives. +* **Error Status Codes by Primitive**: Breaks down error responses by HTTP status codes for each primitive. + +For more information, see [MCP Analytics](/ai-management/mcp-gateway/mcp-analytics). + +![Activity by MCP](/img/ai-management/activity-by-mcp.png) + +### Activity by Error + +The error overview page limits the analytics down to errors only, and gives you a detailed look over the range of the number of errors that your APIs have generated. This view is very similar to the Dashboard, but will provide more detail on the error types: + +Error Overview + + + +From Tyk v5.1 (and LTS patches v4.0.14 and v5.0.3) the Errors by Category data will not be visible to a user if they are assigned the [Owned Analytics](/platform-management/user-permissions) permission. + + + +### Activity by OAuth Client + +Traffic statistics are available on a per OAuth Client ID basis if you are using the OAuth mode for one of your APIs. To get a breakdown view of traffic aggregated to a Client ID, you will need to go to the **System Management -> APIs** section and then under the **OAuth API**, there will be a button called **OAuth API**. Selecting an OAuth client will then show its aggregate activity + +OAuth Client + +In the API list view – an **OAuth Clients** button will appear for OAuth enabled APIs, use this to browse to the Client ID and the associated analytics for that client ID: + +OAuth Client Analytics Data + +You can view the analytics of individual tokens generated by this Client ID in the regular token view. + + + +From Tyk v5.1 (and LTS patches v4.0.14 and v5.0.3) the Traffic per OAuth Client ID charts will not be visible to a user if they are assigned the [Owned Analytics](/platform-management/user-permissions) permission. + diff --git a/api-management/dashboard-analytics/analytics-storage-management.mdx b/api-management/dashboard-analytics/analytics-storage-management.mdx new file mode 100644 index 0000000000..ad99c12525 --- /dev/null +++ b/api-management/dashboard-analytics/analytics-storage-management.mdx @@ -0,0 +1,174 @@ +--- +title: "Analytics Storage Management" +description: "Manage the size of the persistent analytics storage, with capped MongoDB collections, TTL indexes, and SQL table sharding." +keywords: "Tyk Pump, Tyk Dashboard, Capping, TTL, MongoDB, SQL, Table Sharding" +sidebarTitle: "Analytics Storage Management" +--- + +## Overview + +Tyk Pump writes two different kinds of data to the Control Plane's persistent storage, from which it's used by Tyk Dashboard's Traffic Analytics and Log Browser, and they behave very differently when it comes to storage growth. + +- **Traffic logs** are one record per request, stored close to verbatim, so they grow unbounded: proportional to request volume, with no built-in limit. +- **Aggregated analytics**, computed by the Aggregate pumps, roll traffic logs up into per-hour (or per-minute) buckets by dimensions such as API and key, so their size is bounded by the number of tracked endpoints and time buckets rather than by request volume; they stay small even at high traffic. + +This page offers strategies for managing traffic logs; aggregated analytics don't need it. + +As a guideline, every 3 million requests generates roughly 1GB of traffic log data. [Detailed recording](/api-management/logs/traffic-logs#detailed-recording), which captures the full request and response body on every record, multiplies this considerably, and is the single biggest driver of storage growth where it's enabled. + +This creates two different problems, not one: + +- **Persistent storage** accumulates every record you don't evict, so it needs active management over time, covered below. +- **Redis** is different: Tyk Pump continuously reads and purges these records, so the analytics buffer doesn't grow indefinitely under normal operation. Its risk is one of throughput instead: a high request rate, or Tyk Pump falling behind, can spike Redis memory and compete with the request path for resources. See [Separate Analytics Storage](/planning-for-production/database-settings#separate-analytics-storage) for information on isolating analytics traffic onto its own Redis instance. + +## Managing Analytics Storage + +How you control the size of your analytics store depends on which type of storage is in use. + +### MongoDB + +The techniques on this page target `tyk_analytics`, the collection the [Standard Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#standard-mongo-pump) writes every traffic log into, one document per request, shared across all Organisations by default. If you're using the [Per-Organisation Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#per-organisation-mongo-pump) instead, each Organisation gets its own collection, `z_tyk_analyticz_{ORG_ID}`; apply the same techniques to each one individually. + +MongoDB gives you two independent ways to bound the size of these collections: cap by size, or evict records after a fixed time with a TTL index. The two are mutually exclusive: MongoDB won't create a TTL index on a collection that's already capped. + +#### Capped Collections + +You can make use of MongoDB's [capped collection](https://docs.mongodb.com/manual/core/capped-collections/) concept. A capped collection acts as a FIFO buffer: once it reaches its size limit, new records replace the oldest ones rather than the collection continuing to grow. This has no effect on Tyk Dashboard's Traffic Analytics graphs if you're using [pre-computed aggregation](/api-management/dashboard-analytics#pre-computed-vs-live-aggregation); if you're relying on live aggregation instead, capping this collection also shortens the historical window available to those screens. + + +Capped collections are not supported on Amazon DocumentDB. See the [DocumentDB documentation](https://docs.aws.amazon.com/documentdb/latest/developerguide/mongo-apis.html) for details. + + +To have Tyk Pump create a capped collection for you, add the following to the `mongo.meta` object in `pump.conf`: + +```json +{ + "pumps": { + "mongo": { + "type": "mongo", + "meta": { + "collection_cap_enable": true, + "collection_cap_max_size_bytes": 1048577 + } + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `collection_cap_enable` | `false` | If `true`, caps the collection at `collection_cap_max_size_bytes`, turning it into a FIFO buffer. | +| `collection_cap_max_size_bytes` | 5GB | Maximum collection size, in bytes, when `collection_cap_enable` is `true`. | + +Tyk Pump only does this for a collection that doesn't exist yet: on startup, if `tyk_analytics` already exists, Tyk Pump logs a warning and leaves it alone rather than risk data loss by capping it retroactively. + + +The size value is in bytes. We recommend a value just under the amount of RAM on your machine. + + +To convert an existing collection instead, use MongoDB's [convertToCapped](https://docs.mongodb.com/manual/reference/command/convertToCapped/) command directly: + +```javascript +use tyk_analytics +db.runCommand({"convertToCapped": "tyk_analytics", size: 100000}); +``` + +If you're using the [Per-Organisation Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#per-organisation-mongo-pump), run the equivalent command for each Organisation's collection: + +```javascript +db.runCommand({"convertToCapped": "z_tyk_analyticz_", size: 100000}); +``` + +#### TTL Indexes + +As an alternative to capping by size, you can configure MongoDB to delete documents automatically based on a TTL (Time To Live) index. A TTL index can be any date field in a document - once that field's value is in the past, the document will be deleted. This runs in the background, not instantly: MongoDB sweeps for expired documents roughly once a minute. + + +Unlike capped collections, Tyk never creates a TTL index for you, even on a brand-new collection. It's always a manual step, and you need to repeat it for every collection this applies to (each Organisation's collection, if you're using the Per-Organisation Mongo Pump). + + + +If `tyk_analytics` is already a capped collection, MongoDB won't create the TTL index, and you'll see errors in the MongoDB logs. See the [MongoDB TTL documentation](https://docs.mongodb.com/manual/tutorial/expire-data/) for details. + + + +Azure CosmosDB (`mongo_db_type: 2`) does not support the `expireAt` TTL index; Tyk Pump skips creating it on that target automatically. + + +The traffic log contains two fields that can be used for the TTL index: + +- `timestamp` which is set with the current time when the log record is created +- `expireAt` which Tyk Gateway calculates based on a retention period set in the Organisation Key; if no retention period is configured, it defaults to 100 years from creation, so the record effectively never expires via this index + +**Creating the TTL Index** + +You create a TTL index the same way as any other MongoDB index, with: + +```javascript +db..createIndex(, ) +``` + +What makes it a TTL index specifically is adding `expireAfterSeconds` to ``. + +**Setting a Shared TTL for All Traffic Logs** + +If you want the same lifetime for all traffic logs, then use the `timestamp` field for the index, and set `expireAfterSeconds` to the required TTL, in seconds. + +This example keeps the entries in the collection for 30 days (2,592,000 seconds) before deletion: + +```javascript +db.tyk_analytics.createIndex( { "timestamp": 1 }, { expireAfterSeconds: 2592000 } ) +``` + +**Setting Different TTLs per Organisation** + +If the collection contains records created for different Organisations that need different retention periods, we take a different approach. + + +If you're using the [Per-Organisation Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#per-organisation-mongo-pump), each Organisation already has its own collection. Apply the shared-TTL approach above to each collection individually. + + +Use the `expireAt` field for the index and set `expireAfterSeconds` to `0`, so MongoDB deletes each document as soon as its own `expireAt` value is in the past: + +```javascript +db.tyk_analytics.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } ) +``` + +This configures MongoDB to delete the traffic logs once they expire - but you still need to configure Tyk Gateway to set an appropriate retention period in `expireAt` when creating the log. + +The value set in `expireAt` combines the creation timestamp with the `data_expires` value taken from the Organisation Key. The Organisation Key is created using the Tyk Gateway API's [Create an Organisation Key](https://tyk.io/docs/api-reference/organisation-quotas/create-an-organisation-key) endpoint (`POST /tyk/org/keys/{org-id}`), passing a payload such as: + +```json +{ + "org_id": "{your-org-id}", + "data_expires": 86400 +} +``` + +Traffic logs generated for this Organisation will be retained in MongoDB for 24 hours (86400 seconds). + +### SQL + +Unlike MongoDB, the SQL pumps (PostgreSQL and MySQL) have no capped-collection or TTL-index equivalent: there's no built-in way to have Tyk Pump automatically evict old rows. The closest tool is table sharding, which doesn't cap anything by itself, but makes it practical to manage size yourself by dropping old dated tables instead of running slow `DELETE` queries against one huge table. + +#### Table Sharding + +By default, every SQL pump type stores all its records in one table, which becomes slow to query or prune as it grows. Setting `table_sharding: true` switches to one table per day instead, using the pump's table name as a prefix, for example `tyk_analytics_20230327`. + +You must ensure that the equivalent `table_sharding` configuration for each relevant section of Tyk Dashboard's `storage` configuration (`main`, `analytics`, `logs`, `uptime`) matches. + +**Maintaining Consistency** + +When Tyk Pump starts, it checks its sharded tables against the current data model (traffic log schema) and adds any missing columns, for example following an update that adds to the schema; it never drops or renames existing columns. + +When using table sharding, by default it only updates the schema for the current day's table, leaving older dated tables exactly as they were when created. Setting `migrate_sharded_tables: true` automatically scans the database for every table matching this pump's prefix on startup, updating the schema for any that are out of date. If a table fails to migrate, Tyk Pump logs a warning and continues. + + +This scan-and-update runs on every Pump restart, not just once, and it touches every table matching this pump's prefix. + +In a deployment with months or years of daily-sharded tables, that can mean scanning and potentially altering hundreds or thousands of tables. Startup can take a long time, and Tyk Pump won't begin processing analytics again until it completes. + +The sustained read and write load this puts on the database can also affect other services sharing it, not just Tyk Pump's own performance. + +Only enable `migrate_sharded_tables` when you actually need it, such as the first restart after a Tyk Pump upgrade that changed the schema, then turn it back off. + diff --git a/api-management/dashboard-analytics/control-plane-pumps.mdx b/api-management/dashboard-analytics/control-plane-pumps.mdx new file mode 100644 index 0000000000..c161b087da --- /dev/null +++ b/api-management/dashboard-analytics/control-plane-pumps.mdx @@ -0,0 +1,517 @@ +--- +title: "Control Plane Pumps" +description: "Configure the Tyk Pump types that write traffic logs and aggregated analytics into Tyk Dashboard's MongoDB or SQL storage." +keywords: "Control Plane Pumps, Tyk Pump, Dashboard Analytics, Log Browser, Mongo Pump, SQL Pump, Aggregate Pump, GraphQL, MCP" +sidebarTitle: "Control Plane Pumps" +--- + +## Introduction + +This page covers the [Tyk Pump types](/api-management/tyk-pump#pump-type-catalog) that write traffic logs and aggregated analytics into Tyk Dashboard's persistent storage (MongoDB or SQL): the pumps that ultimately feed Tyk Dashboard's Log Browser and Traffic Analytics screens. + +How you deploy these pumps depends on your deployment topology: + +- In a combined control and data plane, such as the `tyk-stack` chart, you configure them directly: Tyk Pump already has access to both Redis and the persistent storage. +- In a distributed deployment, with separate control and data planes connected via Tyk MDCB, these same pump types are instead configured as the Control Plane Pump, running on the control plane, which receives data forwarded by the Hybrid Pump on each data plane. See [Data Plane Pump](/api-management/dashboard-analytics/data-plane-pump) for that mechanism. + +For an explanation of how traffic logging works in general, including detailed recording and aggregation, see the [Dashboard Analytics overview](/api-management/dashboard-analytics). + +For settings common to every pump type, such as `filters` and `timeout`, see [Common Pump Settings](/api-management/tyk-pump#common-pump-settings). + +## Choosing a Pump Type + +Tyk Pump implements separate pump types for MongoDB and SQL because of how differently the two store data. + +- MongoDB groups records into collections, so Tyk Pump has a separate pump type for each way of storing them: one collection holding every Organisation's traffic logs together, one collection per Organisation splitting traffic logs apart, and one collection holding aggregated analytics. +- SQL databases store records as rows in a single table, so there's no equivalent to Mongo's "one collection per Organisation" pump type: every Organisation's rows live in the same table, distinguished by an indexed `org_id` column. + +The pump type that you should deploy depends on which Tyk Dashboard feature you want to populate and the protocol (REST, GraphQL, MCP) the traffic logs come from. + +For Tyk Dashboard's Traffic Analytics screens (Traffic Analytics graphs, Activity by Graph, Activity by MCP): + +| Protocol | MongoDB | SQL | +| :-- | :-- | :-- | +| REST | [`mongo-pump-aggregate`](#mongo-aggregate-pump) | [`sql_aggregate`](#sql-aggregate-pump) | +| GraphQL | not available | [`sql-graph-aggregate`](#sql-graphql-aggregate-pump) (PostgreSQL only) | +| MCP | [`mongo-mcp-aggregate`](#mongo-mcp-aggregate-pump) | [`sql-mcp-aggregate`](#sql-mcp-aggregate-pump) | + +For Tyk Dashboard's Log Browser: + +| Protocol | MongoDB | SQL | +| :-- | :-- | :-- | +| REST | [`mongo`](#standard-mongo-pump) (or [`mongo-pump-selective`](#per-organisation-mongo-pump), which splits Log Browser data into a separate collection per Organisation) | [`sql`](#standard-sql-pump) | +| GraphQL | not available | not available | +| MCP | not available | not available | + +For your own downstream querying: + +| Protocol | MongoDB | SQL | +| :-- | :-- | :-- | +| REST | already covered by [`mongo`](#standard-mongo-pump)/[`mongo-pump-selective`](#per-organisation-mongo-pump) above | already covered by [`sql`](#standard-sql-pump) above | +| GraphQL | [`mongo-graph`](#mongo-graphql-pump) | [`sql-graph`](#sql-graphql-pump) | +| MCP | [`mongo-mcp`](#mongo-mcp-pump) | [`sql-mcp`](#sql-mcp-pump) | + +These pumps write the data into a MongoDB collection or SQL table, where it's queryable by your own tooling. For REST, that's the same `mongo`/`mongo-pump-selective`/`sql` pump you're already running for the Log Browser; GraphQL and MCP need a dedicated pump instead, since the plain pumps either pass GraphQL through without the extra structured detail, or exclude MCP entirely. Tyk Dashboard itself doesn't read any of this back out, so you'll need your own tooling, such as a BI tool or a scheduled export job, connected directly to that database. This is different from the [External Data Sink Pumps](/api-management/tyk-pump#external-data-sink-pumps), which actively forward traffic logs to a separate third-party system such as Splunk or Kafka. + +## MongoDB + +Tyk Pump offers MongoDB pump types for REST, MCP proxy and GraphQL traffic. All share the same connection settings. + +The pumps are declared and configured as described in the [Tyk Pump configuration guide](/api-management/tyk-pump#declaring-pumps). Each pump to be deployed has its own entry in that configuration and has both [common](/api-management/tyk-pump#common-pump-settings) and specific configuration options. The specific config is held in the `meta` object - some of this is common, some is dependent on the specific pump as outlined in the following sections. + +### Common Mongo Meta + +Every Mongo pump type on this page accepts these fields in `meta`, in addition to whatever is listed in its own section below. Each pump's own snippet only shows its additional fields; combine them with these to build a complete `meta` block. + +| Field | Default | Description | +| :-- | :-- | :-- | +| `mongo_url` | - | Full connection URL to your MongoDB instance, including credentials and database name. Can point at a cluster. | +| `mongo_use_ssl` | `false` | Enables a TLS connection to MongoDB. | +| `mongo_ssl_insecure_skip_verify` | `false` | Allows self-signed certificates when `mongo_use_ssl` is enabled. | +| `mongo_ssl_allow_invalid_hostnames` | `false` | Skips the TLS hostname check, useful when connecting through an SSH tunnel; the rest of TLS verification still applies. | +| `mongo_ssl_ca_file` | - | Path to a PEM file containing trusted root certificates. | +| `mongo_ssl_pem_keyfile` | - | Path to a PEM file containing both the client certificate and private key, for mutual TLS. | +| `mongo_db_type` | `0` | Target database: `0` for MongoDB, `1` for Amazon DocumentDB, `2` for CosmosDB. | +| `mongo_session_consistency` | `Strong` | Session consistency mode: `strong`, `monotonic`, or `eventual`. | +| `omit_index_creation` | `false` | If `true`, Tyk Pump never creates indexes. If `false`, it creates them only if the collection doesn't already exist, except on Amazon DocumentDB or Azure CosmosDB, where indexes are always (re-)created, since checking for an existing collection is unreliable on those platforms. | +| `driver` | `mongo-go` | Underlying MongoDB driver: `mongo-go` (MongoDB v4+) or `mgo` (deprecated, MongoDB v4 and below). | +| `mongo_direct_connection` | `false` | If `true`, connects only to the host given in `mongo_url` rather than discovering the rest of the cluster. Useful where network restrictions prevent discovery, such as SSH tunneling. | + + +Only set `mongo_ssl_insecure_skip_verify` to `true` for local development or testing. + +It disables all TLS certificate validation for the connection to MongoDB, not just the hostname check that `mongo_ssl_allow_invalid_hostnames` skips, so Tyk Pump can no longer verify it's actually talking to your MongoDB server. + +This exposes the connection to man-in-the-middle attacks, where an attacker intercepts or tampers with the analytics data in transit. + +If you need to connect to a MongoDB instance with a self-signed certificate in production, add that certificate to `mongo_ssl_ca_file` instead, so Tyk Pump can verify it properly. + + +### Standard Mongo Pump + +The `mongo` pump stores every individual traffic log as a separate document in one collection. It's the source for Tyk Dashboard's Log Browser in a single-Organisation deployment. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-mongo-meta), the pump has the following settings: + +```json +{ + "pumps": { + "mongo": { + "type": "mongo", + "meta": { + "collection_name": "tyk_analytics", // required + "max_insert_batch_size_bytes": 10485760, + "max_document_size_bytes": 10485760, + "collection_cap_enable": false, + "collection_cap_max_size_bytes": 5368709120, + ... + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `collection_name` | - | The collection to write to. Required; there's no default. Set this to `tyk_analytics` unless you're not using the Log Browser at all: Tyk Dashboard has no setting of its own for this name, and always queries `tyk_analytics` directly when `use_sharded_analytics` is `false`, so any other value here means Log Browser finds nothing. | +| `max_insert_batch_size_bytes` | 10MB | Maximum size of a single insert batch (in bytes); larger batches are split into multiple inserts. | +| `max_document_size_bytes` | 10MB | Maximum document size (in bytes); documents that would exceed this are skipped rather than written. | +| `collection_cap_enable` | `false` | If `true`, caps the collection at `collection_cap_max_size_bytes`, turning it into a FIFO buffer. See [Analytics Storage Management](/api-management/dashboard-analytics/analytics-storage-management). | +| `collection_cap_max_size_bytes` | 5GB | Maximum collection size, in bytes, when `collection_cap_enable` is `true`. | + +Because this collection grows with every request, especially with detailed recording enabled, it [should be capped](/api-management/dashboard-analytics/analytics-storage-management). + +**Tyk Dashboard Setting:** set [`use_sharded_analytics: false`](/tyk-dashboard/configuration#use_sharded_analytics) in the Tyk Dashboard configuration so that it queries the `tyk_analytics` collections to populate the Log Browser. + +### Per-Organisation Mongo Pump + +The `mongo-pump-selective` pump stores every individual traffic log as a separate document, in a collection called `z_tyk_analyticz_{ORG_ID}` (i.e. one collection per Organisation). It's the source for Tyk Dashboard's Log Browser in a multi-Organisation deployment, isolating each Organisation's data from the others. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-mongo-meta), the pump has the following settings: + +```json +{ + "pumps": { + "mongo-pump-selective": { + "type": "mongo-pump-selective", + "meta": { + "max_insert_batch_size_bytes": 10485760, + "max_document_size_bytes": 10485760, + ... + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `max_insert_batch_size_bytes` | 10MB | Maximum size of a single insert batch; larger batches are split. | +| `max_document_size_bytes` | 10MB | Maximum document size; oversized documents are skipped rather than written. | + +There's no `collection_name` field: the collection name is always computed per-Organisation. There's also no `collection_cap_*` support, so, unlike the [Standard Mongo Pump](#standard-mongo-pump), Tyk Pump can't cap these collections for you. Given the volume of individual documents, cap each Organisation's collection manually instead, using MongoDB's `convertToCapped` command; see [Analytics Storage Management](/api-management/dashboard-analytics/analytics-storage-management#capped-collections) for the exact command. + +**Tyk Dashboard Setting:** set [`use_sharded_analytics: true`](/tyk-dashboard/configuration#use_sharded_analytics) in the Tyk Dashboard configuration so that it queries `z_tyk_analyticz_{ORG_ID}` collections to populate the Log Browser. + +### Mongo Aggregate Pump + +The `mongo-pump-aggregate` pump computes analytics from traffic logs, aggregating hourly or per minute, and stores them in a collection called `z_tyk_analyticz_aggregate_{ORG_ID}` (i.e. one collection per Organisation). Tyk Dashboard's Traffic Analytics graphs are built on this aggregated analytics. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-mongo-meta), the pump has the following settings: + +```json +{ + "pumps": { + "mongo-pump-aggregate": { + "type": "mongo-pump-aggregate", + "meta": { + "use_mixed_collection": true, + "track_all_paths": false, + "ignore_tag_prefix_list": [], + "threshold_len_tag_list": 1000, + "store_analytics_per_minute": false, + "aggregation_time": 60, + "enable_aggregate_self_healing": false, + "ignore_aggregations": [], + ... + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `use_mixed_collection` | `false` | If `true`, the pump also writes to the org-less `tyk_analytics_aggregates` collection, see note below. | +| `track_all_paths` | `false` | If `true`, stores aggregated data for every endpoint rather than only [tracked endpoints](/api-management/dashboard-analytics#controlling-which-endpoints-are-tracked). | +| `ignore_tag_prefix_list` | (none) | Prefixes of [custom aggregation tags](/api-management/dashboard-analytics#custom-aggregation-tags) to exclude from aggregation. | +| `threshold_len_tag_list` | `1000` | Logs a warning if the number of distinct tag values in an aggregation exceeds this threshold. | +| `store_analytics_per_minute` | `false` | If `true`, aggregates per minute instead of per hour. Takes precedence over `aggregation_time`. | +| `aggregation_time` | `60` | Aggregation window, in minutes (1-60). Ignored if `store_analytics_per_minute` is `true`. | +| `enable_aggregate_self_healing` | `false` | See [Self-Healing](#self-healing) below. | +| `ignore_aggregations` | (none) | Dimensions to exclude from aggregation, to reduce document size. Valid values (case-insensitive): `APIID`, `Errors`, `Versions`, `APIKeys`, `OauthIDs`, `Geo`, `Tags`, `Endpoints`, `KeyEndpoint`, `OauthEndpoint`, `ApiEndpoint`. | + +Because it stores aggregated analytics, this collection needs minimal capping. If an API definition tags unique per-request headers, such as a `request_id`, aggregation creates one document per unique value and the collection can grow rapidly; avoid tagging unique headers where possible, or add them to `ignore_aggregations`. + +**Tyk Dashboard Setting:** this pump supplies the **API Usage Data** screens. Set [enable_aggregate_lookups: true](/tyk-dashboard/configuration#enable_aggregate_lookups) in the Tyk Dashboard configuration so it reads this pre-computed data, rather than computing it live from traffic logs on every request; see [Pre-Computed vs Live Aggregation](/api-management/dashboard-analytics#pre-computed-vs-live-aggregation) for the trade-off. + + +Tyk Dashboard's [`use_sharded_analytics`](/tyk-dashboard/configuration#use_sharded_analytics) controls which collection(s) it queries. If `use_sharded_analytics: false`, you must set `use_mixed_collection: true` so the pump populates the shared collection `tyk_analytics_aggregates`, which Dashboard always reads in this mode. If `use_sharded_analytics: true`, ordinary per-Organisation queries read the per-Organisation collections, which the pump populates regardless of `use_mixed_collection`, but a superuser session not attached to a single Organisation still falls back to the shared collection, so set `use_mixed_collection: true` too if you need those cross-org views to show data. + + +#### Self-Healing + +MongoDB, DocumentDB, and CosmosDB all cap individual document size (16MB for standard MongoDB). The Mongo Aggregate Pump writes one document per `aggregation_time` period, and if that document grows past the limit before the period ends, the database rejects further writes for that period. + +The pump's self-healing feature solves this problem by automatically adjusting the aggregation window for each document, resulting in more, smaller documents for a given time period. The performance trade-off is that this can increase the query complexity and load for Tyk Dashboard when it reads and consolidates this data to render the Dashboard Analytics graphs. + +Set `enable_aggregate_self_healing: true` to trigger self-healing: when a write fails because the document is too large, Tyk Pump creates a new document immediately and halves `aggregation_time` for subsequent periods, reducing the chance of hitting the limit again. This can repeat, halving `aggregation_time` further each time the limit is hit, down to a minimum of 1 minute. For example, with `aggregation_time: 50`, if the document reaches 16MB, Tyk Pump starts a new document and reduces `aggregation_time` to 25. + + +`store_analytics_per_minute` takes precedence over `aggregation_time`. If `store_analytics_per_minute` is `true`, `aggregation_time` is fixed at 1 and self-healing has no effect. + + +### Mongo GraphQL Pump + +The `mongo-graph` pump parses GraphQL-specific detail out of the raw request and response of every GraphQL request: types requested, fields requested per type, operation type, variables, root fields, and any errors. + +The resultant MongoDB collection exists for your own downstream querying, such as with a BI tool or data warehouse; Tyk Dashboard doesn't read this data back out anywhere. + +Enable [detailed recording](/api-management/logs/traffic-logs#detailed-recording) first, so that GraphQL information can be parsed from the request body and response; this applies globally across all APIs on the Gateway. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-mongo-meta), the pump has the following settings: + +```json +{ + "pumps": { + "mongo-graph": { + "type": "mongo-graph", + "meta": { + "collection_name": "tyk_graph_analytics", // required + "max_insert_batch_size_bytes": 10485760, + "max_document_size_bytes": 10485760, + "collection_cap_enable": false, + "collection_cap_max_size_bytes": 5368709120, + ... + }, + ... + } + } +} +``` + +Accepts the same `collection_name`, `max_insert_batch_size_bytes`, `max_document_size_bytes`, and `collection_cap_*` fields as the [Standard Mongo Pump](#standard-mongo-pump); `collection_name` is required, with no default. + + +The [Standard Mongo Pump](#standard-mongo-pump) doesn't filter out traffic logs for GraphQL requests, so they already reach `tyk_analytics` as ordinary, undifferentiated entries. If you point `mongo-graph`'s `collection_name` at the same collection as your Standard Mongo Pump, you'll get duplicate entries in the Log Browser for every GraphQL request, one plain copy and one GraphQL-enriched copy, without gaining anything: Log Browser can't display the extra fields anyway. Always use a separate, dedicated collection. + + +Limitations: + +- Records can grow quickly, since detailed recording is required. +- Subgraph requests in a federation setup are not recorded, only supergraph requests. +- Universal Data Graph requests are recorded, but subsequent requests to data sources are ignored. + +### Mongo MCP Pump + +The `mongo-mcp` pump stores MCP (Model Context Protocol) tool-call traffic logs in their own collection, mirroring the [Standard Mongo Pump](#standard-mongo-pump). **Unlike GraphQL records, MCP records are excluded from the Standard Mongo Pump, so this is the only way to get MCP traffic logs into MongoDB.** + +As with the [Mongo GraphQL Pump](#mongo-graphql-pump), Tyk Dashboard doesn't read this data anywhere, including the Activity by MCP screen; it exists for your own downstream querying. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-mongo-meta), the pump has the following settings: + +```json +{ + "pumps": { + "mongo-mcp": { + "type": "mongo-mcp", + "meta": { + "collection_name": "tyk_mcp_analytics", // required + "max_insert_batch_size_bytes": 10485760, + "max_document_size_bytes": 10485760, + "collection_cap_enable": false, + "collection_cap_max_size_bytes": 5368709120, + ... + }, + ... + } + } +} +``` + +Accepts the same `collection_name`, `max_insert_batch_size_bytes`, `max_document_size_bytes`, and `collection_cap_*` fields as the [Standard Mongo Pump](#standard-mongo-pump); `collection_name` is required, with no default. + +### Mongo MCP Aggregate Pump + +The `mongo-mcp-aggregate` pump computes aggregated MCP analytics, mirroring the [Mongo Aggregate Pump](#mongo-aggregate-pump), and stores them in a collection called `z_tyk_mcp_analyticz_aggregate_{ORG_ID}`. Tyk Dashboard's MCP Traffic Analytics graphs are built on this aggregated analytics. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-mongo-meta), the pump has the following settings: + +```json +{ + "pumps": { + "mongo-mcp-aggregate": { + "type": "mongo-mcp-aggregate", + "meta": { + "use_mixed_collection": true, + "track_all_paths": false, + "ignore_tag_prefix_list": [], + "threshold_len_tag_list": 1000, + "store_analytics_per_minute": false, + "aggregation_time": 60, + "enable_aggregate_self_healing": false, + "ignore_aggregations": [], + ... + }, + ... + } + } +} +``` + +Accepts the same `use_mixed_collection`, `track_all_paths`, `ignore_tag_prefix_list`, `threshold_len_tag_list`, `store_analytics_per_minute`, `aggregation_time`, `enable_aggregate_self_healing`, and `ignore_aggregations` fields as the [Mongo Aggregate Pump](#mongo-aggregate-pump) above. `use_mixed_collection: true` writes to the org-less `tyk_mcp_analytics_aggregate` collection instead of `tyk_analytics_aggregates`. + +**Tyk Dashboard Setting:** this pump supplies the **Activity by MCP** screen. [enable_aggregate_lookups: true](/tyk-dashboard/configuration#enable_aggregate_lookups) is required in the Tyk Dashboard configuration, not just recommended: unlike the REST case, there's no working [live fallback](/api-management/dashboard-analytics#pre-computed-vs-live-aggregation) if it's left `false`, since that fallback has no MCP-specific logic and queries the plain REST traffic log collection, returning nothing useful. See [MCP Analytics](/ai-management/mcp-gateway/mcp-analytics) for the Dashboard side of this feature. + + +Tyk Dashboard's [`use_sharded_analytics`](/tyk-dashboard/configuration#use_sharded_analytics) controls which collection(s) it queries. If `use_sharded_analytics: false`, you must set `use_mixed_collection: true` so the pump populates the shared collection `tyk_mcp_analytics_aggregate`, which Dashboard always reads in this mode. If `use_sharded_analytics: true`, ordinary per-Organisation queries read the per-Organisation collections, which the pump populates regardless of `use_mixed_collection`, but a superuser session not attached to a single Organisation still falls back to the shared collection, so set `use_mixed_collection: true` too if you need those cross-org views to show data. + + +## SQL + +Tyk Pump offers SQL pump types for REST, MCP proxy and GraphQL traffic. All share the same connection settings. + +The pumps are declared and configured as described in the [Tyk Pump configuration guide](/api-management/tyk-pump#declaring-pumps). Each pump to be deployed has its own entry in that configuration and has both [common](/api-management/tyk-pump#common-pump-settings) and specific configuration options. The specific config is held in the `meta` object - some of this is common, some is dependent on the specific pump as outlined in the following sections. + + +Tyk no longer supports SQLite as of Tyk 5.7.0. Transition to [PostgreSQL](/planning-for-production/database-settings#postgresql), [MongoDB](/planning-for-production/database-settings#mongodb), or a listed compatible alternative. + + +### Common SQL Meta + +Every SQL pump type on this page accepts these fields in `meta`, in addition to whatever is listed in its own section below. Each pump's own snippet only shows its additional fields; combine them with these to build a complete `meta` block. + +| Field | Default | Description | +| :-- | :-- | :-- | +| `type` | - | `postgres` or `mysql`. | +| `connection_string` | - | The database connection string, for example `user:password@tcp(hostname:3306)/dbname` for MySQL, or host/port/user/password/dbname for PostgreSQL. | +| `postgres.prefer_simple_protocol` | `false` | Disables implicit prepared statement usage. PostgreSQL only. | +| `mysql.default_string_size` | `256` | Default size for string fields. MySQL only. | +| `mysql.disable_datetime_precision` | `false` | Disables datetime precision, unsupported before MySQL 5.6. | +| `mysql.dont_support_rename_index` | `false` | Drops and recreates an index instead of renaming it; needed before MySQL 5.7, or on MariaDB. | +| `mysql.dont_support_rename_column` | `false` | Uses `CHANGE` instead of `RENAME COLUMN`; needed before MySQL 8, or on MariaDB. | +| `mysql.skip_initialize_with_version` | `false` | Skips auto-configuring behavior based on the connected MySQL version. | +| `table_sharding` | `false` | If `true`, records are stored in one table per day instead of a single table. See [Table Sharding](/api-management/dashboard-analytics/analytics-storage-management#table-sharding). | +| `log_level` | `silent` | SQL log verbosity: `debug`, `info`, or `warning`. Any other value, including `error`, is treated as `silent` (no query logging). | +| `batch_size` | `1000` | Maximum records written per batch. | +| `migrate_sharded_tables` | `false` | See [Table Sharding](/api-management/dashboard-analytics/analytics-storage-management#table-sharding). | + +### Standard SQL Pump + +The `sql` pump stores every individual traffic log as a separate row in one table. It's the source for Tyk Dashboard's Log Browser, for both single- and multi-Organisation deployments: unlike MongoDB, there's no separate per-Organisation pump type, since every Organisation's rows already live in the same table, distinguished by an indexed `org_id` column. + +It has no pump-specific fields beyond [Common SQL Meta](#common-sql-meta) and the [common pump settings](/api-management/tyk-pump#common-pump-settings): set `type` to `postgres` or `mysql` and configure those. + +By default, the pump stores all records in a single table, `tyk_analytics`. With `table_sharding: true`, records are instead stored in per-day tables named `tyk_analytics_YYYYMMDD`. + +**Tyk Dashboard Setting:** the **API Usage Data > Log Browser** screen shows requests recorded by the `sql` pump. Unlike MongoDB, there's no Dashboard setting to configure: the same table is always queried, regardless of Organisation. + +### SQL Aggregate Pump + +The `sql_aggregate` pump computes analytics from traffic logs, aggregating hourly or per minute, and stores them in a table called `tyk_aggregated` by default. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-sql-meta), the pump has the following settings: + +```json +{ + "pumps": { + "sql_aggregate": { + "type": "sql_aggregate", + "meta": { + "track_all_paths": false, + "ignore_tag_prefix_list": [], + "store_analytics_per_minute": false, + "omit_index_creation": false, + ... + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `track_all_paths` | `false` | If `true`, stores aggregated data for every endpoint rather than only [tracked endpoints](/api-management/dashboard-analytics#controlling-which-endpoints-are-tracked). | +| `ignore_tag_prefix_list` | (none) | Prefixes of [custom aggregation tags](/api-management/dashboard-analytics#custom-aggregation-tags) to exclude from aggregation. | +| `store_analytics_per_minute` | `false` | If `true`, aggregates per minute instead of per hour. | +| `omit_index_creation` | `false` | If `true`, Tyk Pump never creates the default indexes. | + +With `table_sharding: true`, records are stored in a `tyk_aggregated_YYYYMMDD` table per day instead of a single `tyk_aggregated` table. + +**Tyk Dashboard Setting:** supplies **Activity by API**, **Activity by Key**, and **Errors**. Set [enable_aggregate_lookups: true](/tyk-dashboard/configuration#enable_aggregate_lookups) in the Tyk Dashboard configuration so it reads this pre-computed data, rather than computing it live from traffic logs on every request, and configure matching SQL connection settings, as above. See [Pre-Computed vs Live Aggregation](/api-management/dashboard-analytics#pre-computed-vs-live-aggregation) for the trade-off. + + +Unlike MongoDB, there's no `use_mixed_collection` equivalent here, and `use_sharded_analytics` has no effect on this pump: Tyk Dashboard always reads the single `tyk_aggregated` table, regardless of Organisation, since SQL storage never splits into per-Organisation tables the way MongoDB can. + + +### SQL GraphQL Pump + +The `sql-graph` pump parses GraphQL-specific detail out of the raw request and response of every GraphQL request, storing it in a dedicated table (`tyk_analytics_graph` by default): types requested, fields requested per type, operation type, variables, root fields, and any errors. + +The resultant table exists for your own downstream querying, such as with a BI tool or data warehouse; Tyk Dashboard doesn't read this data back out anywhere. + +Enable [detailed recording](/api-management/logs/traffic-logs#detailed-recording) first, so that GraphQL information can be parsed from the request body and response; this applies globally across all APIs on the Gateway. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-sql-meta), the pump has the following settings: + +```json +{ + "pumps": { + "sql-graph": { + "type": "sql-graph", + "meta": { + "table_name": "tyk_analytics_graph", + ... + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `table_name` | `tyk_analytics_graph` | Used directly for unsharded setups, or as a table prefix for sharded setups, for example `tyk_analytics_graph_20230327`. | + + +The [Standard SQL Pump](#standard-sql-pump) doesn't filter out traffic logs for GraphQL requests, so they already reach `tyk_analytics` as ordinary, undifferentiated rows. If you point `sql-graph`'s `table_name` at the same table as your Standard SQL Pump, you'll get duplicate rows in the Log Browser for every GraphQL request, one plain copy and one GraphQL-enriched copy, without gaining anything: Log Browser can't display the extra fields anyway. Always use a separate, dedicated table. + + +The same limitations apply as for the [Mongo GraphQL Pump](#mongo-graphql-pump) above. + +### SQL GraphQL Aggregate Pump + +The `sql-graph-aggregate` pump computes aggregated GraphQL-specific analytics, mirroring the [SQL Aggregate Pump](#sql-aggregate-pump), and stores them in a fixed table, `tyk_graph_aggregated`. Unlike the GraphQL pumps that store traffic logs, this one does feed Tyk Dashboard. Unlike Mongo, there's no `mongo-graph-aggregate` equivalent. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-sql-meta), the pump has the following settings: + +```json +{ + "pumps": { + "sql-graph-aggregate": { + "type": "sql-graph-aggregate", + "meta": { + "track_all_paths": false, + "ignore_tag_prefix_list": [], + "store_analytics_per_minute": false, + "omit_index_creation": false, + ... + }, + ... + } + } +} +``` + +Accepts the same `track_all_paths`, `ignore_tag_prefix_list`, `store_analytics_per_minute`, and `omit_index_creation` fields as the [SQL Aggregate Pump](#sql-aggregate-pump) above. The table name is fixed at `tyk_graph_aggregated`; there's no `table_name` override, though [table sharding](/api-management/dashboard-analytics/analytics-storage-management#table-sharding) still applies, storing per-day tables such as `tyk_graph_aggregated_20230327`. + +**Tyk Dashboard Setting:** powers the **Activity by Graph** screen (Popularity by Graph API, Errors by Graph API, All Graph APIs). Tyk Dashboard's Postgres storage driver queries this pump's table directly; there's no separate Dashboard config flag to enable it, and it's PostgreSQL-only. + +### SQL MCP Pump + +The `sql-mcp` pump stores MCP tool-call traffic logs in a dedicated table, `tyk_analytics_mcp` by default, mirroring the [Standard SQL Pump](#standard-sql-pump). **Unlike GraphQL records, MCP records are excluded from the [Standard SQL Pump](#standard-sql-pump), so this is the only way to get MCP traffic logs into SQL.** + +As with the [SQL GraphQL Pump](#sql-graphql-pump), Tyk Dashboard doesn't read this data anywhere, including the Activity by MCP screen; it exists for your own downstream querying. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-sql-meta), the pump has the following settings: + +```json +{ + "pumps": { + "sql-mcp": { + "type": "sql-mcp", + "meta": { + "table_name": "tyk_analytics_mcp", + ... + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `table_name` | `tyk_analytics_mcp` | Used directly for unsharded setups, or as a table prefix for sharded setups. | + +### SQL MCP Aggregate Pump + +The `sql-mcp-aggregate` pump computes aggregated MCP analytics, mirroring the [SQL Aggregate Pump](#sql-aggregate-pump), and stores them in a fixed table, `tyk_mcp_aggregated`; unlike the [SQL MCP Pump](#sql-mcp-pump), this table name isn't configurable. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings) and [common `meta` fields](#common-sql-meta), the pump has the following settings: + +```json +{ + "pumps": { + "sql-mcp-aggregate": { + "type": "sql-mcp-aggregate", + "meta": { + "track_all_paths": false, + "ignore_tag_prefix_list": [], + "store_analytics_per_minute": false, + "omit_index_creation": false, + ... + }, + ... + } + } +} +``` + +Accepts the same `track_all_paths`, `ignore_tag_prefix_list`, `store_analytics_per_minute`, and `omit_index_creation` fields as the [SQL Aggregate Pump](#sql-aggregate-pump) above. The table name is fixed at `tyk_mcp_aggregated`; there's no `table_name` override, though [table sharding](/api-management/dashboard-analytics/analytics-storage-management#table-sharding) still applies, storing per-day tables such as `tyk_mcp_aggregated_20230327`. + +**Tyk Dashboard Setting:** this pump supplies the **Activity by MCP** screen. [enable_aggregate_lookups: true](/tyk-dashboard/configuration#enable_aggregate_lookups) is required in the Tyk Dashboard configuration, not just recommended: unlike the REST case, there's no working [live fallback](/api-management/dashboard-analytics#pre-computed-vs-live-aggregation) if it's left `false`, since that fallback has no MCP-specific logic and queries the plain REST traffic log table, returning nothing useful. See [MCP Analytics](/ai-management/mcp-gateway/mcp-analytics) for the Dashboard side of this feature. diff --git a/api-management/dashboard-analytics/data-plane-pump.mdx b/api-management/dashboard-analytics/data-plane-pump.mdx new file mode 100644 index 0000000000..5bf46794b0 --- /dev/null +++ b/api-management/dashboard-analytics/data-plane-pump.mdx @@ -0,0 +1,219 @@ +--- +title: "Data Plane Pump" +description: "How Tyk Dashboard gets its analytics data when the control plane and data plane(s) are separate, connected via Tyk MDCB, and what the aggregated analytics setting does and does not affect." +keywords: "Data Plane Pump, Distributed Deployment, Tyk MDCB, Hybrid Pump, Aggregated Analytics, forward_analytics_to_pump" +sidebarTitle: "Data Plane Pump" +--- + +In a distributed deployment, the control plane and one or more data planes run separately, often in different regions or clouds, connected via [Tyk MDCB](/api-management/mdcb). This changes how traffic data reaches Tyk Dashboard's persistent storage: instead of Tyk Pump writing directly to MongoDB or SQL, as it does when the control and data planes are combined, a Data Plane Pump (`type: hybrid`) on each data plane is used to forward the data to Tyk MDCB on the control plane, which then writes it to the Control Plane's persistent storage. + +```mermaid +graph TD + G[Data Plane Gateway] -->|Generate Traffic Log| R[Local Redis] + R -->|Hybrid Pump reads traffic logs| H[Hybrid Pump] + H -->|aggregated: true| AG[Aggregated Analytics] + H -->|aggregated: false| TL[Traffic Logs] + AG --> MDCB[Tyk MDCB] + TL --> MDCB + MDCB -->|Aggregated analytics: always| E[Control Plane Persistent Storage] + MDCB -->|Traffic logs, forward_analytics_to_pump: false| W[Tyk MDCB's Built-In Writer] + W --> E + MDCB -->|Traffic logs, forward_analytics_to_pump: true| Q[Control Plane Redis] + Q --> C[Control Plane Pump] + C --> E + C -.->|optional| S[Third-Party Sink] +``` + +## Why a Separate Pump Process + +Tyk Gateway writes traffic logs to its own local Redis, which is not reachable from the control plane. Tyk MDCB never reaches into a data plane's Redis itself. It only receives data pushed to it over the RPC connection Gateways already use for configuration synchronization. + +The Hybrid Pump is a Tyk Pump instance, configured with `type: hybrid`, deployed alongside your data plane Gateways. It reads traffic logs from the local Redis and forwards them, individually or as aggregated analytics, to Tyk MDCB, the same role Tyk Pump plays when the control and data planes are combined, adapted for a deployment where they're separate. + + +Tyk Gateway also has a legacy built-in mechanism for this, enabled with `analytics_config.type: rpc`, which sends traffic logs to Tyk MDCB without a separate pump process. It has no aggregation support and runs inside the Gateway process itself. The Hybrid Pump is the current recommended approach. If you adopt the Hybrid Pump, set `analytics_config.type` to an empty string on your Data Plane Gateways to disable the legacy mechanism, otherwise both compete to send the same traffic logs to Tyk MDCB. + + +## Connecting the Hybrid Pump to Tyk MDCB + +To connect to Tyk MDCB, on the control plane, the Hybrid Pump needs to know where to find it (`connection_string`) and a secret to authenticate with (`api_key`). + +`api_key` is the API key of a Tyk Dashboard user, obtained by registering a user scoped to the same Organisation as your data plane. See [Tyk MDCB](/api-management/mdcb) for that setup. + +Set `use_ssl: true` to encrypt this connection over TLS. The Hybrid Pump is the TLS client and Tyk MDCB the server; there's no client certificate involved, so this only encrypts the connection, it doesn't identify the Pump to MDCB (`api_key` does that). If Tyk MDCB's certificate isn't trusted by your system's CA pool, for example because it's self-signed, set `ssl_insecure_skip_verify: true` rather than skip TLS entirely. + +## Hybrid Pump Meta + +The Hybrid Pump is declared and configured like any other pump, as described in the [Tyk Pump configuration guide](/api-management/tyk-pump#declaring-pumps), with both [common](/api-management/tyk-pump#common-pump-settings) and pump-specific settings. The specific settings live in `meta`: + +```json +{ + "pumps": { + "hybrid": { + "type": "hybrid", + "meta": { + "connection_string": "", // required + "api_key": "", // required + "use_ssl": false, + "ssl_insecure_skip_verify": false, + "rpc_key": "", // unused + "call_timeout": 10, + "rpc_pool_size": 5, + "aggregated": false, + "track_all_paths": false, + "store_analytics_per_minute": false, + "enable_mcp_aggregation": false, + "ignore_tag_prefix_list": [] + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `connection_string` | - | Tyk MDCB's address, as `host:port`, for example `mdcb.example.com:9091`. **Required** | +| `api_key` | - | See [Connecting the Hybrid Pump to Tyk MDCB](#connecting-the-hybrid-pump-to-tyk-mdcb) above. **Required** | +| `use_ssl` | `false` | Connect to Tyk MDCB over TLS. | +| `ssl_insecure_skip_verify` | `false` | Skips TLS certificate verification when `use_ssl` is `true`. | +| `rpc_key` | - | Unused. | +| `call_timeout` | `10` | RPC call timeout, in seconds. | +| `rpc_pool_size` | `5` | RPC connection pool size, see [Tuning the RPC Connection Pool](#tuning-the-rpc-connection-pool) below. | +| `aggregated` | `false` | See [Traffic Logs or Aggregated Analytics](#traffic-logs-or-aggregated-analytics) below. | +| `track_all_paths` | `false` | See [Traffic Logs or Aggregated Analytics](#traffic-logs-or-aggregated-analytics) below. | +| `store_analytics_per_minute` | `false` | See [Traffic Logs or Aggregated Analytics](#traffic-logs-or-aggregated-analytics) below. | +| `enable_mcp_aggregation` | `false` | See [Traffic Logs or Aggregated Analytics](#traffic-logs-or-aggregated-analytics) below. | +| `ignore_tag_prefix_list` | (none) | Prefixes of [custom aggregation tags](/api-management/dashboard-analytics#custom-aggregation-tags) to exclude from aggregation, when `aggregated` is `true`. | + +### Tuning the RPC Connection Pool + +`rpc_pool_size` controls how many concurrent RPC connections the Hybrid Pump opens to Tyk MDCB. Each connection can carry data independently, so a larger pool lets the pump push more data to Tyk MDCB in parallel. + +Consider increasing this value if: + +- You're running high API request volumes and the pump can't keep up with sending data to Tyk MDCB. +- You're sending (non-aggregated) traffic logs, which produce significantly more records than `aggregated` mode. + +Each connection has a real cost on both ends: the Hybrid Pump and Tyk MDCB each allocate a 64KB send buffer and a 64KB receive buffer per connection. Tyk MDCB doesn't cap how many connections it will accept, so this cost accumulates across every data plane connected to it, not just your own. + +Raising `rpc_pool_size` also has diminishing returns past a point: Tyk MDCB only processes up to 8,192 RPC calls concurrently by default, a limit shared across every connection from every data plane. Once that's saturated, more connections just queue for the same shared budget rather than increasing overall throughput. + +There's usually no benefit to raising `rpc_pool_size` beyond your actual concurrency needs. + +## Traffic Logs or Aggregated Analytics + +The Hybrid Pump can send every traffic log to Tyk MDCB, or it can create [aggregated analytics](/api-management/dashboard-analytics#how-aggregation-works). + +The `aggregated` field controls this behavior: + +- `aggregated: false` (default): the Hybrid Pump sends every traffic log to Tyk MDCB individually. This is the appropriate mode to use if you need to see the traffic in Tyk Dashboard's Log Browser. +- `aggregated: true`: the Hybrid Pump summarizes traffic logs, before sending it to the Control Plane. This reduces the bandwidth requirement with less data crossing the (often expensive, sometimes cross-region) link to Tyk MDCB. The side-effect is that **the individual traffic logs are not available to view in the Log Browser**. + + +The `tyk-data-plane` Helm chart sets `aggregated: true` by default, to minimize traffic out of the box. + + +The following settings only take effect when `aggregated: true`; they have no effect on individual traffic logs: + +- `track_all_paths`: aggregate all endpoints, not just [tracked ones](/api-management/dashboard-analytics#controlling-which-endpoints-are-tracked). +- `store_analytics_per_minute`: aggregate per minute rather than per hour. +- `enable_mcp_aggregation`: see [MCP Proxy Traffic](#mcp-proxy-traffic) below. + +## How Tyk MDCB Handles The Data + +Tyk MDCB receives either [traffic logs or aggregated analytics](#traffic-logs-or-aggregated-analytics) from each Hybrid Pump, and writes it into the Control Plane's persistent storage that Tyk Dashboard reads from. + +Aggregated analytics (generated if the Hybrid Pump is configured with `aggregated: true`) are always written directly to persistent storage, for Tyk Dashboard's [Traffic Analytics graphs](/api-management/dashboard-analytics#traffic-analytics). + +Traffic logs (`aggregated: false`) can be handled in one of two ways, controlled by Tyk MDCB's [`forward_analytics_to_pump`](/tyk-multi-data-centre/mdcb-configuration-options#forward_analytics_to_pump) setting: + +| `forward_analytics_to_pump` | Behavior | +| :-- | :-- | +| `false` (default) | Tyk MDCB's [built-in writer](#tyk-mdcbs-built-in-writer) processes the data. | +| `true` | Tyk MDCB stores the traffic logs in the Control Plane Redis instead, for a Control Plane Pump to process. | + +If you need to [export the data](/api-management/logs/external-data-sinks) from the Control Plane to a third-party sink such as Splunk or Datadog, you must first store it in the Control Plane Redis by setting `forward_analytics_to_pump: true`. + + +**`forward_analytics_to_pump` has no effect on aggregated analytics.** Tyk MDCB always writes aggregated analytics straight to persistent storage, whichever way it's set. This means analytics aggregated in the data plane by the Hybrid Pump reliably populate Tyk Dashboard's Traffic Analytics graphs, but cannot be routed onward to a third-party sink through the Control Plane Pump. + + +### Tyk MDCB's Built-In Writer + +Tyk MDCB has much of the same capability as the dedicated Mongo/SQL pumps described in [Control Plane Pumps](/api-management/dashboard-analytics/control-plane-pumps), but not all of it. When `forward_analytics_to_pump` is `false`, this built-in writer will process the traffic logs received from the data planes. + +This facility: + +- Writes traffic logs directly to the persistent storage for the Log Browser +- In parallel, computes and writes aggregated analytics from that same data, for the Traffic Analytics graphs: the same computation the Mongo/SQL Aggregate Pumps perform + +This is configured in Tyk MDCB's own config file, `tyk_sink.conf`, not in `pump.conf`: + +```json +{ + "forward_analytics_to_pump": false, + "analytics": { + "type": "mongo", + ... + }, + "dont_store_selective": false, + "dont_store_aggregate": false, + "track_all_paths": false, + "store_analytics_per_minute": false, + "ignore_tag_prefix_list": [] +} +``` + +The `analytics` object's connection fields exactly mirror [Common Mongo Meta](/api-management/dashboard-analytics/control-plane-pumps#common-mongo-meta) or [Common SQL Meta](/api-management/dashboard-analytics/control-plane-pumps#common-sql-meta), depending on `analytics.type`; see those for the full field list rather than a third copy of it here. + +| Field | Default | Description | +| :-- | :-- | :-- | +| `forward_analytics_to_pump` | `false` | See [How Tyk MDCB Handles The Data](#how-tyk-mdcb-handles-the-data) above. | +| `analytics.type` | `mongo` | Storage backend: `mongo` or `postgres`. | +| `dont_store_selective` | `false` | If `true`, skips writing per-Organisation storage, which otherwise mirrors the [Per-Organisation Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#per-organisation-mongo-pump). | +| `dont_store_aggregate` | `false` | If `true`, skips computing aggregated analytics; only traffic logs are stored, for the Log Browser. | +| `track_all_paths` | `false` | Aggregate all endpoints, not just [tracked ones](/api-management/dashboard-analytics#controlling-which-endpoints-are-tracked). | +| `store_analytics_per_minute` | `false` | Aggregate per minute rather than per hour. | +| `ignore_tag_prefix_list` | (none) | Prefixes of [custom aggregation tags](/api-management/dashboard-analytics#custom-aggregation-tags) to exclude from aggregation. | + +Limitations: + +- **No filtering:** there's no equivalent to a Tyk Pump's `filters` setting (`org_ids`, `api_ids`, `response_codes`, and their `skip_*` counterparts). Every request that reaches Tyk MDCB is stored; you can't exclude specific APIs, Organisations, or response codes. +- **No MySQL support:** `analytics.type` only recognizes `mongo` and `postgres`; any other value, including `mysql`, is accepted but silently falls back to `mongo` rather than failing. +- **No GraphQL awareness:** GraphQL requests pass through as ordinary traffic logs and general aggregated analytics only; see [GraphQL Traffic](#graphql-traffic) below for what's missing. +- **No automatic MCP aggregation:** MCP traffic logs are stored, but not aggregated automatically the way REST traffic is; see [MCP Proxy Traffic](#mcp-proxy-traffic) below for how to populate Activity by MCP. + +If you need MySQL, filtering, or a third-party sink, use a Control Plane Pump instead: set `forward_analytics_to_pump: true` and configure the Control Plane Pump as any other [Control Plane Pump](/api-management/dashboard-analytics/control-plane-pumps). + +## Choosing a Configuration + +This table covers REST traffic; see [GraphQL Traffic](#graphql-traffic) and [MCP Proxy Traffic](#mcp-proxy-traffic) below for how those differ. + +| Goal | Hybrid Pump `aggregated` | Tyk MDCB `forward_analytics_to_pump` | +| :-- | :-- | :-- | +| Traffic Analytics graphs only, minimize WAN traffic | `true` | Either value; has no effect | +| Traffic Analytics graphs and Log Browser | `false` | `false` | +| Traffic Analytics graphs, Log Browser, and third-party sinks | `false` | `true`, with a Control Plane Pump configured to write to both the Control Plane persistent storage and your third-party sink | +| Third-party sink only, minimal Tyk Dashboard dependency | Run a separate [OpenTelemetry](/api-management/traces) integration on the Gateway instead | n/a | + +## GraphQL Traffic + +The mechanics above are for REST traffic. There is no dedicated path through the Hybrid Pump or Tyk MDCB for GraphQL. + +The Hybrid Pump has no GraphQL-specific handling, so GraphQL requests pass through as ordinary, undifferentiated traffic logs: they're included in Log Browser and in the general aggregated analytics like any other request, but they never populate the GraphQL-specific [Activity by Graph](/api-management/dashboard-analytics#activity-by-graph) screen. That screen depends on the `sql-graph-aggregate` pump, which only exists for combined deployments; there's no distributed-deployment equivalent. + +## MCP Proxy Traffic + +The mechanics above are for REST traffic. MCP proxy traffic has its own dedicated path through the Hybrid Pump and Tyk MDCB. But unlike REST, storing MCP traffic logs doesn't also generate aggregated MCP analytics automatically, and Tyk Dashboard's Log Browser never reads MCP data at all, in any topology. + +When `forward_analytics_to_pump` is `false`, Tyk MDCB stores MCP traffic logs in the Control Plane's persistent storage for your own downstream querying only, the same as the [Mongo MCP Pump](/api-management/dashboard-analytics/control-plane-pumps#mongo-mcp-pump) in a combined deployment. It doesn't compute and store MCP aggregated analytics. + +To populate [Activity by MCP](/ai-management/mcp-gateway/mcp-analytics), you can either: + +- Set `enable_mcp_aggregation: true` and `aggregated: true` on the Hybrid Pump. This aggregates MCP traffic logs on the data plane before sending: you gain Activity by MCP, but lose the individual MCP traffic logs. This is the same trade-off as REST API traffic has with `aggregated: true`. +- Keep the Hybrid Pump sending MCP traffic logs individually, set `forward_analytics_to_pump: true`, and configure a Control Plane Pump with an MCP aggregate pump type (`mongo-mcp-aggregate` or `sql-mcp-aggregate`) to compute Activity by MCP's data from that forwarded data. + + +If `aggregated: true` on the Hybrid Pump but `enable_mcp_aggregation` is left `false` (the default), MCP analytics are dropped entirely rather than aggregated. Unlike REST traffic, which still gets aggregated in this mode, MCP traffic gets nothing at all: no aggregated data, and no traffic logs either, since `aggregated: true` already means individual traffic logs of any kind aren't sent. + diff --git a/api-management/dashboard-configuration.mdx b/api-management/dashboard-configuration.mdx new file mode 100644 index 0000000000..b4a68eccd8 --- /dev/null +++ b/api-management/dashboard-configuration.mdx @@ -0,0 +1,110 @@ +--- +title: "Tyk Dashboard Overview" +description: "Learn about Tyk Dashboard, the control plane for managing your Tyk Platform, and how to configure its data storage" +keywords: "Tyk Dashboard, Dashboard Configuration, Data Storage, Platform Management" +sidebarTitle: "Tyk Dashboard" +--- + +## Introduction + +Tyk Dashboard diagram + +The Tyk Dashboard is a web-based interface that serves as the **central management hub for your API ecosystem**. It provides a user-friendly Graphical User Interface (GUI) for configuring, monitoring, and analyzing your APIs managed by Tyk. + +The Dashboard also exposes a **REST API**, allowing for programmatic control and integration with other tools and workflows. + +## Exploring the Dashboard UI + +To get a tour of the Dashboard UI, refer to this [document](/getting-started/using-tyk-dashboard). + +## Exploring the Dashboard API + +The Dashboard is a large, granular REST API with a thin-client web front-end, and if being deployed as part of a Tyk install, serves as the main integration point instead of the Gateway API. + +API Overview + +**The Dashboard API is a superset of the Gateway API**, providing the same functionality, with additional features (anything that can be done in the Dashboard has an API endpoint), and offers some additional advantages: + - The Dashboard API has a granular structure, you can create separate clients easily. + - The API features read/write permissions on a per-endpoint level to have extra control over integrations. + - The API enforces a schema that can be modified and hardened depending on your usage requirements. + +### Types of Dashboard API + +The Dashboard exposes two APIs: + - **Dashboard API**: Is used for operational management of Tyk resources (APIs, policies, keys, etc.). This API offers granular permissions based on user roles. + + To know more about the Dashboard API, refer to the [Tyk Dashboard API documentation](/tyk-dashboard-api). + + - **Dashboard Admin API**: Is used for system-level administration and initial setup tasks like managing organizations, initial user creation, backups/migrations and SSO setup. + + To know more about the Dashboard Admin API, refer to the [Tyk Dashboard Admin API documentation](/dashboard-admin-api). + +### Authenticating with Dashboard APIs + +**Dashboard API** + +The [Tyk Dashboard API](/tyk-dashboard-api) is secured using an `Authorization` header that must be added to each request that is made. The **Tyk Dashboard API Access Credentials** `Authorization` key can be found within the Dashboard UI at the bottom of the **Edit User** section for a user. + +**Dashboard Admin API** + +The Tyk Dashboard Admin API is secured using a shared secret that is set in the `tyk_analytics.conf` file. Calls to the Admin API require the `admin-auth` header to be provided, to differentiate the call from a regular Dashboard API call. + +## Data Storage Solutions + +Tyk Dashboard requires a persistent datastore for its operations. For supported database engines and versions, see [Installation Requirements](/tyk-self-managed/install#requirements). + +Tyk stores a variety of data in 4 separate data storage layers. You can configure each layer separately to use one of our supported database platforms. Alternatively a single platform can be used for all layers. The 4 data storage layers are as follows: +1. **Main**: Stores configurations of: APIs, Policies, Users and User Groups. +2. **Aggregate Analytics**: Data used to display Dashboard charts and [analytics](/api-management/dashboard-analytics#traffic-analytics). +3. **Logs**: When [detailed logging](/api-management/troubleshooting-debugging#capturing-detailed-logs) is enabled, request and response data is logged to storage. These logs can previewed in the Dashboard [log browser](/api-management/dashboard-analytics#activity-logs). +4. **Uptime**: Uptime test analytics. + +Being extensible, Tyk supports storing this data across different databases (MongoDB, MySQL and PostgreSQL etc.). For example, Tyk can be configured to store analytics in PostgreSQL, logs in MongoDB and uptime data in MySQL. + +As illustrated below it can be seen that Tyk Pump writes to one or more external data sources via a Redis store. Conversely, Tyk Dashboard reads this data from the external data sources. + +Tyk Dashboard Pump Architecture + +The following details are required to manage this configuration: +- Data storage layer type +- Database engine +- Database connection string + +### Configure Dashboard to Read from a Data Storage Layer + +Tyk Dashboard has configuration environment variables for each data storage layer in the following format: + +```console +TYK_DB_STORAGE__TYPE +TYK_DB_STORAGE__CONNECTIONSTRING +``` + +where *LAYER* can be *MAIN*, *ANALYTICS*, *LOGS* or *UPTIME*. + +For example, to configure Tyk Dashboard to read logs from a mongo database, the following environment variables are required: + +```console +TYK_DB_STORAGE_LOGS_TYPE=mongo +TYK_DB_STORAGE_LOGS_CONNECTIONSTRING=mongodb://db_host_name:27017/tyk_analytics +``` + +The full set of environment variables are listed below: + +```console +TYK_DB_STORAGE_MAIN_TYPE +TYK_DB_STORAGE_MAIN_CONNECTIONSTRING +TYK_DB_STORAGE_LOGS_TYPE +TYK_DB_STORAGE_LOGS_CONNECTIONSTRING +TYK_DB_STORAGE_ANALYTICS_TYPE +TYK_DB_STORAGE_ANALYTICS_CONNECTIONSTRING +TYK_DB_STORAGE_UPTIME_TYPE +TYK_DB_STORAGE_UPTIME_CONNECTIONSTRING +``` + +It should be noted that Tyk will attempt to use the configuration for the *main* data storage layer when no corresponding configuration is available for logs, uptime or analytics. + +Please refer to the [storage configuration](/tyk-dashboard/configuration#storage) section to explore the parameters for configuring Tyk Dashboard to read from different storage layers. + +### Configure Pump to Write to Data Storage Layers + +Tyk Pump writes traffic logs, aggregated analytics and uptime test results to these same storage layers. See [Control Plane Pumps](/api-management/dashboard-analytics/control-plane-pumps) for configuring Tyk Pump to write logs and analytics, or [Uptime Tests](/planning-for-production/ensure-high-availability/uptime-tests#monitoring-uptime-tests-in-tyk-dashboard) for the uptime storage layer specifically. diff --git a/api-management/data-graph.mdx b/api-management/data-graph.mdx new file mode 100644 index 0000000000..7683988aa9 --- /dev/null +++ b/api-management/data-graph.mdx @@ -0,0 +1,1970 @@ +--- +title: "Universal Data Graph" +description: "Learn how to configure Tyk Data Graph to stitch multiple APIs into a single GraphQL endpoint" +keywords: "UDG, Universal Data Graph, Datasource, Concepts, Arguments, Field Mapping, Header Management, Graphql, Kafka, Rest, Examples" +sidebarTitle: "Universal Data Graph (UDG)" +--- + +## Overview + +The Universal Data Graph (UDG) lets you combine multiple APIs into one universal interface. +With the help of GraphQL you're able to access multiple APIs with a single query. + +It's important to note that you don't even have to build your own GraphQL server. +If you have existing REST APIs all you have to do is configure the UDG. + +With the Universal Data Graph Tyk becomes your central integration point for all your internal as well as external APIs. +In addition to this, the UDG benefits from all existing solutions that already come with your Tyk installation. +That is, your Data Graph will be secure from the start and there's a large array of middleware you can build on to power your Graph. + +Universal Datagraph Overview + +Currently supported DataSources: +- REST +- GraphQL +- SOAP (through the REST datasource) +- Kafka + + + + + To start creating your first Universal Data Graph in Tyk Dashboard, go to "Data Graphs" section of the menu. + + + +Make sure to check some of the resources to help you start: +- [How to create UDG schema](/api-management/data-graph#creating-schema) +- [How to connect data sources](/api-management/data-graph#connect-datasource) +- [How to secure the data graph](/api-management/data-graph#security) + +## Key Concepts + +### Universal Data Graph + +The Universal Data Graph (UDG) introduces a few concepts you should fully understand in order to make full use of it. + +UDG comes with a fully spec compliant GraphQL engine that you don't have to code, you just have to configure it. + +For that you have to define your "DataSources" and might want to add "Field Mappings" as well as "Arguments" to your configuration. +Read on in the sub sections to understand the full picture to use UDG to its full potential. + +To help you, we have put together the following video. + + + +### DataSources + +In most GraphQL implementations you have the concept of Resolvers. +Resolvers are functions that take optional parameters and return (resolve) some data. +Each resolver is attached to a specific type and field. + +DataSources are similar in that they are responsible for loading the data for a certain field and type. +The difference is that with DataSources you simply configure how the engine should fetch the data whereas with traditional GraphQL frameworks you have to implement the function on your own. + +DataSources can be internal as well as external. + +Internal DataSources are APIs that are already managed by Tyk, such as REST or SOAP services configured through the Dashboard. +You can take advantage of Tyk’s rich middleware ecosystem to validate and transform requests and responses for these internal DataSources. + +External DataSources are APIs that you’re not currently managing through Tyk. +For simplicity, you can add them to your data graph without first configuring them as dedicated APIs in Tyk. +If you later decide to apply middleware or other policies, you can easily transition an external DataSource into a managed internal API. + +Head over to the [connect data source](/api-management/data-graph#udg) section to learn about the supported data sources and how to connect them to Tyk. + +### Arguments + +Looking back at the example from the "Field Mappings", you might wonder how to use the "id" argument from the GraphQL query to make the correct REST API call to the user service. + +Here's the schema again: + +```graphql +type Query { + user(id: Int!): User +} + +type User { + id: Int! + name: String +} +``` + +We assume you already have your DataSource attached and now want to configure it so that the path argument gets propagated accordingly. +You need to tell the GraphQL engine that when it comes to resolving the field "user", take the argument with the name "id" and use it in the URL to make the request to the REST API. +You do this by using templating syntax to inject it into the URL. +This is done from the "Configure data source" tab, which will show after clicking a schema argument or object field. +Typing an opening curly brace ( `{` ) will produce a dropdown that contains all available fields and arguments. + +```html +https://example.com/user/{{ .arguments.id }} +``` + +Create New API + +### Field Mappings + +Universal Data Graph can automatically resolve where data source information should go in the GraphQL response as long as the GraphQL schema mirrors the data source response structure. + +Let's assume you have a REST API with a user resource like this: `http://example.com/users/:id` + +The following is an example response: + +```json +{ + "id": 1, + "name": "Martin Buhr" +} +``` + +If GraphQL schema in UDG is set as the following: +```graphql +type Query { + user(id: Int!): User +} + +type User { + id: Int! + name: String +} +``` +and REST data source at attached behind `user(id: Int!)` query, UDG will be able to automatically resolve where `id` and `name` values should be in UDG response. In this case no field mapping is necessary. + + + +GraphQL does not support field names with hyphens (e.g. `"user-name"`). This can be resolved by using field mappings as described below. + + + +Let's assume that the JSON response looked a little different: + +````json +{ + "id": 1, + "user_name": "Martin Buhr" +} +```` + +If this were the JSON response you received from the REST API, you must modify the path for the field "name". +This is achieved by unchecking the "Disable field mapping" checkbox and setting the Path to "user_name". + +Nested paths can be defined using a period ( . ) to separate each segment of the JSON path, *e.g.*, "name.full_name" + +In cases where the JSON response from the data source is wrapped with `[]` like this: + +```json +[ + { + "id": 1, + "name": "Martin Buhr", + "phone-number": "+12 3456 7890" + } +] +``` +UDG will not be able to automatically parse `id`, `name` and `phone-number` and fields mapping needs to be used as well. To get the response from inside the brackets the following syntax has to be used in field mapping: `[0]`. + +It is also possible to use this syntax for nested paths. For example: `[0].user.phone-number` + +#### Field mapping in Tyk Dashboard + +See below how to configure the field mapping for each individual field. + +Field mapping UI + + +#### Field mapping in Tyk API definition + +If you're working with raw Tyk API definition the field mapping settings look like this: + +```json +{"graphql": { + "engine": { + "field_configs": [ + { + "type_name": "User", + "field_name": "phoneNumber", + "disable_default_mapping": false, + "path": [ + "[0]", + "user", + "phone-number" + ] + } + ] + } + } + } +``` + +Notice that even though in Tyk Dashboard the nested path has a syntax with ( . ), in Tyk API definition it becomes an array of strings. + +There's more UDG concepts that would be good to understand when using it for the first time: +* [UDG Arguments](/api-management/data-graph#arguments) +* [UDG Datasources](/api-management/data-graph#udg) + +### Reusing response fields + +When using the UDG, there may be a situation where you want to access an API with data coming from another API. +Consider the following REST APIs: + + - REST API for people: `https://people-api.dev/people` + - REST API for a specific person: `https://people-api.dev/people/{person_id}` + - REST API for driver licenses: `https://driver-license-api.dev/driver-licenses/{driver_license_id}` + +The REST API for a person will give us the following response: +```json +{ + "id": 1, + "name": "John Doe", + "age": 40, + "driverLicenseID": "DL1234" +} +``` + +And the REST API response for driver licenses looks like this: +```json +{ + "id": "DL1234", + "issuedBy": "United Kingdom", + "validUntil": "2040-01-01" +} +``` + +As you can see by looking at the example responses, you could use the `driverLicenseID` from the People API to obtain the driver license data from the Driver License API. + +You also want to design the schema so that it represents the relationship between a person and a driver license. +As the person object is referencing a driver license by its ID, it means that we will need to define the driver license inside the person object as a field. +Consequently, a schema representing such a relationship might look like this: + +```graphql +type Query { + people: [Person] # Data source for people + person(id: Int!): Person # Data Source for a specific person +} + +type Person { + id: Int! + name: String! + age: Int! + driverLicenseID: ID + driverLicense: DriverLicense # Data Source for a driver license +} + +scalar Date + +type DriverLicense { + id: ID! + issuedBy: String! + validUntil: Date! +} +``` + +#### Defining the data source URLs + +Now it's all about defining the data source URLs. + +For the field `Query.people`, you can simply use the URL to the API: +``` +https://people-api.dev/people +``` + +The `Query.person` field needs to use its `id` argument to call the correct API endpoint. + +See [Concept: Arguments](/api-management/data-graph#arguments) to learn more about it. + ``` + https://people-api.dev/people/{{.arguments.id}} + ``` + +To retrieve the driver license data you need to be able to use the `driverLicenseID` from the `Person` object. As we defined the driver license data source on the `Person` object, you can now access all properties from the `Person` object by using the `.object` placeholder. + + + +If you want to access data from the object on which the data source is defined, use the `.object` placeholder (e.g: `.object.id` to access the `id` property from an object). + + + +So the URL for the driver license data source would look like this: +``` +https://driver-license-api.dev/driver-licenses/{{.object.driverLicenseID}} +``` + Use the object placeholder + +#### Result + +A query like: +```graphql +{ + people { + id + name + age + driverLicense { + id + issuedBy + validUntil + } + } +} +``` + +... will now result in something like this: +```json +{ + "data": { + "people": [ + { + "id": 1, + "name": "John Doe", + "age": 40, + "driverLicense": { + "id": "DL1234", + "issuedBy": "United Kingdom", + "validUntil": "2040-01-01" + } + }, + { + "id": 2, + "name": "Jane Doe", + "age": 30, + "driverLicense": { + "id": "DL5555", + "issuedBy": "United Kingdom", + "validUntil": "2035-01-01" + } + } + ] + } +} +``` + + +### Header management + +With Tyk v5.2 the possibilities of managing headers for Universal Data Graph and all associated data sources have been extended. + +#### Global headers for UDG + +Global headers can be configured via Tyk API Definition. The correct place to do that is within `graphql.engine.global_headers` section. For example: + +```json +{ + "graphql": { + "engine": { + "global_headers": [ + { + "key": "global-header", + "value": "example-value" + }, + { + "key": "request-id", + "value": "$tyk_context.request_id" + } + ] + } + } +} +``` + +Global headers now have access to all [request context variables](/api-management/traffic-transformation/request-context-variables). + +By default, any header that is configured as a global header, will be forwarded to all data sources of the UDG. + +#### Data source headers + +Data source headers can be configured via Tyk API Definition and via Tyk Dashboard UI. The correct place to do that is within `graphql.engine.datasources.config.headers` section. For example: + +```json +{ + "engine": { + "data_sources": [ + { + "config": { + "headers": { + "data-source-header": "data-source-header-value", + "datasource1-jwt-claim": "$tyk_context.jwt_claims_datasource1" + } + } + } + ] + } +} +``` + +Data source headers now have access to all [request context variables](/api-management/traffic-transformation/request-context-variables). + +#### Headers priority order + +If a header has a value at the data source and global level, then the data source value takes precedence. + +For example for the below configuration: + +```json +{ + "engine": { + "data_sources": [ + { + "config": { + "headers": { + "example-header": "data-source-value", + "datasource1-jwt-claim": "$tyk_context.jwt_claims_datasource1" + } + } + } + ], + "global_headers": [ + { + "key": "example-header", + "value": "global-header-value" + }, + { + "key": "request-id", + "value": "$tyk_context.request_id" + } + ] + } +} +``` + +The `example-header` header name is used globally and there is also a data source level header, with a different value. Value `data-source-value` will take priority over `global-header-value`, resulting in the following headers being sent to the data source: + +| Header name | Header value | Defined on level | +| :---------------- | :------------------------------------- | :------------------ | +| example-header | data-source-value | data source | +| datasource1 | $tyk_context.jwt_claims_datasource1 | data source | +| request-id | $tyk_context.request_id | global | + +## Connect Data Sources + +### UDG + +Datasources are the fuel to power any Unified Data Graph and the designed schema. + +Datasources can be attached to any field available in the composed UDG schema. They can also be nested within each other. + +You can add Datasources to your Universal Data Graph without adding them to Tyk as a dedicated API. This is useful for getting started but also limited in capabilities. Datasources that are managed within Tyk offer much more flexibility and allow for a much fuller API Management control. + +If you want to add quotas, rate limiting, body transformations etc. to a REST Datasource it is recommended to first import the API to Tyk. + +Supported DataSources: +- REST (Query and Mutation only) +- GraphQL (Query, Mutation, and Subscription) +- SOAP (through the REST DataSource, Query and Mutation only) +- Kafka (Subscription only) + + + + +UDG subscriptions (including SSE) are only supported for data sources of kind **GraphQL** or **Kafka**. REST data sources do not support subscriptions. If your upstream service exposes subscriptions via SSE, configure it as a **GraphQL** data source with the appropriate `subscription_type`. See [GraphQL Subscriptions](/api-management/graphql#graphql-subscriptions) for configuration details. + + +### GraphQL + +The GraphQL Datasource is able to make GraphQL queries to your upstream GraphQL service. In terms of configuration there are no real differences between the GraphQL Datasource and the one for REST with one slight exception. + +#### GraphQL data source at operation root level + +To illustrate this we'll have a look at an example graph. + +Consider the following schema: + +```graphql +type Query { + employee(id: Int!): Employee +} +type Employee { + id: Int! + name: String! +} +``` + +Let's assume we would send the following query to a GraphQL server running this schema: + +```graphql +query TykCEO { + employee(id: 1) { + id + name + } +} +``` + +The response of this query would look like this: + +```json +{ + "data": { + "employee": { + "id": 1, + "name": "Martin Buhr" + } + } +} +``` + +Compared to a REST API one difference is obvious. The response is wrapped in the root field "data". +There's also the possibility of having a root field "errors" but that's another story. +For simplicity reasons the GraphQL Datasource will not return the "data" object but rather extract the "employee" object directly. +So if you want to get the field mappings right you don't have to think about errors or data. +You can assume that your response object looks like this: + +````json +{ + "employee": { + "id": 1, + "name": "Martin Buhr" + } +} +```` + +Compared to a REST API you should be able to identify the key difference here. +The response is wrapped in the field "employee" whereas in a typical REST API you usually don't have this wrapping. + +Because of this, field mappings are by default disabled for REST APIs. +For GraphQL APIs, the mapping is enabled by default and the path is set to the root field name. + +Create New API + +Other than this slight difference what's so special about the GraphQL Datasource to give it a dedicated name? + +The GraphQL Datasource will make specification-compliant GraphQL requests to your GraphQL upstream. When you attach a GraphQL Datasource to a field the Query planner of the Tyk GraphQL engine will collect all the sub fields of a root field in order to send the correct GraphQL query to the upstream. This means you can have multiple GraphQL and REST APIs side by side in the same schema, even nested, and the query planner will always send the correct query/request to each individual upstream to fetch all the data required to return a query response. + +**How does the query planner know which Datasource is responsible for a field?** + +When the query planner enters a field it will check if there is a Datasource attached to it. +If that's the case this Datasource will be responsible for resolving this field. +If there are multiple nested fields underneath this root field they will all be collected and provided to the root field Datasource. + +If however, one of the nested fields has another Datasource attached, ownership of the Datasource will shift to this new "root" field. +After leaving this second root field ownership of the Datasource for resolving fields will again shift back to the first Datasource. + +#### GraphQL data source at type/field level + +In case you want to add GraphQL data source at a lower level of your schema - type/field - the configuration steps are as follows: + +1. Navigate to the field you want the GraphQL data source to be connected to and click on it. +2. From the right-hand side menu choose **GraphQL | Tyk** or **External GraphQL** depending on wheather your data source was previously created in Tyk or if it's an external service. +Provide a data source name and URL. + +Above steps are explained in detail in our [Getting started pages](/api-management/data-graph#connect-datasource). + + +4. Tick the box next to `Add GraphQL operation` to see additional configuration fields. This will allow you to provide a query that will execute against the data source. +5. Write the query in the `Operation` box and if you're using any variables provide those in `Variables` box. + + + + + You can use objects from your Data Graph schema as variables by referring to them using this syntax: `{{.object.code}}` + + + + +Add GQL Operation + +### Kafka + +The Kafka DataSource is able to subscribe to Kafka topics and query the events with GraphQL. + + +The Kafka DataSource utilizes consumer groups to subscribe to the given topics, and inherits all behavior of the consumer group concept. + +Consumer groups are made up of multiple cooperating consumers, and the membership of these groups can change over time. Users can easily add a new consumer to the group to scale the processing load. A consumer can also go offline either for planned maintenance or due to an unexpected failure. Kafka maintains the membership of each group and redistributes work when necessary. + +When multiple consumers are subscribed to a topic and belong to the same consumer group, each consumer in the group will receive messages from a different subset of the partitions in the topic. You should know that if you add more consumers to a single group with a single topic than you have partitions, some consumers will be idle and get no messages. + +#### Basic Configuration + +You can find the full documentation for Kafka DataSource configuration here. + +**broker_addresses** +In order to work with the Kafka DataSource, you first need a running Kafka cluster. The configuration takes a list of known broker addresses and discovers the rest of the cluster. + +``` bash +{ + "broker_addresses": ["localhost:9092"] +} +``` + +**topics** +The Kafka DataSource is able to subscribe to multiple topics at the same time but you should know that the structs of events have to match the same GraphQL schema. + +```bash +{ + "topics": ["product-updates"] +} +``` + +**group_id** +As mentioned earlier, the Kafka DataSource utilizes the consumer group concept to subscribe to topics. We use the `group_id` field to set the consumer group name. + +```bash +{ + "group_id": "product-updates-group" +} +``` + +Multiple APIs can use the same `group_id` or you can run multiple subscription queries using the same API. Please keep in mind that the Kafka DataSource inherits all behaviors of the consumer group concept. + +**client_id** +Finally, we need the `client_id` field to complete the configuration. It is a user-provided string that is sent with every request to the brokers for logging, debugging, and auditing purposes. + +```bash +{ + "client_id": "tyk-kafka-integration" +} +``` + +Here is the final configuration for the Kafka DataSource: + +```bash +{ + "broker_addresses": ["localhost:9092"], + "topics": ["product-updates"], + "group_id": "product-updates-group", + "client_id": "tyk-kafka-integration" +} +``` + +The above configuration object is just a part of the API Definition Object of Tyk Gateway. + +#### Kafka Datasource configuration via Dashboard + +1. Click on the field which should have Kafka datasource attached + +2. From the right-hand side *Configure data source* panel choose KAFKA at the bottom in the *Add a new external data source* section + +Kafkaconfig + +3. Provide datasource name, broker address (at least 1), topics (at least 1), groupID, clientID. Optionally you can also choose Kafka version, balance strategy and field mapping options. + +4. Click *SAVE* button to persist the configuration. + +Once done the field you just configured will show information about data source type and name: + +KafkaList + +##### Subscribing to topics + +The `Subscription` type always defines the top-level fields that consumers can subscribe to. Let's consider the following definition: + +```bash +type Product { + name: String + price: Int + inStock: Int +} + +type Subscription { + productUpdated: Product +} +``` + +The `productUpdated` field will be updated each time a product is updated. Updating a product means a `price` or `inStock` fields of `Product` are updated and an event is published to a Kafka topic. Consumers can subscribe to the `productUpdated` field by sending the following query to the server: + +```bash +subscription Products { + productUpdated { + name + price + inStock + } +} +``` + +You can use any GraphQL client that supports subscriptions. + +##### Publishing events for testing + +In order to test the Kafka DataSource, you can publish the following event to `product-updates` topic: + +```bash +{ + "productUpdated": { + "name": "product1", + "price": 1624, + "inStock": 219 + } +} +``` + +You can use any Kafka client or GUI to publish events to `product-updates`. + +When you change any of the fields, all subscribers of the `productUpdated`kafk field are going to receive the new product info. + +The result should be similar to the following: + +API Menu + + +##### API Definition for the Kafka DataSource + +The Kafka DataSource configuration: + +```bash +{ + "kind": "Kafka", + "name": "kafka-consumer-group", + "internal": false, + "root_fields": [{ + "type": "Subscription", + "fields": [ + "productUpdated" + ] + }], + "config": { + "broker_addresses": [ + "localhost:9092" + ], + "topics": [ + "product-updates" + ], + "group_id": "product-updates-group", + "client_id": "tyk-kafka-integration" + } +} +``` + +Here is a sample API definition for the Kafka DataSource. + +```bash +{ + "created_at": "2022-09-15T16:19:07+03:00", + "api_model": {}, + "api_definition": { + "api_id": "7ec1a1c117f641847c5adddfdcd4630f", + "jwt_issued_at_validation_skew": 0, + "upstream_certificates": {}, + "use_keyless": true, + "enable_coprocess_auth": false, + "base_identity_provided_by": "", + "custom_middleware": { + "pre": [], + "post": [], + "post_key_auth": [], + "auth_check": { + "name": "", + "path": "", + "require_session": false, + "raw_body_only": false + }, + "response": [], + "driver": "", + "id_extractor": { + "extract_from": "", + "extract_with": "", + "extractor_config": {} + } + }, + "disable_quota": false, + "custom_middleware_bundle": "", + "cache_options": { + "cache_timeout": 60, + "enable_cache": true, + "cache_all_safe_requests": false, + "cache_response_codes": [], + "enable_upstream_cache_control": false, + "cache_control_ttl_header": "", + "cache_by_headers": [] + }, + "enable_ip_blacklisting": false, + "tag_headers": [], + "jwt_scope_to_policy_mapping": {}, + "pinned_public_keys": {}, + "expire_analytics_after": 0, + "domain": "", + "openid_options": { + "providers": [], + "segregate_by_client": false + }, + "jwt_policy_field_name": "", + "enable_proxy_protocol": false, + "jwt_default_policies": [], + "active": true, + "jwt_expires_at_validation_skew": 0, + "config_data": {}, + "notifications": { + "shared_secret": "", + "oauth_on_keychange_url": "" + }, + "jwt_client_base_field": "", + "auth": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "check_host_against_uptime_tests": false, + "auth_provider": { + "name": "", + "storage_engine": "", + "meta": {} + }, + "blacklisted_ips": [], + "graphql": { + "schema": "type Product {\n name: String\n price: Int\n inStock: Int\n}\n\ntype Query {\n topProducts(first: Int): [Product]\n}\n\ntype Subscription {\n productUpdated: Product\n}", + "enabled": true, + "engine": { + "field_configs": [{ + "type_name": "Query", + "field_name": "topProducts", + "disable_default_mapping": false, + "path": [ + "topProducts" + ] + }, + { + "type_name": "Subscription", + "field_name": "productUpdated", + "disable_default_mapping": false, + "path": [ + "productUpdated" + ] + } + ], + "data_sources": [{ + "kind": "GraphQL", + "name": "topProducts", + "internal": false, + "root_fields": [{ + "type": "Query", + "fields": [ + "topProducts" + ] + }], + "config": { + "url": "http://localhost:4002/query", + "method": "POST", + "headers": {}, + "default_type_name": "Product" + } + }, + { + "kind": "Kafka", + "name": "kafka-consumer-group", + "internal": false, + "root_fields": [{ + "type": "Subscription", + "fields": [ + "productUpdated" + ] + }], + "config": { + "broker_addresses": [ + "localhost:9092" + ], + "topics": [ + "product-updates" + ], + "group_id": "product-updates-group", + "client_id": "tyk-kafka-integration" + } + } + ] + }, + "type_field_configurations": [], + "execution_mode": "executionEngine", + "proxy": { + "auth_headers": { + "Authorization": "Bearer eyJvcmciOiI2MWI5YmZmZTY4OGJmZWNmZjAyNGU5MzEiLCJpZCI6IjE1ZmNhOTU5YmU0YjRmMDFhYTRlODllNWE5MjczZWZkIiwiaCI6Im11cm11cjY0In0=" + } + }, + "subgraph": { + "sdl": "" + }, + "supergraph": { + "subgraphs": [], + "merged_sdl": "", + "global_headers": {}, + "disable_query_batching": false + }, + "version": "2", + "playground": { + "enabled": false, + "path": "/playground" + }, + "last_schema_update": "2022-09-15T16:45:42.062+03:00" + }, + "hmac_allowed_clock_skew": -1, + "dont_set_quota_on_create": false, + "uptime_tests": { + "check_list": [], + "config": { + "expire_utime_after": 0, + "service_discovery": { + "use_discovery_service": false, + "query_endpoint": "", + "use_nested_query": false, + "parent_data_path": "", + "data_path": "", + "cache_timeout": 60 + }, + "recheck_wait": 0 + } + }, + "enable_jwt": false, + "do_not_track": false, + "name": "Kafka DataSource", + "slug": "kafka-datasource", + "analytics_plugin": {}, + "oauth_meta": { + "allowed_access_types": [], + "allowed_authorize_types": [], + "auth_login_redirect": "" + }, + "CORS": { + "enable": false, + "max_age": 24, + "allow_credentials": false, + "exposed_headers": [], + "allowed_headers": [ + "Origin", + "Accept", + "Content-Type", + "X-Requested-With", + "Authorization" + ], + "options_passthrough": false, + "debug": false, + "allowed_origins": [ + "*" + ], + "allowed_methods": [ + "GET", + "POST", + "HEAD" + ] + }, + "event_handlers": { + "events": {} + }, + "proxy": { + "target_url": "", + "service_discovery": { + "endpoint_returns_list": false, + "cache_timeout": 0, + "parent_data_path": "", + "query_endpoint": "", + "use_discovery_service": false, + "_sd_show_port_path": false, + "target_path": "", + "use_target_list": false, + "use_nested_query": false, + "data_path": "", + "port_data_path": "" + }, + "check_host_against_uptime_tests": false, + "transport": { + "ssl_insecure_skip_verify": false, + "ssl_min_version": 0, + "proxy_url": "", + "ssl_ciphers": [] + }, + "target_list": [], + "preserve_host_header": false, + "strip_listen_path": true, + "enable_load_balancing": false, + "listen_path": "/kafka-datasource/", + "disable_strip_slash": true + }, + "client_certificates": [], + "use_basic_auth": false, + "version_data": { + "not_versioned": true, + "default_version": "", + "versions": { + "Default": { + "name": "Default", + "expires": "", + "paths": { + "ignored": [], + "white_list": [], + "black_list": [] + }, + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [], + "transform": [], + "transform_response": [], + "transform_jq": [], + "transform_jq_response": [], + "transform_headers": [], + "transform_response_headers": [], + "hard_timeouts": [], + "circuit_breakers": [], + "url_rewrites": [], + "virtual": [], + "size_limits": [], + "method_transforms": [], + "track_endpoints": [], + "do_not_track_endpoints": [], + "validate_json": [], + "internal": [] + }, + "global_headers": {}, + "global_headers_remove": [], + "global_response_headers": {}, + "global_response_headers_remove": [], + "ignore_endpoint_case": false, + "global_size_limit": 0, + "override_target": "" + } + } + }, + "jwt_scope_claim_name": "", + "use_standard_auth": false, + "session_lifetime": 0, + "hmac_allowed_algorithms": [], + "disable_rate_limit": false, + "definition": { + "enabled": false, + "name": "", + "default": "", + "location": "header", + "key": "x-api-version", + "strip_path": false, + "strip_versioning_data": false, + "versions": {} + }, + "use_oauth2": false, + "jwt_source": "", + "jwt_signing_method": "", + "jwt_not_before_validation_skew": 0, + "use_go_plugin_auth": false, + "jwt_identity_base_field": "", + "allowed_ips": [], + "request_signing": { + "is_enabled": false, + "secret": "", + "key_id": "", + "algorithm": "", + "header_list": [], + "certificate_id": "", + "signature_header": "" + }, + "org_id": "630899e6688bfe5fd6bbe679", + "enable_ip_whitelisting": false, + "global_rate_limit": { + "rate": 0, + "per": 0 + }, + "protocol": "", + "enable_context_vars": false, + "tags": [], + "basic_auth": { + "disable_caching": false, + "cache_ttl": 0, + "extract_from_body": false, + "body_user_regexp": "", + "body_password_regexp": "" + }, + "listen_port": 0, + "session_provider": { + "name": "", + "storage_engine": "", + "meta": {} + }, + "auth_configs": { + "authToken": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "basic": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "coprocess": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "hmac": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "jwt": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "oauth": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + }, + "oidc": { + "disable_header": false, + "auth_header_name": "Authorization", + "cookie_name": "", + "name": "", + "validate_signature": false, + "use_param": false, + "signature": { + "algorithm": "", + "header": "", + "use_param": false, + "param_name": "", + "secret": "", + "allowed_clock_skew": 0, + "error_code": 0, + "error_message": "" + }, + "use_cookie": false, + "param_name": "", + "use_certificate": false + } + }, + "strip_auth_data": false, + "id": "6323264b688bfe40b7d71ab3", + "certificates": [], + "enable_signature_checking": false, + "use_openid": false, + "internal": false, + "jwt_skip_kid": false, + "enable_batch_request_support": false, + "enable_detailed_recording": false, + "scopes": { + "jwt": {}, + "oidc": {} + }, + "response_processors": [], + "use_mutual_tls_auth": false + }, + "hook_references": [], + "is_site": false, + "sort_by": 0, + "user_group_owners": [], + "user_owners": [] +} +``` + +### REST + +The REST Datasource is a base component of UDG to help you add existing REST APIs to your data graph. By attaching a REST datasource to a field the engine will use the REST resource for resolving. + + +REST data sources only support **Query** and **Mutation** operations. **Subscriptions** (including SSE and WebSockets) are **not supported** for REST kind data sources. + +To use GraphQL subscriptions with UDG (including SSE), you must use a data source of kind **GraphQL** or **Kafka**. See [GraphQL Subscriptions](/api-management/graphql#graphql-subscriptions) for details on configuring SSE subscriptions with a GraphQL data source. + + +We have a video which demoes this functionality for you. + + + +#### Using external REST API as a Datasource + +In order to use an external REST API as a Datasource you need to first navigate to the field which that Datasource should be attached to. + +1. Click on the field which should have a datasource attached +2. From the right-hand side *Configure data source* panel choose REST at the bottom in the *Add a new external data source* section + +ExternalREST + +3. Provide data source name, URL, method to be used. Optionally you can add headers information and configure field mapping + +ExternalRESTdetail + +4. Click the *Save & Update API* button to persist the configuration and generate a REST resolver, to resolve this field at runtime. + +#### Using Tyk REST API as a Datasource + +1. Click on the field which should have a datasource attached +2. From the right-hand side *Configure data source* panel choose *REST | Tyk* dropdown to see all available APIs + +InternalREST + +3. Choose which Tyk REST API you want to attach +4. Provide data source name, endpoint and method to be used. Optionally you can add headers information and configure field mapping + +InternalRESTdetail + +5. Click the *Save & Update API* button to persist the configuration and generate a REST resolver, to resolve this field at runtime. + +Once done the field you just configured will show information about data source type and name: + +datasourcesList + +#### Automatically creating REST UDG configuration based on OAS specification + +Tyk Dashboard users have an option to use Tyk Dashboard API and quickly transform REST API OAS specification into a UDG config and have it published in the Dasboard within seconds. + +See our [Postman collections](https://www.postman.com/tyk-technologies/workspace/tyk-public-workspace/overview) and fork `Tyk Dashboard API v5.1`. + +The endpoint you need to use is: + +```bash +POST /api/data-graphs/data-sources/import +``` + +Request body: + +```json +{ + "type": "string", + "data": "string" +} +``` + +`type` is an enum with the following possible values: + +- openapi +- asyncapi + +To import an OAS specification you need to choose `openapi`. + +If you are using Postman and your OAS document is in `yaml` format you can use a simple pre-request script to transform it into a `string`. + +```bash +pm.environment.set("oas_document", JSON.stringify(``)) +``` + +Then your request body will look like this: + +```json +{ + "type": "openapi", + "data": {{oas_document}} +} +``` + +### Tyk + +Tyk DataSources are exactly the same as GraphQL or REST DataSources. + +The only difference is that you can directly choose an endpoint from your existing APIs using a drop-down. +This makes it easier to set up and prevents typos compared to typing in the URL etc. + +From a technical perspective there's another difference: + +Tyk DataSources make it possible to call into existing APIs on a Tyk Gateway, even if those are marked as internal. +They also add a lot of flexibility as you can add custom middleware, AuthZ as well as AuthN, rate limits, quotas etc. to these. + +In general, it is advised to first add all APIs you'd wish to add to a data graph as a dedicated API to Tyk. +Then in a second step you'd add these to your data graph. + +Then in a second step you'd add these to your data graph. + + + +As of `v3.2.0` internal datasorces (`TykRESTDataSource` and `TykGraphQLDataSource`) will be deprecated at the API level. Please use `HTTPJSONDataSource` or `GraphQLDataSource` respectively. + + + +## Getting Started + +### Overview + + + +In this getting started tutorial we will combine 2 different HTTP services (Users and Reviews) into one single unified UDG API. Instead of querying these two services separately (and probably merging their responses later) we'll use UDG to get result from both the API's in one single response. + +#### Prerequisites + +- Access to Tyk Dashboard +- Node.JS v.13^ (only to follow this example) + +#### Running example services locally + + + +Clone repo + +```bash +git clone https://github.com/jay-deshmukh/example-rest-api-for-udg.git +``` + +Run it locally +```bash +cd example-rest-api-for-udg +``` + +```bash +npm i +``` + +```bash +npm run build +``` + +```bash +npm start +``` + +You should see following in your terminal + +``` +Users Service Running on http://localhost:4000 +Review service running on http://localhost:4001 +``` + +
+ +Now that we have Users service running on port `4000` and Reviews service running on port `4001` let's see how we can combine these two into one single UDG API in following tutorial. + + +### Creating Schema + + + +1. Create API + +To start with a Universal Data Graph from scratch head over to the dashboard and click on “APIs” in the left menu. Then click the `“Add New API”` and `UDG`. You might want to give your Universal Data Graph an individual name (i.e. `User-Reviews-Demo`) + + +2. Set Authentication + +To get started easily we'll set the API to `Keyless(Open)`. To do this, scroll down to the Authentication section. + + + +The API authentication is set to Keyless for demo purposes, it’s not recommended to use this setting in production, we’ll explore how to secure the UDG later in this guide. + + + +3. Configure Schema + +Switch to schema tab in your designer and you should already see a default schema. We will edit the schema as follows to connect with our datasources later. + +```gql +type Mutation { + default: String +} + +type Query { + user(id: String): User +} + +type Review { + id: String + text: String + userId: String + user: User +} + +type User { + id: String + username: String + reviews: [Review] +} + +``` + +You can also import an existing schema using the import feature, file types supported : `gql` , `graphql` and `graphqls`. + +4. Save + +Click on save button and that should create our first UDG API + +
+ +Now if we try to query our UDG API it should error at this moment as we do not have any data-source attached to it, let's see how we can do that in next section. + +### Connect Datasource + + + +Upon navigating to schema tab on API details page you’ll see a split screen view with schema and user interface for available fields to configure the datasource. + +You can attach datasource to each individual field and can also re-use the datasource for multiple fields for performance benefits in case it has similar configuration (it needs to use the same upstream URL and method). + +We will start with attaching datasource to user query using following approach. + +#### 1. Select field to attach datasource. +Upon selecting the `Users` field on type `Query`, you'll see the options to configure that field for following kinds of datasources. + +* REST +* GraphQL +* Kafka + +#### 2. Select datasource type. + +Since our upstream services are REST, we'll select REST as datasource type but other kind of datasources can be used as well: + +* *Use external data source*: Will allow to configure the field to resolve with the external API (outside Tyk environment) +* *Using exiting APIs*: Which will allow to configure the field with the API that already exists in Tyk environment. +* *Re-use already configured data source*: If you already have configured a data source for the same API you can re-use the same data-source. If the data source is reused the endpoint will only be called once by Tyk. + +You can learn more about it [here](#udg) + +#### 3. Configure datasource details. + +Configure the data source with the following fields + +**Name** + + Enter a unique datasource name configuration to reuse it in the future. We will name this as `getUserById` for the given example. +When configuring a datasource name with Tyk Dashboard, a default name is created automatically by concatenating the field name and the GraphQL type name with an underscore symbol in between. For example, _getUserById_Query_. This name is editable and can be changed by the user. + +**URL** + +We will use the URL for our `Users` service which returns details of an user for given `id` i.e `http://localhost:4000/users/:id`. + +To dynamically inject the `id` for every request made, we can use templating syntax and inject `id` with user supplied argument or we can also use session object. + +To avoid typos in template you can use the UI component to automatically create a template for you. You can select from the available argument and object template options from the list generated by input component which is triggered by entering `{` in input. + +To learn more about arguments click [here](#arguments) + +To learn more about reusing response fields click [here](#reusing-response-fields) + +#### 4. Enter datasource name. + +Enter a unique datasource name your configuration to reuse it in the future. We will name this as `getUserById` for the given example + +#### 5. Select HTTP method for the URL. + +You can select the HTTP method for your upstream url. Which should be `GET` in our case. + +#### 6. Add headers (Optional) + +If you upstream expects headers, you can supply them using this. +You can also use templating syntax here to reuse request headers. + +#### 7. Select field mapping + +Keep the field mapping disabled by default. +You can use field mapping to map the API response with your schema. + +You can learn more about field mapping [here](#field-mappings) + +#### 8. Save data source + +It is important to save the datasource configuration in order to reflect the changes in your API definition. +The` “Save & Update API” `button will persist the full API definition. + +#### 9. Update API and Test + +Click Update the API. + +You can now query your UDG API of `user` using the Playground tab in API designer + +```gql +query getUser { + user(id:"1"){ + username + id + reviews { + id + text + user { + id + } + + } + } +} +``` + +The above query should return the response as follows + +```json +{ + "data": { + "user": { + "username": "John Doe", + "id": "1", + "reviews": null + } + } +} +``` + +#### Challenge + +1. Try to resolve `reviews` field on type `Users` +2. Try to resolve `users` field on type `Reviews` + +As you can see our query resolved for user details but returns `null` for `reviews`. + +This happens because we haven't defined datasource on field level for `reviews` on type `User`. + +``` +Notes +- For reviews field on type User +- - Description :: get reviews by userId +- - URL :: http://localhost:4001/reviews/:userId +- - Method :: GET + +- For users field on type Review +- - Description :: get user details by Id +- - URL :: http://localhost:4000/users/:userId +- - Method :: GET + +- You can reuse response filed using templating syntax example `{{.object.id}}` +``` + + + +You can find the solution for the challenge in the above video. + + + +
+ +Now that we have linked datasources for our queries, let's see how we can do the same for mutations in the next section. + + +### Mutations + + + +Now that we have attached datasources to our `Query` in the schema let's try to do the same for `Mutation`. + +#### Steps for Configuration + +1. **Update Schema** + + ```gql + type Mutation { + addReview(text: String, userId: String): Review + deletReview(reviewId: String): String + } + ``` + + We’ll update the Mutatation type as above where we’ll add two operations + + * `addReview`: Which accepts two `arguments` (i.e `text` and `userId`) and adds a new review by making a `POST` request to `http://localhost:4001/reviews` endpoint, which expects something like the following in the request payload + + ``` + { + "id": "1", // UserId of the user posting review + "text": "New Review by John Doe11" // review text + } + ``` + * `deleteReview`: Which accepts one `argument` (i.e `reviewId`), that deletes a review by making a `DELETE` request to `http://localhost:4001/reviews/:reviewId` + +2. **Configure datasource.** + + Follow these steps to configure a data source for the `Mutation`. + + * Navigate to schema tab in the api where you would see the split screen view of schema editor on left and list of configurable fields on right + * Select `addReview` field from `Mutation` type + * Select `REST` option + * Set a unique datasource name + * Set the URL as `http://localhost:4001/reviews` + * Select method type as `POST` + * Set request body to relay the graphql arguments to our upstream payload as follows: + + ``` + { + "text": "{{.arguments.text}}", + "userId": "{{.arguments.userId}}" + } + ``` + * Update the API + +3. **Execute mutation operation** + + We can now test our mutation operation with the playground in API designer using the following operation + + ```gql + mutation AddReview { + addReview(text: "review using udg", userId:"1"){ + id + text + } + } + ``` + + That should return us the following response: + + ```gql + { + "data": { + "addReview": { + "id": "e201e6f3-b582-4772-b95a-d25199b4ab82", + "text": "review using udg" + } + } + } + + ``` + + +#### Challenge + +Configure a datasource to delete a review using review id. + +``` +Notes + +- For users field on type Review +- - Description :: delete review using reviewId +- - URL :: http://localhost:4001/reviews/:reviewId +- - Method :: DELETE + +- Enable field mapping to map your API response + +``` + + +You can find the solution for the challenge in the above video. + + + +
+ +Now that we have a good idea how we could do CRUD operations with UDG APIs, let's see how we can secure them using policies + +### Security + + + +Due to the nature of graphql, clients can craft complex or large queries which can cause your upstream APIs to go down or have performance issues. + +Some of the common strategies to mitigate these risks include + +- Rate limiting +- Throttling +- Query depth limiting + + +For this tutorial we'll mitigate these risks using `Query Depth Limit` but you can also use common strategies like rate limiting and throttling, which you can read more about [here](/api-management/rate-limit) + +#### Steps for Configuration + +1. **Set authentication mode** + + In you Api designer core settings tab scroll down to Authentication section and set the authentication mode `Authentication Token` and update the API. + + Our API is not open and keyless anymore and would need appropriate Authentication token to execute queries. + +2. **Applying to query depth** + + Currently if users want they could run queries with unlimited depth as follows + + ```gql + query getUser { + user(id: "1") { + reviews { + user { + reviews { + user { + reviews { + user { + reviews { + user { + id + } + } + } + } + } + } + } + } + } + } + + ``` + + To avoid these kind of scenarios we will set query depth limit on the keys created to access this API. + + Although we can directly create keys by selecting this API but we'll use a Policy as it will make it easier to update keys for this API in future. You can read more about Policies [here](/api-management/policies) + + **Create Policy** + - Navigate to policies page + - Click Add Policy + - Select our API from Access Rights table + - Expand `Global Limits and Quota` section + - Unselect `Unlimited Query Depth` and set limit to `5` + - Switch to configuration tab + - Set policy name (eg. user-reviews-policy) + - Set expiration date for the keys that would be created using this policy + - Click on create policy + + **Create a key using above policy** + - Navigate to keys page + - Click Add Key + - Select our newly created policy + - Click create key + - Copy the key ID + + Now if you try to query our UDG API using the key you should see an error as follows + + ```json + { + "error": "depth limit exceeded" + } + ``` + + + +Watch the video above to see how you can use these policies to publish your UDG APIs on your portal with documentation and playground. + + + +### Field Based Permissions + + + +It is also possible to restrict user's based on fields using policies. For example you can create two policies + +1. For read-only access for users to only execute queries. + +2. For read and write access to run mutations and queries both. + +#### Creating keys with read-only access + +**Create Policy** + +- Navigate to policies page +- Click Add Policy +- Select our API from Access Rights table +- Expand Api panel under global section +- Toggle Field-Based Permissions and check Mutation +- Switch to configuration tab +- Set policy name (eg. user-reviews-policy-read-only) +- Set expiration date for the keys that would be created using this policy +- Click on create policy + +Now keys created using these policies cannot be used for mutations. + +### Header Forwarding + +**Min Version: Tyk v3.2.0** + +You’re able to configure upstream Headers dynamically, that is, you’re able to inject Headers from the client request into UDG upstream requests. For example, it can be used to access protected upstreams. + +The syntax for this is straight forward: + +``` +{{.request.headers.someheader}} +``` + + In your data sources, define your new Header name and then declare which request header's value to use: + + Forwarding Headers + + That's it! + + + +A JSON string has to be escaped before using as a header value. For example: +``` +{\"hello\":\"world\"} +``` + + + +### UDG Examples + +It is possible to import various UDG examples from the [official Tyk examples repository](https://github.com/TykTechnologies/tyk-examples). + +We offer 3 ways of importing an example into Tyk: + - Using [tyk-sync](/api-management/sync/use-cases#synchronize-api-configurations-with-github-actions) + - Manually import via [Dashboard API Import](/api-management/gateway-config-managing-classic#import-an-api) +- Using Tyk Dashboard to browse and import the examples directly + +#### Import via tyk-sync + +Please follow the [tyk-sync documentation](/product-stack/tyk-sync/commands#examples-publish-command) to learn more about this approach. + +#### Import via Tyk Dashboard API Import + +Navigate to an example inside the [examples repository](https://github.com/TykTechnologies/tyk-examples) and grab the relevant API definition from there. +Then you can move in the Dashboard UI to `APIs -> Import API` and select `Tyk API` as source format. + +Paste the API definition inside the text box and hit `Import API`. + +You can find more detailed instructions in the [Dashboard API Import documentation section](/api-management/gateway-config-managing-classic#import-an-api). + +#### Import via Tyk Dashboard UI + +Navigate to `Data Graphs` section of the Tyk Dashboard menu. If you haven't yet created any Universal Data Graphs you will see three options in the screen - one of them `Try example data graph` - will allow you to browse all examples compatible with your Dashboard version and choose the one you want to import. + +Examples in Dashboard + +In case you have created data graphs before and your screen looks different, just use the `Add Data Graph` button and in the next step decide if you want to create one yourself, or use one of the available examples. + +Examples in Dashboard New Graph +## Data Graphs API + +Currently `/api/data-graphs/` has only one endpoint called `/data-sources` with only a `POST` HTTP method. + +The Dashboard exposes the `/api/data-graphs/data-sources/import` endpoint which allows you to import an [AsyncAPI](https://www.asyncapi.com/docs/reference/specification/v3.0.0) or [OpenAPI](https://swagger.io/specification/) document. + +### Supported AsyncAPI versions +* 2.0.0 +* 2.1.0 +* 2.2.0 +* 2.3.0 +* 2.4.0 + +### Supported OpenAPI versions +* 3.0.0 + +### Import a document from a remote resource + +| **Property** | **Description** | +| :-------------- | :-------------------------------------------- | +| Resource URL | `/api/data-graphs/data-sources/import` | +| Method | `POST` | +| Content-Type | `application/json` | +| Body | `{`
` "url": "resource URL" `
`}` | + +The fetched document can be an OpenAPI or AsyncAPI document. The format will be detected automatically. The data source import API only checks the fetched data and tries to determine the document format, the status codes are ignored. +It returns an error if it fails to determine the format and the document type. HTTP 500 is returned if a programming or network error occurs. If the fetched request body is malformed then HTTP 400 is returned. + +### Import an OpenAPI document + +The data source import API supports importing OpenAPI documents. The document can be used as a request body. + +| **Property** | **Description** | +| :-------------- | :------------------------------------------- | +| Resource URL | `/api/data-graphs/data-sources/import` | +| Method | `POST` | +| Content-Type | `application/vnd.tyk.udg.v2.openapi` | +| Body | `` | + + +The document can be in JSON or YAML format. The import API can determine the type and parse it. + +### Import an AsyncAPI document + +The data source import API supports importing AsyncAPI documents. The document can be used as a request body. + +| **Property** | **Description** | +| :-------------- | :---------------------------------------- | +| Resource URL | `/api/data-graphs/data-sources/import` | +| Method | `POST` | +| Content-Type | `application/vnd.tyk.udg.v2.asyncapi` | +| Body | `` | + +The document can be in JSON or YAML format. The import API can determine the type and parse it. + +### Response Structure + +The response structure is consistent with other endpoints, as shown in the table below: + +| **Property** | **Description** | +| :-------------- | :------------------------------------------------------- | +| Status | `Error` or `OK` | +| Message | Verbal explanation | +| Meta | API ID for success and `null` with error (not in use) | + +**Sample Response** + +```json +{ + "Status": "OK", + "Message": "Data source imported", + "Meta": "64102568f2c734bd2c0b8f99" +} +``` + diff --git a/api-management/enable-audit-logs-dashboard.mdx b/api-management/enable-audit-logs-dashboard.mdx new file mode 100644 index 0000000000..4d687073aa --- /dev/null +++ b/api-management/enable-audit-logs-dashboard.mdx @@ -0,0 +1,157 @@ +--- +title: "Enable and View Audit Logs in Tyk Dashboard" +description: "Learn how to enable audit logging in Tyk Dashboard using database storage and view audit logs through the Dashboard UI." +keywords: "Audit Logs, Tyk Dashboard, database storage, compliance, security, self-managed" +sidebarTitle: "Enable Audit Logs" +--- + +## Availability + +| Component | Version | Edition | +| :-------- | :------ | :------- | +| Tyk Dashboard | Available since [v5.7.0](/developer-support/release-notes/dashboard#5-7-0-release-notes) | Enterprise | + +## Prerequisites + +1. **Dashboard License**: [Contact our team](https://tyk.io/contact/) to obtain a license or get a self-managed trial license by completing the registration on our [website](https://tyk.io/self-managed-trial/). +2. **Working Tyk Environment**: You need access to a running Tyk instance. For quick setup instructions using Docker, please refer to the [Tyk Getting Started Guide](/getting-started/quick-start). +3. **Admin Access**: You need admin permissions to configure audit logging and access audit log data. + +## What We Will Do + +In this guide, we will: +1. Enable [audit logs in Tyk Dashboard](/api-management/logs/audit-logs) using the [database storage](/api-management/logs/audit-logs#audit-log-storage) option +2. View the audit logs through the Dashboard UI +3. Learn how to [access audit logs via the API](/api-management/logs/audit-logs#viewing-and-retrieving-audit-logs) + + +This guide is focused on **self-managed / on-premise** installations. For Tyk Cloud deployments, refer to the [Tyk Cloud Audit Logs guide](/api-management/cloud/audit-logs). + + +## Instructions + +### 1. Configure Audit Logging with Database Storage + +To enable audit logging with database storage, you need to update your Tyk Dashboard configuration. Database storage is recommended as it enables viewing logs directly in the Dashboard UI. + + + + +Add the following `audit` section to your `tyk_analytics.conf` file: + +```json +{ + "audit": { + "enabled": true, + "format": "json", + "store_type": "db", + "detailed_recording": false + } +} +``` + + + + +Set the following environment variables: + +```bash +TYK_DB_AUDIT_ENABLED=true +TYK_DB_AUDIT_FORMAT=json +TYK_DB_AUDIT_STORETYPE=db +TYK_DB_AUDIT_DETAILEDRECORDING=false +``` + + + + +For more information on configuration options and default values, refer to the [Audit Logs Configuration Reference](/tyk-dashboard/configuration#audit). + +### 2. Restart Tyk Dashboard + +After updating the configuration, restart your Tyk Dashboard service for the changes to take effect: + +```bash +# For systemd +sudo systemctl restart tyk-dashboard + +# For Docker +docker restart tyk-dashboard + +# For Kubernetes +kubectl rollout restart deployment tyk-dashboard -n tyk +``` + +### 3. View Audit Logs in Dashboard UI + +Once audit logging is enabled with database storage, you can view logs directly in the Dashboard: + +1. Log in to your Tyk Dashboard +2. Interact with the system (e.g., create/update APIs, users, policies) to generate audit log entries +3. Go to **System Management** > **Audit Logs** to see the recorded logs +4. Use the available filters to search and filter logs: + - **Date range**: Filter logs by time period + - **User**: Filter by the user who performed the action + - **IP address**: Filter by the originating IP address + - **HTTP method**: Filter by http method + + Dashboard Audit Logs + +### 5. Access Audit Logs via API + +You can also retrieve audit logs programmatically using the Dashboard API: + +```bash +curl -X GET "https://your-dashboard.com/api/audit-logs" \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" +``` + +For more information on the API endpoint, refer to the [Dashboard API Reference](https://tyk.io/docs/api-reference/auditlogs/list-audit-logs). + +## Troubleshooting + + + + + +Users need the appropriate RBAC permissions to view audit logs. + +#### Via Dashboard UI + +1. Navigate to **Users** in your Tyk Dashboard +2. Select the user you want to grant access +3. In the **Permissions** section, enable **Audit Logs** with `read` access +4. Save the user + +#### Via Dashboard API + +Include `audit_logs` in the user's permissions object when creating or updating a user: + +```json +{ + "user_permissions": { + "IsAdmin": "false", + "analytics": "read", + "apis": "write", + "audit_logs": "read" + } +} +``` + + +Only `read` access is available for audit logs. Write or delete operations are not permitted to maintain audit integrity. + + + + + + +1. **Verify configuration**: Ensure `audit.enabled` is set to `true` in your configuration +2. **Check storage type**: Confirm `audit.store_type` is set to `db` for UI access +3. **Restart Dashboard**: Configuration changes require a restart to take effect +4. **Check version**: Database storage requires Tyk Dashboard v5.7.0 or later + + + + diff --git a/api-management/endpoint-designer.mdx b/api-management/endpoint-designer.mdx new file mode 100644 index 0000000000..47ac5b1e33 --- /dev/null +++ b/api-management/endpoint-designer.mdx @@ -0,0 +1,101 @@ +--- +title: "API Endpoint Designer" +description: "Learn how to use the Tyk Dashboard's API Endpoint Designer, a graphical environment for configuring your Tyk Classic APIs" +keywords: "Endpoint Designer, Tyk Classic, API Designer, Core Settings, Versions, Advanced Options, Uptime Tests, Debugging" +sidebarTitle: "Endpoint Designer" +--- + +## Classic APIs + +Tyk Dashboard's Endpoint Designer provides a graphical environment for the creation and update of your Tyk Classic APIs. + +The Endpoint Designer allows to configure all elements of your Tyk Classic API and consists of several tabs, plus a **Raw Definition** view which allows you to directly edit the Tyk Classic API Definition (in JSON format). Note that + +## Core Settings + +The Tyk Classic Endpoint Designer - Core Settings tab + +The **Core Settings** tab provides access to configure basic settings for the API: +- [Detailed logging](/api-management/logs/traffic-logs#detailed-recording) +- API Settings including + - Listen path + - [API Categories](/platform-management/api-categories) +- Upstream settings including + - Upstream service (target) URL + - [Service Discovery](/planning-for-production/ensure-high-availability/service-discovery) +- [API Ownership](/platform-management/api-ownership) +- [API level rate limiting](/api-management/rate-limit#configuring-the-rate-limiter-at-the-api-level) +- [Authentication](/api-management/client-authentication) + +## Versions + +The Tyk Classic Endpoint Designer - Versions tab + +The **Versions** tab allows you to create and manage [API versioning](/api-management/gateway-config-tyk-classic#tyk-classic-api-versioning) for the API. + +At the top of the Endpoint Designer, you can see which version you are currently editing. If you have more than one option, selecting it from the drop-down will load its endpoint configuration into the editor. + +## Endpoint Designer + +The Tyk Classic Endpoint Designer - Endpoint Designer tab + +The **Endpoint Designer** is where you can define endpoints for your API so that you can enable and configure Tyk middleware to [perform checks and transformations](/api-management/traffic-transformation) on the API traffic. + +In some cases, you will want to set global settings that affect all paths that are managed by Tyk. The **Global Version Settings** section will enable you to configure API-level [request](/api-management/traffic-transformation/request-headers#tyk-classic-api) and [response](/api-management/traffic-transformation/request-headers#tyk-classic-api) header transformation. + +## Advanced Options + +The Tyk Classic Endpoint Designer - Advanced Options tab + +The **Advanced Options** tab is where you can configure Tyk's other powerful features including: +- Upstream certificate management +- [API-level caching](/api-management/response-caching#configuring-the-cache-via-the-dashboard) including a button to invalidate (flush) the cache for the API +- [CORS](/api-management/gateway-config-tyk-classic#cross-origin-resource-sharing-cors) +- Add custom attributes to the API definition as *config data* that can be accessed by middleware +- Enable [context variables](/api-management/traffic-transformation/request-context-variables) so that they are extracted from requests and made available to middleware +- Manage *segment tags* if you are working with [sharded gateways](/api-management/api-sharding#gateway-sharding) +- Manage client IP address [allow](/api-management/gateway-config-tyk-classic#ip-access-control) and [block](/api-management/gateway-config-tyk-classic#ip-access-control) lists +- Attach [webhooks](/api-management/gateway-events#event-handling-with-webhooks) that will be triggered for different events + +## Uptime Tests + +The Tyk Classic Endpoint Designer - Uptime Tests tab + +In the **Uptime Tests** tab you can configure Tyk's [Uptime Test](/api-management/gateway-config-tyk-classic#uptime-tests) functionality + +## Debugging + +The Tyk Classic Endpoint Designer - Debugging tab + +The **Debugging** tab allows you to test your endpoints before you publish or update them. You can also use it for testing any middleware plugins you have implemented. Any debugging you create will persist while still in the current API, enabling you to make changes in the rest of the API settings without losing the debugging scenario. + +The Debugging tab consists of the following sections: + +- Request +- Response +- Logs + +#### Request + +Debugging Request + +In this section, you can enter the following information: + +- Method - select the method for your test from the drop-down list +- Path - your endpoint to test +- Headers/Body - enter any header information, such as Authorization, etc. Enter any body information. For example, entering user information if creating/updating a user. + +Once you have entered all the requested information, click **Run**. Debugging Response and Log information will be displayed: + +#### Response + +Debugging Response + +The Response section shows the JSON response to your request. + +#### Logs + +Debugging Logs + +The debugging level is set to **debug** for the request. This outputs all logging information in the Endpoint Designer. In the Tyk Gateway logs you will see a single request. Any Error messages will be displayed at the bottom of the Logs output. + diff --git a/api-management/event-driven-apis.mdx b/api-management/event-driven-apis.mdx new file mode 100644 index 0000000000..cf22a31b3a --- /dev/null +++ b/api-management/event-driven-apis.mdx @@ -0,0 +1,1099 @@ +--- +title: "Tyk Streams – Manage Event-Driven APIs" +description: "Introduction to Tyk Streams" +keywords: "Tyk Streams, Glossary, Use Cases, Asynchronus APIs, Async, Configuration" +sidebarTitle: "Tyk Streams" +--- + +{/* ## TODO: Add availability */} + +## Overview + +*Tyk Streams* is a feature of the Tyk API management platform that enables organizations to securely expose, +manage and monetize real-time event streams and asynchronous APIs. + +With *Tyk Streams*, you can easily connect to event brokers and streaming platforms, such as +[Apache Kafka](https://kafka.apache.org), and expose them as +managed API endpoints for internal and external consumers. + +
+Tyk Streams Overview +
+ +The purpose of Tyk Streams is to provide a unified platform for managing both synchronous APIs (such as REST and +GraphQL) and asynchronous APIs, in addition to event-driven architectures. This allows organizations to leverage the +full potential of their event-driven systems while maintaining the same level of security, control and visibility they +expect from their API management solution. + +### Why use Tyk Streams + +Tyk Stream is a powerful stream processing engine integrated into the Tyk API Gateway, available as part of the Enterprise Edition. It allows you to manage asynchronous APIs and event streams as part of your API ecosystem. It provides a range of capabilities to support async API management, including: + +- **Protocol Mediation**: Tyk Streams can mediate between different asynchronous protocols and API styles, such as WebSocket, Server-Sent Events (SSE), and Webhooks. This allows you to expose your event streams in a format compatible with your consumers' requirements. +- **Security**: Apply the same security policies and controls to your async APIs as you do to your synchronous APIs. This includes features like authentication and authorization. +- **Transformations**: Transform and enrich your event data on the fly using Tyk's powerful middleware and plugin system. This allows you to adapt your event streams to meet the needs of different consumers. +- **Analytics**: Monitor the usage and performance of your async APIs with detailed analytics and reporting. Gain insights into consumer behavior and system health. +- **Developer Portal**: Publish your async APIs to the Tyk Developer Portal, which provides a centralised catalog for discovery, documentation, and subscription management. + +--- +## Getting Started + +This guide will help you implement your first event-driven API with Tyk in under 15 minutes. To illustrate the capabilities of Tyk Streams, let's consider an example: building a basic asynchronous chat application, nicknamed **Chat Jippity**. + +In this scenario, a user sends a message (e.g., asking for a joke) via a simple web interface and receives an asynchronous response generated by a backend service. + +This application flow demonstrates two key patterns enabled by Tyk Streams: acting as an **API Producer Gateway** and an **API Consumer Gateway**. + +```mermaid +sequenceDiagram + participant Browser + participant Tyk Gateway + participant Joker Service + + Note over Browser, Tyk Gateway: Consumer - SSE Connection Setup + Browser->>+Tyk Gateway: Make Server Side Events (SSE) Connection + Tyk Gateway-->>-Browser: Connection Established + + Note over Browser, discKafka: Producer - Message Flow for Request/Response + Browser->>+Tyk Gateway: (1) POST /chat (Request: "Tell me a joke") + Tyk Gateway->>+chatKafka: (2) Publish message to 'chat' topic + chatKafka-->>-Tyk Gateway: Ack (implied) + chatKafka-->>+Joker Service: (3) Consume message from 'chat' topic + Note over Joker Service: Processes request, gets joke + Joker Service->>+discKafka: (4) Publish response to 'discussion' topic + discKafka-->>-Joker Service: Ack (implied) + discKafka-->>+Tyk Gateway: (6) Consume/Receive message from 'discussion' topic + Tyk Gateway-->>-Browser: (7) Push joke response (via established WS/SSE connection) + Tyk Gateway-->>-discKafka: Ack (implied) +``` + +Let's break down how Tyk Streams facilitates this, focusing on the distinct producer and consumer roles Tyk plays: + +### Example Scenario + +#### Tyk as an API Producer Gateway (Client to Stream) + +* **Goal:** Allow a client (like a browser or a script) to easily send a message into our asynchronous system without needing direct access or knowledge of the backend message broker (Kafka in this case). +* **Scenario:** The user types "Tell me a joke" into the chat interface and hits send. +* **Flow:** + 1. The browser sends a standard HTTP `POST` request to an endpoint exposed by Tyk Gateway (e.g., `/chat`). + 2. **Tyk Streams Role (Producer):** Tyk Gateway receives this `POST` request. An API definition configured with Tyk Streams defines this endpoint as an *input*. Tyk takes the request payload and *publishes* it as a message onto a designated backend topic (e.g., the `chat` topic in Kafka). + 3. A backend service (our "Joker Service") listens to the `chat` topic for incoming requests. +* **Value Demonstrated:** + * **Protocol Bridging:** Tyk translates a synchronous HTTP POST into an asynchronous Kafka message. + * **Decoupling:** The browser only needs to know the Tyk HTTP endpoint, not the Kafka details (brokers, topic name, protocol). + * **API Management:** Tyk can enforce authentication, rate limits, etc., on the `/chat` endpoint before the message even enters the Kafka system. + +#### Tyk as an API Consumer Gateway (Stream to Client) + +* **Goal:** Deliver the asynchronous response (the joke) from the backend system to the client in real time. +* **Scenario:** The "Joker Service" has processed the request and generated a joke. It needs to send this back to the originating user's browser session. +* **Flow:** + 1. The Joker Service *publishes* the joke response as a message onto a different backend topic (e.g., the `discussion` topic in Kafka). + 2. **Tyk Streams Role (Consumer):** Tyk Gateway is configured via another (or the same) API definition to *subscribe* to the `discussion` topic. + 3. When Tyk receives a message from the `discussion` topic, it *pushes* the message content (the joke) to the appropriate client(s) (provided they have already established a connection) using a suitable real-time protocol like Server-Sent Events (SSE) or WebSockets. + **Note:** In case of multiple clients, events would round-robin amongst the consumers. + +* **Value Demonstrated:** + * **Protocol Bridging:** Tyk translates Kafka messages into SSE messages suitable for web clients. + * **Decoupling:** The browser doesn't need a Kafka client; it uses standard web protocols (SSE/WS) provided by Tyk. The Joker Service only needs to publish to Kafka, unaware of the final client protocol. + +The following sections will guide you through the prerequisites and steps to configure Tyk Gateway to implement this use case. + +### Prerequisites + +- **Docker**: We will run the entire Tyk Stack on Docker. For installation, refer to this [guide](https://docs.docker.com/desktop/setup/install/mac-install/). +- **Git**: A CLI tool to work with git repositories. For installation, refer to this [guide](https://git-scm.com/downloads) +- **Dashboard License**: We will configure Streams API using Dashboard. [Contact our team](https://tyk.io/contact/) to obtain a license or get self-managed trial license by completing the registration on our [website](https://tyk.io/self-managed-trial/). **Note:** To use the dashboard to design streams APIs, the dashboard license must include the `streams` scope. +- **Curl and JQ**: These tools will be used for testing. + +### Instructions + +1. **Clone Git Repository:** + + The [tyk-demo](https://github.com/TykTechnologies/tyk-demo) repository offers a docker-compose environment you can run locally to explore Tyk streams. Open your terminal and clone the git repository using the command below. + + ```bash + git clone https://github.com/TykTechnologies/tyk-demo + cd tyk-demo + ``` + +2. **Enable Tyk Streams:** + + By default, Tyk Streams is disabled. To enable Tyk Streams in the Gateway and Dashboard, you need to configure the following settings: + + Create an `.env` file and populate it with the values below: + + ```bash + DASHBOARD_LICENCE= + GATEWAY_IMAGE_REPO=tyk-gateway-ee + TYK_DB_STREAMING_ENABLED=true + TYK_GW_STREAMING_ENABLED=true + ``` + + - `DASHBOARD_LICENCE`: Add your license key. Contact [our team](https://tyk.io/contact/) to obtain a license. + - `GATEWAY_IMAGE_REPO`: Tyk Streams is available as part of the Enterprise Edition of the Gateway. + - `TYK_DB_STREAMING_ENABLED` and `TYK_GW_STREAMING_ENABLED`: These must be set to `true` to enable Tyk Streams in the Dashboard and Gateway, respectively. Refer to the [configuration options](/tyk-oss-gateway/configuration#streaming) for more details. + +3. **Start Tyk Streams** + + Execute the following command: + ```bash + ./up.sh + ``` + + + + + This script also starts `Kafka` within a Docker container, which is necessary for this guide. + + + + This process will take a few minutes to complete and will display some credentials upon completion. Copy the Dashboard **username, password, and API key**, and save them for later use. + ``` + ▾ Tyk Demo Organisation + Username : admin-user@example.org + Password : 3LEsHO1jv1dt9Xgf + Dashboard API Key : 5ff97f66188e48646557ba7c25d8c601 + ``` + +4. **Verify Setup:** + + Open Tyk Dashboard in your browser by visiting [http://localhost:3000](http://localhost:3000) or [http://tyk-dashboard.localhost:3000](http://tyk-dashboard.localhost:3000) and login with the provided **admin** credentials. + +5. **Create Producer API:** + + Create a file `producer.json` with the below content: (**Note:** `tyk-demo-kafka-1` is the hostname used to access Kafka running in a container; alternatively, you can use the IP address assigned to your computer.) + + + + + ```json + { + "components": {}, + "info": { + "title": "jippity-chat", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "servers": [ + { + "url": "http://tyk-gateway.localhost:8080/jippity-chat/" + } + ], + "x-tyk-api-gateway": { + "info": { + "name": "jippity-chat", + "state": { + "active": true + } + }, + "server": { + "listenPath": { + "value": "/jippity-chat/", + "strip": true + } + }, + "upstream": { + "url": "" + } + }, + "x-tyk-streaming": { + "streams": { + "default_stream": { + "input": { + "http_server": { + "address": "", + "allowed_verbs": [ + "POST" + ], + "path": "/chat", + "rate_limit": "", + "timeout": "5s" + }, + "label": "" + }, + "output": { + "kafka": { + "addresses": ["tyk-demo-kafka-1:9092"], + "max_in_flight": 10, + "topic": "chat" + }, + "label": "" + } + } + } + } + } + ``` + + + + + + Create the API by executing the following command. Be sure to replace `` with the API key you saved earlier: + + ```bash + curl -H "Authorization: " -H "Content-Type: application/vnd.tyk.streams.oas" http://localhost:3000/api/apis/streams -d @producer.json + ``` + + You should expect a response similar to the one shown below, indicating success. Note that the Meta and ID values will be different each time: + ```bash + {"Status":"OK","Message":"API created","Meta":"67e54cadbfa2f900013b501c","ID":"3ddcc8e1b1534d1d4336dc6b64a0d22f"} + ``` + +5. **Create Consumer API:** + + Create a file `consumer.json` with the below content: (**Note:** `tyk-demo-kafka-1` is the hostname used to access Kafka running in a container; alternatively, you can use the IP address assigned to your computer.) + + + + + ```json + { + "components": {}, + "info": { + "title": "jippity-discuss", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "servers": [ + { + "url": "http://tyk-gateway.localhost:8080/jippity-discuss/" + } + ], + "x-tyk-api-gateway": { + "info": { + "name": "jippity-discuss", + "state": { + "active": true + } + }, + "server": { + "listenPath": { + "value": "/jippity-discuss/", + "strip": true + } + }, + "upstream": { + "url": "" + } + }, + "x-tyk-streaming": { + "streams": { + "default_stream": { + "input": { + "kafka": { + "addresses": ["tyk-demo-kafka-1:9092"], + "auto_replay_nacks": true, + "checkpoint_limit": 1024, + "consumer_group": "tyk-streams", + "target_version": "3.3.0", + "topics": ["discussion"] + }, + "label": "" + }, + "output": { + "http_server": { + "address": "", + "allowed_verbs": [ + "GET" + ], + "stream_path": "/sse" + }, + "label": "" + } + } + } + } + } + ``` + + + + + + Create the API by executing the following command. Be sure to replace `` with the API key you saved earlier: + + ```bash + curl -H "Authorization: " -H "Content-Type: application/vnd.tyk.streams.oas" http://localhost:3000/api/apis/streams -d @consumer.json + ``` + + You should expect a response similar to the one shown below, indicating success. Note that the Meta and ID values will be different each time: + ```bash + {"Status":"OK","Message":"API created","Meta":"67e54cadbfa2f900013b501c","ID":"3ddcc8e1b1534d1d4336dc6b64a0d22f"} + ``` + +7. **Start Joker Service:** + + Create a file `joker-service.sh` with the below content: + + + + + ```bash + #!/bin/bash + + # Container name + CONTAINER="tyk-demo-kafka-1" + + # Kafka bootstrap server + BOOTSTRAP_SERVER="localhost:9092" + + # Topics + SOURCE_TOPIC="chat" + TARGET_TOPIC="discussion" + + # Kafka consumer and producer commands + CONSUMER_CMD="/opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server $BOOTSTRAP_SERVER --topic $SOURCE_TOPIC" + PRODUCER_CMD="/opt/kafka/bin/kafka-console-producer.sh --broker-list $BOOTSTRAP_SERVER --topic $TARGET_TOPIC" + + # Joke API URL + JOKE_API="https://icanhazdadjoke.com/" + + echo "Starting to listen for messages on '$SOURCE_TOPIC'..." + + # Run the consumer in the container, pipe output to a while loop + docker exec -i $CONTAINER bash -c "$CONSUMER_CMD" | while IFS= read -r message; do + # Skip empty lines + [ -z "$message" ] && continue + + echo "Received message from $SOURCE_TOPIC: $message" + + # Fetch a random joke from the API and extract it with jq + joke=$(curl -s -H "Accept: application/json" "$JOKE_API" | jq .joke) + + # Check if joke was fetched successfully + if [ -n "$joke" ]; then + response_message="In response to '$message': Here's a dad joke - $joke" + else + response_message="In response to '$message': Couldn't fetch a joke, sorry!" + fi + + # Send the response message to 'discussion' topic + echo "$response_message" | docker exec -i $CONTAINER bash -c "$PRODUCER_CMD" + + echo "Posted to $TARGET_TOPIC: $response_message" + done + + echo "Consumer stopped." + ``` + + + + + + Make the file executable and start the service. + + ```bash + chmod +x joker-service.sh + ./joker-service.sh + ``` + +8. **Test the API:** + + Open a terminal and execute the following command to start listening for messages from the Consumer API you created: + + ```bash + curl -N http://tyk-gateway.localhost:8080/jippity-discuss/sse + ``` + + In a second terminal, execute the command below to send a message to the Producer API. You can run this command multiple times and modify the message to send different messages: + + ```bash + curl -X POST http://tyk-gateway.localhost:8080/jippity-chat/chat -H "Content-Type: text/plain" -d "Tell me a joke." + ``` + + Now, you will see the message appear in the terminal window where you are listening for messages. + +**Wrapping Up:** And that’s it—you’ve just created an Async API with Tyk Streams! From here, you can tweak the configuration to suit your needs, [explore glossary](#glossary), or explore more [advanced use cases](#use-cases). + +--- +## How It Works + +Tyk Streams seamlessly integrates with the Tyk API Gateway, extending its capabilities beyond traditional synchronous request/response patterns to natively support asynchronous APIs and event-driven architectures. + +This section details the architecture, components, and request processing flow of Tyk Streams. + +### High-Level Architecture + +At a high level, Tyk Streams operates within the Tyk ecosystem, interacting with several key elements: + +* **Tyk API Gateway**: The core API management platform. It is the entry point, handling initial request processing (like authentication and rate limiting) and routing requests. +* **Tyk Streams Module**: An integrated extension within the Gateway designed explicitly for asynchronous communication. It intercepts relevant requests and manages the streaming logic. +* **Event Brokers / Sources**: External systems that act as the origin or destination for data streams. Examples include Apache Kafka, NATS, MQTT brokers, or WebSocket servers. Tyk Streams connects to these systems based on API configuration. +* **Upstream Services / APIs**: The backend systems, microservices, or APIs that ultimately produce or consume the data being streamed or processed via Tyk Streams. + +Think of the Tyk Gateway as the central dispatch for all API traffic. When traffic requires asynchronous handling (like pushing data to Kafka or subscribing to an MQTT topic), the integrated Tyk Streams module manages the interaction with the specific Event Broker and Upstream Service according to the API's configuration. + +### Internal Components of Tyk Streams + +To manage these asynchronous interactions, the Tyk Streams module relies on several internal components operating within the Gateway: + +1. **Stream Middleware**: This component plugs into the Tyk Gateway's request processing chain. It runs *after* standard middleware like authentication and rate limiting but *before* the request would normally be proxied. Its job is to inspect incoming requests, identify if they match a configured stream path, and if so, divert them from the standard proxy flow into the stream handling logic. +2. **Stream Manager**: Acts as the supervisor for streaming operations defined in an API. A given stream configuration is responsible for initializing, managing the lifecycle (starting/stopping), and coordinating the necessary `Stream Instances`. It ensures the correct streaming infrastructure is ready based on the API definition. +3. **Stream Instance**: Represents a running, active instance of a specific stream processing task. Each instance executes the logic defined in its configuration – connecting to an event broker, processing messages, transforming data, handling connections, etc. There can be multiple instances depending on the configuration and workload. +4. **Stream Analytics**: This component captures connection attempts and errors related to HTTP outputs. This data can be exported to popular analytics platforms like Prometheus, OpenTelemetry, and StatsD. + +The following diagram shows the relationships and primary interactions between these internal components and how they relate to the Gateway and Upstream API: + +```mermaid +graph TD + idClient[Client] + idTykGateway[Tyk Gateway] + idStreamMiddleware[Stream Middleware] + idStreamManager[Stream Manager] + idStreamAnalytics[Stream Analytics] + idStreamInstance[Stream Instance] + idUpstreamAPI[Upstream API] + + idClient -- Request --> idTykGateway + idTykGateway -- Response --> idClient + idTykGateway -- Process Request --> idStreamMiddleware + idStreamMiddleware -- Response --> idTykGateway + idStreamMiddleware -- Configure & Manage --> idStreamManager + idStreamMiddleware -- Capture Analytics --> idStreamAnalytics + idStreamManager -- Create & Run --> idStreamInstance + idStreamInstance -- Processed Response --> idStreamMiddleware + idStreamInstance -- Process Data --> idUpstreamAPI + idUpstreamAPI -- Response --> idStreamInstance +``` + +### Request Processing Flow + +Understanding how these components work together is key. Here’s the typical flow when a request interacts with a Tyk Streams-enabled API endpoint: + +```mermaid +sequenceDiagram + participant Client + participant TykGateway as Tyk Gateway + participant StreamingMiddleware as Streaming Middleware + participant StreamManager as Stream Manager + participant StreamInstance as Stream Instance + participant UpstreamService as Upstream Service + + Client->>TykGateway: HTTP Request + TykGateway->>StreamingMiddleware: Process Request + StreamingMiddleware->>StreamingMiddleware: Strip Listen Path + StreamingMiddleware->>StreamingMiddleware: Check if path is handled by streams + + alt Path handled by streams + StreamingMiddleware->>StreamManager: Create/Get Stream Manager for request + StreamingMiddleware->>StreamManager: Match request to route + StreamManager->>StreamInstance: Handle request + StreamInstance->>Client: Stream response + else Not handled by streams + StreamingMiddleware-->>TykGateway: Continue middleware chain + TykGateway->>UpstreamService: Proxy request + UpstreamService->>TykGateway: Response + end + + TykGateway->>Client: HTTP Response +``` + +1. **Request Arrival & Gateway Pre-processing**: A client sends a request to an API endpoint managed by Tyk Gateway. The request passes through the initial middleware, such as authentication, key validation, and rate limiting. +2. **Streaming Middleware Interception**: The request reaches the `Stream Middleware`. It checks the request path against the stream routes defined in the API configuration. +3. **Path Matching**: + * **If No Match**: The `Stream Middleware` will respond with a `404 Not Found` status code. + * **If Match**: The request is intended for a stream. The `Stream Middleware` takes control of the request handling. +4. **Stream Manager Coordination**: The middleware interacts with the `Stream Manager` associated with the API's stream configuration. The `Stream Manager` ensures the required `Stream Instance`(s) are initialized and running based on the loaded configuration. This might involve creating a new instance or reusing a cached one. +5. **Stream Instance Execution**: The instance then executes its defined logic, interacting with the configured `Upstream Service / Event Broker` (e.g., publishing a message to Kafka, subscribing to an MQTT topic, forwarding data over a WebSocket). +6. **Analytics Capture**: The `Stream Analytics` component captures relevant metrics throughout the stream handling process. +8. **Final Gateway Response**: The response or data stream generated by the streaming components is relayed back through the Gateway to the originating client. + +### Scaling and Availability + +The beauty of Tyk Streams is that it’s baked into the Tyk Gateway, so it scales naturally as your API traffic ramps up—no extra setup or separate systems required. It’s efficient too, reusing the same resources as the Gateway to keep things lean. + +--- +## Configuration Options + +Configuring Tyk Streams involves two distinct levels: + +1. **System-Level Configuration:** Enabling the Streams functionality globally within your Tyk Gateway and Tyk Dashboard instances. This activates the necessary components but doesn't define any specific streams. +2. **API-Level Configuration:** Defining the actual stream behaviors (inputs, outputs, processing logic) within a specific Tyk OAS API Definition using the `x-tyk-streaming` extension. This is where you specify *how* data flows for a particular asynchronous API. + +Let's look at each level in detail. + +### System-Level Configuration + +Before you can define streams in your APIs, you must enable the core Streams feature in both the Tyk Gateway and, if you're using it for management, the Tyk Dashboard. + +#### Installation / Deployment + +Tyk Streams is available as part of the Enterprise Edition of the Gateway. + +- **Helm**: Use `--set tyk-gateway.gateway.image.repository=tykio/tyk-gateway-ee --set global.streaming.enabled=true` +- **Docker/Kubernetes**: Use the `tykio/tyk-gateway-ee` image. +- **OS packages**: Available via Package cloud in the `tyk-ee` repo. + +#### Tyk Gateway + +Enable the Streams processing engine within the Gateway by setting `enabled` to `true` in the `streaming` section of your `tyk.conf` file or via environment variables. + + + +```json +{ +// Partial config from tyk.conf + "streaming": { + "enabled": true // Required to activate Streams functionality + }, +// ... more config follows +} +``` + + +```bash +export TYK_GW_STREAMING_ENABLED=true +``` + + + +Refer to the [Tyk Gateway Configuration Reference](/tyk-oss-gateway/configuration#streaming-enabled) for more details on this setting. + +**Note on WebSockets:** To use WebSockets as a streaming protocol, you must also enable `TYK_GW_HTTPSERVEROPTIONS_ENABLEWEBSOCKETS=true` in your Gateway configuration. + +#### Tyk Dashboard + +If you manage your APIs via the Tyk Dashboard, you must also enable Streams support within the Dashboard configuration (`tyk_analytics.conf`) to expose Streams-related UI elements and functionality. + + + +```json +{ +// Partial config from tyk_analytics.conf + "streaming": { + "enabled": true // Required to activate Streams functionality + }, +// ... more config follows +} +``` + + +```bash +export TYK_DB_STREAMING_ENABLED=true +``` + + + +Refer to the [Tyk Dashboard Configuration Reference](/tyk-dashboard/configuration#streaming-enabled) for more details. + +### API-Level Configuration + +Once Streams is enabled at the system level, you define the specific behavior for each asynchronous API within its Tyk Open API Specification (OAS) definition. This is done using the `x-tyk-streaming` vendor extension. + +The core structure under `x-tyk-streaming` is the `streams` object, which contains one or more named stream configurations. Each named stream defines: + +* **`input`**: Specifies how data enters this stream (e.g., via an HTTP request, by consuming from Kafka, connecting via WebSocket). +* **`output`**: Specifies where the data goes after processing (e.g., published to Kafka, sent over WebSocket, delivered via webhook). + +```json +{ +// Partial config from Tyk OAS API Definition + "x-tyk-streaming": { + "streams": { + "your_stream_name": { // A unique name for this stream configuration within the API + "input": { + // Input configuration object - specifies the data source + // Example: "http_server": { ... } or "kafka": { ... } + }, + "output": { + // Output configuration object - specifies the data destination + // Example: "kafka": { ... } or "websocket_server": { ... } + } + // Optional processing/transformation steps can also be defined here + }, + "another_stream": { // You can define multiple independent streams + "input": { ... }, + "output": { ... } + } + } + }, +// ... more config follows +} +``` + +**Available Input and Output Types:** + +Tyk supports various connector types for both `input` and `output`. **The specific types available (like `http_server`, `kafka`, `http_client`, etc.) and their respective configuration parameters are detailed in the [Tyk Streams Configuration Reference](/api-management/stream-config).** Please consult this reference page for the full list of options and how to configure each one. + +**Example Configuration:** + + + + +```json +{ +// Partial config from Tyk OAS API Definition + "x-tyk-streaming": { + "streams": { + "http_to_kafka_chat": { + "input": { + "http_server": { + "path": "/chat", + "allowed_verbs": [ "POST" ], + + }, + "label": "HTTP Chat Input" + }, + "output": { + "kafka": { + "addresses": ["kafka-broker:9092"], + "topic": "chat", + + }, + "label": "Kafka Chat Output" + } + } + } + }, +// ... more config follows +} +``` + +For comprehensive details on all fields within `x-tyk-streaming`, see the [Tyk OAS Extension documentation](/api-management/gateway-config-tyk-oas#xtykstreaming). + + + + + +The Tyk Dashboard provides a wizard to create Streams APIs, which generates the underlying Tyk OAS configuration shown above. + +1. Navigate to **APIs > Add New API**. +2. Select the **Streams** API type and give your API a name. Click **Configure API**. + Streams Option +3. In the **API Designer**, under the **Streams** tab, configure your desired `Input` and `Output`. Select the types (e.g., HTTP Server for input, Kafka for output) and fill in the required parameters based on the [Streams Configuration Reference](/api-management/stream-config). + Input/Output Selection +4. Configure any other API settings (e.g., Authentication, Rate Limiting) as needed in the other tabs. +5. **Save** the API. The Dashboard translates your UI configuration into the `x-tyk-streaming` JSON structure within the API definition. You can view the generated JSON in the **Advanced Options** tab under **API Definition**. + + + + + +Tyk Streams configuration (`x-tyk-streaming`) is **only supported** within **Tyk OAS API Definitions**. It is not available for legacy Tyk Classic API Definitions. + + + + + +### Supported Connectors and Protocols + +Tyk Streams provides out-of-the-box connectors for popular event brokers and async protocols, including: + +- [Apache Kafka](https://kafka.apache.org/documentation/) +- [WebSocket](https://websocket.org/guides/websocket-protocol/) +- [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) (SSE) +- [Webhooks](https://en.wikipedia.org/wiki/Webhook) + +When configuring HTTP-based outputs, keep the following behavior in mind: +- **REST**: A simple GET request to the path retrieves one entry and exits. This is subject to `http_server_options.write_timeout` and `read_timeout`. +- **SSE**: Stream results for `http_server_options.write_timeout` seconds (defaults to 120s). +- **WebSocket**: Not subject to the same timeout issues as REST and SSE, and will continue indefinitely. + +You can expose all three protocols from a single `http_server` output by defining all three paths together: + +```yaml +output: + http_server: + allowed_verbs: + - GET + path: /output + stream_path: /output/stream + ws_path: /output/subscribe +``` + +This makes the same data available as a single REST response at `/output`, an SSE stream at `/output/stream`, and a WebSocket connection at `/output/subscribe`. + +--- +## Use Cases + +Tyk Streams brings full lifecycle API management to asynchronous APIs and event-driven architectures. It provides a +comprehensive set of capabilities to secure, transform, monitor and monetize your async APIs. + +### Security + +[Tyk Streams](/api-management/event-driven-apis#) supports all the authentication and authorization options available for traditional synchronous APIs. This +ensures that your async APIs are protected with the same level of security as your REST, GraphQL, and other API types. + +Refer to these docs, to know more about [Authentication](/api-management/client-authentication) and [Authorization](/api-management/access-control/overview) in Tyk. + +### Transformations and Enrichment + +[Tyk Streams](/api-management/event-driven-apis#) allows you to transform and enrich the messages flowing through your async APIs. You can modify message payloads, filter events, combine data from multiple sources and more. + +- **[Transformation](/api-management/traffic-transformation)**: Use Tyk's powerful middleware and plugin system to transform message payloads on the fly. You can convert between different data formats (e.g., JSON to XML), filter fields, or apply custom logic. +- **[Enrichment](/api-management/plugins/overview)**: Enrich your async API messages with additional data from external sources. For example, you can lookup customer information from a database and append it to the message payload. +- **Bloblang**: Bloblang can be used in the `pipeline.processors` section to transform the body of the request. For example: + ```yaml + pipeline: + processors: + - bloblang: | + root.name = "jim" + ``` + +### Monetization + +[Tyk Streams](/api-management/event-driven-apis#) enables you to monetize your async APIs by exposing them through the Developer Portal. Developers can discover, subscribe to and consume your async APIs using webhooks or streaming subscriptions. + +- **Developer Portal Integration**: Async APIs can be published to the Tyk Developer Portal, allowing developers to browse, subscribe, and access documentation. Developers can manage their async API subscriptions just like traditional APIs. +- **Webhooks**: Tyk supports exposing async APIs as webhooks, enabling developers to receive event notifications via HTTP callbacks. Developers can configure their webhook endpoints and subscribe to specific events or topics. + +### Complex Event Processing + +Tyk Streams allows you to perform complex event processing on streams of events in real-time. You can define custom processing logic to: + +- Filter events based on specific criteria +- Aggregate and correlate events from multiple streams +- Enrich events with additional data from other sources +- Detect patterns and sequences of events +- Trigger actions or notifications based on event conditions + +Here's an example of a Tyk Streams configuration that performs complex event processing, specifically it creates a new event stream, which filters high-value orders and enriches them with customer email addresses, by making an additional HTTP request. + +```yaml +input: + kafka: + addresses: + - "localhost:9092" # Replace with actual Kafka broker addresses + consumer_group: my-group + topics: + - orders +output: + http_server: + allowed_verbs: + - GET + path: /high-value-orders +pipeline: + processors: + - mapping: | + root = if this.order_value > 1000 { + this + } else { + deleted() + } + - branch: + processors: + - http: + headers: + Content-Type: application/json + url: http://customer-api.local/emails + verb: POST + request_map: |- + root = { + "customer_id": this.customer_id + } + result_map: root.customer_email = this.customer_email + - mapping: | + root = this.merge({ "high_value_order": true }) +``` + +In this example: + +- **Tyk Streams Setup**: Consumes events from a Kafka topic called *orders*. +- **Processor Block Configuration**: Utilizes a custom `Mapping` script that performs the following operations: + - **Filters** orders, only processing those with a value greater than 1000. + - **Enriches** the high-value orders by retrieving the customer ID and email from a separate data source. + - **Adds** a new high_value_order flag to each qualifying event. +- **Output Handling**: Processed high-value order events are exposed via a WebSocket stream at the endpoint */high-value-orders*. + +### Legacy Modernization + +Tyk Streams can help you modernise legacy applications and systems by exposing their functionality as async APIs. This allows you to: +- Decouple legacy systems from modern consumers +- Enable real-time, event-driven communication with legacy apps +- Gradually migrate away from legacy infrastructure + +Here's an example of exposing a legacy application as an async API using Tyk Streams: + +```yaml +input: + http_client: + url: "http://legacy-app/orders" + verb: GET + rate_limit: "60s" +pipeline: + processors: + - mapping: | + root.order_id = this.id + root.total = this.total + root.timestamp = this.timestamp +output: + kafka: + addresses: ["localhost:9092"] + topic: "orders" +``` + +In this configuration: +- Tyk Streams periodically polls the legacy */orders* REST endpoint every 60 seconds +- The *processor* transforms the legacy response format into a simplified event structure +- The transformed events are published to a Kafka topic called *orders*, which can be consumed by modern applications + +### Async API Orchestration + +Tyk Streams enables you to orchestrate multiple async APIs and services into composite event-driven flows. You can: +- Combine events from various streams and sources +- Implement complex routing and mediation logic between async APIs +- Create reactive flows triggered by event conditions +- Fanout events to multiple downstream consumers + +Here's an example async API orchestration with Tyk Streams: + +```yaml +input: + broker: + inputs: + - kafka: + addresses: ["localhost:9092"] + topics: ["stream1"] + consumer_group: "group1" + - kafka: + addresses: ["localhost:9092"] + topics: ["stream2"] + consumer_group: "group2" +pipeline: + processors: + - switch: + cases: + - check: 'meta("kafka_topic") == "stream1"' + processors: + - mapping: | + root.type = "event_from_stream1" + root.details = this + - branch: + processors: + - http: + url: "http://api1.example.com/process" + verb: POST + body: '${! json() }' + result_map: 'root.api1_response = this' + - check: 'meta("kafka_topic") == "stream2"' + processors: + - mapping: | + root.type = "event_from_stream2" + root.details = this + - branch: + processors: + - http: + url: "http://api2.example.com/analyze" + verb: POST + body: '${! json() }' + result_map: 'root.api2_response = this' + - mapping: 'root = if this.type == "event_from_stream1" && this.api1_response.status == "ok" { this } else if this.type == "event_from_stream2" && this.api2_response.status == "ok" { this } else { deleted() }' +output: + broker: + pattern: "fan_out" + outputs: + - kafka: + addresses: ["localhost:9092"] + topic: "processed_stream1" + client_id: "tyk_fanout1" + - kafka: + addresses: ["localhost:9092"] + topic: "processed_stream2" + client_id: "tyk_fanout2" + - http_client: + url: "https://webhook.site/unique-id" + verb: POST + body: '${! json() }' +``` + +1. **Input Configuration** + - Uses a broker to combine events from two different Kafka topics, stream1 and stream2, allowing for the integration of events from various streams. +2. **Complex Routing and Processing** + - A switch processor directs messages based on their origin (differentiated by Kafka topic metadata). + - Each stream’s messages are processed and conditionally sent to different APIs. + - Responses from these APIs are captured and used to decide on message processing further. +3. **Reactive Flows** + - Conditions based on API responses determine if messages are forwarded or discarded, creating a flow reactive to the content and success of API interactions. + - Fanout to Multiple Consumers: + - The broker output with a fan-out pattern sends processed messages to multiple destinations: two different Kafka topics and an HTTP endpoint, demonstrating the capability to distribute events to various downstream consumers. + +These are just a few examples of the advanced async API scenarios made possible with Tyk Streams. The platform provides a flexible and extensible framework to design, deploy and manage sophisticated event-driven architectures. + +### Monetize APIs using Developer Portal + +Tyk Streams seamlessly integrates with the Tyk Developer Portal, enabling developers to easily discover, subscribe to, and consume async APIs and event streams. This section covers how to publish async APIs to the developer portal, provide documentation, and enable developers to subscribe to events and streams. + + + +#### Publishing Async APIs to the Developer Portal + +Publishing async APIs to the Tyk Developer Portal follows a similar process to publishing traditional synchronous APIs. API publishers can create API products that include async APIs and make them available to developers through the portal. + +To publish an async API: +- In the Tyk Dashboard, create a new API and define the async API endpoints and configuration. +- Associate the async API with an API product. +- Publish the API product to the Developer Portal. +- Copy code + +{/* [Placeholder for screenshot or GIF demonstrating the process of publishing an async API to the Developer Portal] */} + + + +#### Async API Documentation + +Providing clear and comprehensive documentation is crucial for developers to understand and effectively use async APIs. While Tyk Streams does not currently support the AsyncAPI specification format, it allows API publishers to include detailed documentation for each async API. + +When publishing an async API to the Developer Portal, consider including the following information in the documentation: +- Overview and purpose of the async API +- Supported protocols and endpoints (e.g., WebSocket, Webhook) +- Event types and payloads +- Subscription and connection details +- Example code snippets for consuming the async API +- Error handling and troubleshooting guidelines + +{/* [Placeholder for screenshot showcasing async API documentation in the Developer Portal] */} + + + +#### Enabling Developers to Subscribe to Events and Streams + +Tyk Streams provides a seamless way for developers to subscribe to events and streams directly from the Developer Portal. API publishers can enable webhook subscriptions for specific API products, allowing developers to receive real-time updates and notifications. +To enable webhook subscriptions for an API product: +1. In the Tyk Developer Portal, navigate to the API product settings. +2. Enable the "Webhooks" option and specify the available events for subscription. +3. Save the API product settings. + +Enable Portal Webhooks + +{/* [Placeholder for screenshot showing the API product settings with webhook configuration] */} + +Once webhook subscriptions are enabled, developers can subscribe to events and streams by following these steps: +- In the Developer Portal, navigate to the My Apps page. +- Select the desired app. +- In the "Webhooks" section, click on "Subscribe". +- Provide the necessary details: + - *Webhook URL*: The URL where the event notifications will be sent. + - *HMAC Secret*: Provide a secret key used to sign the webhook messages for authentication. + - *Events*: Select the specific events to subscribe to. +- Save the subscription settings. +- Copy code +{/* [Placeholder for screenshot illustrating the developer's view of subscribing to webhooks] */} + +subscribe to webhooks from portal + +To configure the async API stream for webhook subscriptions, use the following output configuration in your API definition: + +```yaml +outputs: + - portal_webhook: + event_type: bar + portal_url: http://localhost:3001 + secret: +``` + +Replace `` with the secret key for signing the webhook messages. + +Enabling webhook subscriptions allows developers to easily integrate real-time updates and notifications from async APIs into their applications, enhancing the overall developer experience and facilitating seamless communication between systems. +{/* [Placeholder for a diagram illustrating the flow of webhook subscriptions and event notifications] */} + +With Tyk Streams and the Developer Portal integration, API publishers can effectively manage and expose async APIs, while developers can discover, subscribe to, and consume event streams effortlessly, enabling powerful real-time functionality in their applications. + + +--- +## Glossary + +### Event + +An event represents a significant change or occurrence within a system, such as a user action, a sensor reading, or a data update. Events are typically lightweight and contain minimal data, often just a unique identifier and a timestamp. + +### Stream + +A stream is a continuous flow of events ordered by time. Streams allow for efficient, real-time processing and distribution of events to multiple consumers. + +### Publisher (or Producer) + +A publisher is an application or system that generates events and sends them to a broker or event store for distribution to interested parties. + +### Subscriber (or Consumer) + +A subscriber is an application or system that expresses interest in receiving events from one or more streams. Subscribers can process events in real-time or store them for later consumption. + +### Broker + +A broker is an intermediary system that receives events from publishers, stores them, and forwards them to subscribers. Brokers decouple publishers from subscribers, allowing for scalable and flexible event-driven architectures. + +### Topic (or Channel) + +A topic is a named destination within a broker where events are published. Subscribers can subscribe to specific topics to receive relevant events. + +## Troubleshooting and Gotchas + +When working with Tyk Streams, keep the following points in mind: + + + +Streams APIs cannot be edited over HTTP unless the dashboard is hosted on the localhost interface. + + + +The `upstream` attribute is not used by streams APIs. + + + +If streams are not enabled in the dashboard config, the option to create streams APIs is present, but the "Streaming" tab is missing in the API designer. + + + +If streams are not enabled in the gateway config, the client gets `{"error": "There was a problem proxying the request"}` and the gateway logs `http: proxy error: unsupported protocol scheme ""`. + + + +Analytics are not available for Streams APIs in the dashboard (only connection attempts are tracked). + + + +Streams round-robins output between listeners. Tyk is not a broker, so messages are not sent to all consumers unless a broker such as Kafka is configured. + + + +If the dashboard license does not include the `streams` scope, the Streaming tab still appears and the API can be created. Saving or publishing the API fails with `"Message":"Your license does not support adding/modifying streams API configuration"`. + + + +If the stream definition is invalid or uses an unsupported input or output, the dashboard does not catch this when the API is saved. The gateway logs `Failed to set YAML: lint errors: (2,1) unable to infer component type` in addition to the Gateway Proxy Error above. + + + +The `http_server` input and output used in the [Getting Started](#getting-started) example tie each request and its reply to the Tyk Gateway instance that received it. If multiple Tyk Gateway instances sit behind a load balancer without a shared broker, only requests routed to the instance holding the matching connection succeed, and others fail. Use a broker such as Kafka to distribute messages reliably across multiple Tyk Gateway instances. + + + +Editing a Streams API does not affect connections that are already open. They keep running with the previous configuration. New connections pick up the updated configuration after a short delay (around 10 seconds). Deactivating the API does not stop active connections. + + + +--- +## FAQ + + + +Tyk Streams is an extension to the Tyk API Gateway that supports asynchronous APIs and event-driven architectures. It solves the challenge of managing both synchronous and asynchronous APIs in a unified platform, allowing organizations to handle real-time event streams alongside traditional REST APIs. + + + +Refer this [documentation](#supported-connectors-and-protocols). + + + +Currently, Tyk Streams is only available for hybrid customers on Tyk Cloud. To enable it, [contact support](https://tyk.io/contact/). + + + +Yes, as of Tyk v5.7.0, you can publish Tyk Streams APIs to the Tyk Developer Portal. The process is similar to publishing traditional APIs: create a Tyk Streams API, create a Policy to protect it, and publish it to the Developer Portal Catalog. + + + +Tyk Streams is embedded within the Tyk Gateway and scales with your existing Tyk infrastructure. No additional infrastructure is required for broker-based inputs and outputs, such as Kafka. + +If you use the `http_server` input or output without a broker, requests and their replies are tied to a single Tyk Gateway instance. Running multiple Tyk Gateway instances behind a load balancer can then cause inconsistent results. See [Troubleshooting and Gotchas](#troubleshooting-and-gotchas) for details. + + + +Tyk Streams is available exclusively in the `enterprise` edition. Currently, it is only accessible for hybrid customers using Tyk Cloud. Please refer to the latest documentation or reach out to [Tyk support](https://tyk.io/contact/) for specific availability in your edition. + + diff --git a/api-management/fips-implementation.mdx b/api-management/fips-implementation.mdx new file mode 100644 index 0000000000..bd883794e0 --- /dev/null +++ b/api-management/fips-implementation.mdx @@ -0,0 +1,169 @@ +--- +title: "FIPS Implementation" +description: "Configuring and Verifying FIPS Deployments" +keywords: "FIPS, FIPS 140-3, compliance, cryptography, security, federal, government, regulated" +sidebarTitle: "FIPS Implementation" +--- + +This page covers the practical steps for deploying Tyk in FIPS mode: configuring cryptographic settings, choosing a delivery format, deploying via Helm, verifying image integrity, scanning for vulnerabilities, and understanding what is and is not covered by Tyk's FIPS compliance attestation. + +--- + +## Cryptographic Configuration + +Regardless of delivery format or version, your environment must be configured to use only FIPS-approved cryptographic primitives. Legacy algorithms such as MD5 are not compliant with FIPS 140-3 and must not be used. + +When running Tyk Gateway in FIPS mode, you must configure key hashing to use SHA-256 or higher. + +**In** `tyk.conf`**:** + +```json +"hash_key_function": "sha256" +``` + +**Via environment variable:** + +```bash +TYK_GW_HASHKEYFUNCTION=sha256 +``` + +This setting is not required when using FIPS mode for Tyk Pump. + +--- + +## Choosing a Delivery Format + +From version 5.13.x, Tyk FIPS components are available in two formats. Both deliver the same FIPS 140-3 compliant application-layer cryptography. The right choice depends on your infrastructure and compliance requirements. + +| | Docker Hardened Images (DHI) | Native OS Packages (RPM/DEB) | +| --- | --- | --- | +| FIPS 140-3 application-layer cryptography | ✅ | ✅ | +| Built on FIPS-certified base image | ✅ | ❌ | +| SLSA Level 3 build provenance | ✅ | ❌ | +| Signed SBOMs (CycloneDX/SPDX) | ✅ | ❌ | +| Cryptographic image signatures | ✅ | ❌ | +| STIG-compliant | ✅ | ❌ | +| VEX statements for vulnerability scanning | ✅ | ❌ | +| Supported platforms | Container environments | Ubuntu, Debian, RHEL, CentOS, CentOS Stream | + +> If your organization requires a certified base image, supply chain attestations, or STIG compliance, you must use Docker Hardened Images. + +Version 5.8.x (LTS-1) is available as RPM/DEB packages only, targeting FIPS 140-2. See the [FIPS Policy](/developer-support/release-types/fips-release#fips-standard-by-version) page for a full breakdown of compliance posture by version. + +--- + +## Deploying with Docker Hardened Images + +### Helm + +Tyk's Helm charts support FIPS deployments by allowing you to override the default image references with FIPS-compliant image tags. The charts themselves are not FIPS-specific — FIPS readiness is achieved through the image override for each component you are deploying (Gateway, Dashboard, Pump, MDCB, Enterprise Developer Portal). + +The override follows the same pattern for each component: set the image repository to the `-fips` suffixed equivalent and pin the tag to your target version. Full per-component key paths are documented in the [Tyk Helm chart reference](/product-stack/tyk-charts/overview). + +--- + +### Verifying Image Integrity + +All Tyk DHI FIPS images are cryptographically signed using [Sigstore](https://www.sigstore.dev/). Each image ships with a signed SBOM in both CycloneDX and SPDX formats and SLSA Level 3 build provenance, which can be inspected using `cosign` or any compatible attestation tooling. + +To verify signatures and attestations, you will need the Tyk signing identity and OIDC issuer values. These are available from your account manager or via [Tyk support](https://support.tyk.io/). Once you have those values, verification follows the standard `cosign verify` workflow for image signatures, SBOM attestations, and build provenance. + +--- + +### Vulnerability Scanning + +Tyk's DHI images are published with **Vulnerability Exploitability eXchange (VEX)** statements. VEX statements communicate which reported CVEs are not exploitable in the context of a given image. Compatible scanners consume these statements automatically to suppress non-exploitable findings and reduce false positives. + +Without VEX filtering applied, scanners will report CVEs that are present in the image but not exploitable in this context. This is expected behavior, not a signal that the image is insecure. We strongly recommend configuring VEX filtering before running compliance scans. + +VEX documents for Tyk DHI images are available through your account manager. + +#### Using Grype + +[Grype](https://github.com/anchore/grype) supports VEX filtering natively. To scan with VEX applied: + +```bash +grype tykio/tyk-gateway-fips:v5.13.x --vex +``` + +To generate a full vulnerability report in table format: + +```bash +grype tykio/tyk-gateway-fips:v5.13.x --vex -o table +``` + +#### Using Trivy + +[Trivy](https://github.com/aquasecurity/trivy) supports VEX filtering and also consumes Docker-published VEX statements for the DHI base image layer: + +```bash +trivy image \ +--vex \ +tykio/tyk-gateway-fips:v5.13.x +``` + +To output results in a format suitable for compliance reporting: + +```bash +trivy image \ +--vex \ +--format cyclonedx \ +--output report.json \ +tykio/tyk-gateway-fips:v5.13.x +``` + +#### Using Docker Scout + +If you are already using Docker Scout as part of your DHI workflow, it natively understands VEX statements published alongside DHI images without requiring a separate `--vex` flag: + +```bash +docker scout cves tykio/tyk-gateway-fips:v5.13.x +``` + +--- + +## Deploying with Native OS Packages + +Native packages are available for Ubuntu, Debian, RHEL, CentOS, and CentOS Stream. They deliver the same FIPS 140-3 application-layer cryptography as DHI builds, but do not include a certified base image or supply chain attestations. + +Ensure you install the `-fips` suffixed package for your distribution: + +```bash +# RHEL/CentOS +sudo yum install tyk-gateway-fips +# Ubuntu/Debian +sudo apt-get install tyk-gateway-fips +``` + +Apply the cryptographic configuration described above before starting the service. + +> If you are running 5.8.x packages and have regulatory requirements that mandate FIPS 140-3, a certified base image, or supply chain attestations, contact your account team to plan migration to 5.13.x. + +--- + +## What Is Not Covered + +Tyk's FIPS compliance attestation covers the Tyk components listed on the [FIPS Policy](/developer-support/release-types/fips-release#tyk-fips-offering) page when deployed and configured as described in this documentation. It does not extend to: + +* **The surrounding infrastructure** — operating system configuration, network controls, load balancers, service meshes, or cloud provider services are outside Tyk's attestation boundary +* **Third-party plugins or custom middleware** — any code you introduce into the Tyk pipeline is your responsibility to assess for FIPS compliance +* **Data stores** — Redis, MongoDB, PostgreSQL, and any other backing services must be independently assessed and configured for FIPS compliance +* **Native OS packages (5.13.x)** — while these deliver FIPS 140-3 application-layer cryptography, they are not built on a FIPS-certified base image and do not carry supply chain attestations; if your audit requires these, use DHI +* **Version 5.8.x** — targets FIPS 140-2 only; does not meet FIPS 140-3 requirements +* **Non-FIPS suffixed builds** — standard Tyk releases do not operate in FIPS mode regardless of configuration + +Customers are responsible for ensuring their complete deployment meets applicable regulatory requirements. If you have questions about your specific compliance boundary, contact your account manager. + +--- + +## Responsibility Boundaries for CVEs + +Understanding who is responsible for remediating a CVE helps you assess risk accurately and set appropriate expectations. + +| CVE source | Responsible for fix | Tyk's role | +| --- | --- | --- | +| DHI base OS | Docker | Publishes updated base image and VEX statements. Tyk ships updated container images once the patch is available. | +| Third-party dependencies (Go stdlib, packages) | The upstream maintainer | Tyk applies the fix, recompiles, tests, and releases an updated version once the upstream patch is published. | +| Tyk-owned code | Tyk | Tyk is fully responsible for authoring and releasing the fix. | + +If you hold a **Priority Remediation SLA**, the SLA clock starts once the responsible party (Docker or the upstream maintainer) publishes a patch. Speak to your account manager for details. diff --git a/api-management/gateway-config-introduction.mdx b/api-management/gateway-config-introduction.mdx new file mode 100644 index 0000000000..b28382a89a --- /dev/null +++ b/api-management/gateway-config-introduction.mdx @@ -0,0 +1,90 @@ +--- +title: "Configuring Tyk Gateway" +description: "An introduction to Tyk API definitions, how the Gateway processes requests, and the different configuration types available" +keywords: "API Definition, API Definition Object, API Definition Location" +sidebarTitle: "Overview" +--- + +## Introduction + +Tyk API Gateway is a [reverse-proxy](https://en.wikipedia.org/wiki/Reverse_proxy) that serves as an intermediary managing API traffic between clients and the upstream API service. It consists of a series of middleware blocks that process API requests received from clients. These middleware perform various checks and transformations of and to the request preparing it to be routed to the upstream. The upstream API service executes core business logic and returns responses to Tyk Gateway. The response is similarly passed through a series of middleware blocks before being returned to the client. + +Each of these middleware can be configured so that it will only allow the specific requests that you want to reach your upstream, and in the correct form. The request middleware chain encompasses functionality that includes: + +- listening for requests +- authentication and authorization of the client +- rate and quota limiting +- checking that the request is valid +- applying transformations to the payload and headers +- triggering event handlers that can notify external systems of certain events +- checking availability of the upstream service +- ... and finally routing to the correct target applying load balancing between multiple upstreams if required + +You can even create custom middleware (plugins) that will perform non-standard checks and transformations. As you can imagine Tyk has a lot of configuration options to implement all of this! + + +## Configuring the Gateway + +Tyk Gateway is configurable at three levels of granularity: + +- *Gateway level* settings that apply to all API proxies hosted on Tyk +- *API level* settings that apply to a specific API proxy +- *Endpoint level* settings that apply to specific endpoints (operations consisting of HTTP method and path) within an API proxy + +Some features can be configured at multiple levels. Where this is the case, specific precedence rules apply and are described in the relevant section of the documentation. + +### Gateway level settings + +Gateway level settings are stored in a file (typically `tyk.conf`) that is applied when the Gateway starts up, affecting all API proxies deployed on Tyk. They can also be configured using the equivalent environment variables. The Gateway level settings are documented [here](/tyk-oss-gateway/configuration). + +If you are using a config file you can store settings, typically secrets, in environment variables or an external key-value store and provide references to the stored keys within the configuration file. This is explained [here](/tyk-configuration-reference/kv-store). + +### API and endpoint level settings + +API and endpoint level settings are configured using an *API definition*. + +This is a structured JSON object that encapsulates all of the details that apply specifically to that API, including the listen path, upstream target details, valid endpoints and operations, rate limits, authentication, versioning, and both built-in and custom middleware. + +You can store settings, typically secrets, in environment variables or an external key-value store and provide references to the stored keys within the API definition. This is explained [here](/tyk-configuration-reference/kv-store). + +API definition objects can be compact for a basic pass-through API, and can become very complex and large for APIs that require significant processing to be completed both before the request is proxied to the upstream service and once the response is received. + + +## API Definitions + +An *API definition* is the specification for an API proxy, providing Tyk with everything it needs to receive and process requests. Using Tyk's mock response, virtual endpoint and custom plugin functionality, you don't even need an upstream service - with a single API definition you can emulate a service entirely within Tyk, providing a [mock response](/api-management/traffic-transformation/mock-response#mock-response). + +Tyk supports two types of API definition depending on the type of service that you are looking to proxy: + +- [Tyk OAS API definitions](/api-management/gateway-config-tyk-oas) are used for REST and streaming use cases +- [Tyk Classic API definitions](/api-management/gateway-config-tyk-classic) are used for GraphQL, XML/SOAP and TCP services + + + + + For versions of Tyk prior to 5.8 not all Gateway features can be configured using the Tyk OAS API definition, for edge cases you might need to use Tyk Classic for REST APIs, though we recommend updating to Tyk 5.8 and adopting Tyk OAS. + + + + +### Migrating to Tyk OAS + +In Tyk 4.1, we introduced the Tyk OAS API definition but initially it supported only a subset of the Gateway configuration options offered by Tyk Classic. Since then we have gradually added support until finally, with the launch of Tyk 5.8, we have reached effective parity with Tyk Classic and now recommend that Tyk OAS is used exclusively for REST use cases. + +**Tyk 5.8 continues to support Tyk Classic for REST, but we will not be adding support for new features to this API definition style and strongly recommend migrating to Tyk OAS.** + +For Tyk Dashboard users with an existing portfolio of Tyk Classic API definitions, we provide a [migration tool](/api-management/migrate-from-tyk-classic), available via the Dashboard API and UI. + +### Storing API definitions + +For Tyk Open Source users, API definitions should be stored in `.json` files in the following location accessible by the Tyk Gateway: +- `/var/tyk-gateway/apps` (Linux) +- `/opt/tyk-gateway/apps` (Docker) + +For Tyk Dashboard users, API definitions will be kept in your [main storage](/api-management/dashboard-configuration#data-storage-solutions). + +### A note on terminology + +It's important not to confuse the *API proxy* with the API for the upstream service. Typically we refer to *API proxy* or *API* when refering to the endpoints exposed on Tyk Gateway and *upstream* or *upstream API* for the service that you develop and deploy to perform your business logic and data handling. + + diff --git a/api-management/gateway-config-managing-classic.mdx b/api-management/gateway-config-managing-classic.mdx new file mode 100644 index 0000000000..c7442b0230 --- /dev/null +++ b/api-management/gateway-config-managing-classic.mdx @@ -0,0 +1,556 @@ +--- +title: "Managing Tyk Classic API Definition" +description: "A guide to managing Tyk Classic API definitions" +keywords: "Tyk Classic API, Create, Update, Import, API Key, Security Policy" +sidebarTitle: "Working with Tyk Classic" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; +import CreateApiInclude from '/snippets/create-api-include.mdx'; +import CreateApiKeyInclude from '/snippets/create-api-key-include.mdx'; +import CreateSecurityPolicyInclude from '/snippets/create-security-policy-include.mdx'; +import ImportApiInclude from '/snippets/import-api-include.mdx'; + +## Create an API + +### What does it mean to create an API in Tyk + +You have a running service with an API that you want your users to consume; you want to protect and manage access to that API using Tyk Gateway - how do you do that? +
+For Tyk Gateway to protect and [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) calls to your upstream service, you need to configure an API on Tyk Gateway. The minimum information that Tyk requires is the **listen path** (which is a path on the Tyk Gateway URL that you want your consumers to call) and your **API URL** (which is the URL of your service to which Tyk should forward requests). +
+This information and other configuration values are stored in an object called a *Tyk API Definition*. Once you have created your Tyk API Definition and deployed it in the Gateway, Tyk can start serving your consumers, forwarding their requests to your upstream service's API. + +To reach a detailed guide to creating Tyk API Definitions, please choose the tab for the product you are using: + +### Tyk Cloud + +Tyk Cloud is a fully managed service that makes it easy for API teams to create, secure, publish and maintain APIs at any scale, anywhere in the world. Tyk Cloud includes everything you need to manage your global API ecosystem: [Tyk Gateways](/tyk-oss-gateway), [Tyk Dashboard](/api-management/dashboard-configuration), [Tyk Developer Portal](/portal/overview/intro) and [Universal Data Graph](/api-management/data-graph#overview). +
+ +To embark on your API journey with Tyk Cloud, we recommend going to our [Quick Start guide](/tyk-cloud#quick-start-tyk-cloud). This guide will walk you through the process of creating your very first API in Tyk Cloud. +For an advanced step by step guide we recommend visiting our [Getting Started guide](/tyk-cloud#comprehensive-tyk-cloud-setup). This will explain advanced configuration steps relating to how to distribute your API across nodes, in addition to adding and testing your API. + +### Tyk Self-Managed + + + +If the command succeeds, you will see: +```json +{ + "action": "added", + "key": "xxxxxxxxx", + "status": "ok" +} +``` + +**What did we just do?** + +We just sent an API definition to the Tyk `/apis` endpoint. See [API definition objects](/api-management/gateway-config-tyk-classic) for details of all the available objects. These objects encapsulate all of the settings for an API within Tyk. + +Want to learn more from one of our team of engineers? + + + +### Tyk Open Source + + + +**Note: Integration with your OpenAPI documentation** + +In Tyk v4.1 we introduced support for APIs defined according to the [OpenAPI Specification v3.0.3](https://spec.openapis.org/oas/v3.0.3) (OAS). +This introduces a standard way to describe the vendor-agnostic elements of an API (the OpenAPI Definition, stored as an OpenAPI Document); we take this and add Tyk-specific configuration options to create the *Tyk OAS API Definition*. You can import your own OpenAPI document and Tyk will use this to generate the Tyk OAS API Definition. +For details on using Tyk OAS with Tyk Gateway, check out our guide to [working with Tyk OAS APIs](/api-management/gateway-config-managing-oas). + + + +**Prerequisites** + +Before you continue this tutorial, you will need a running [Tyk OSS gateway](/tyk-oss-gateway). Click the button for instructions on how to install Tyk Gateway: + + + +#### Creating an API on Tyk Gateway + +There are two ways to configure Tyk Gateway with an API definition: +1. [Create an API with the Tyk Gateway API](#using-tyk-gateway-api) - Tyk Gateway has its own APIs which provides various services including the registering of Tyk API Definitions on the Gateway. +2. [Create an API in File-based Mode](#create-an-api-in-file-based-mode) - alternatively you can create a Tyk API Definition in a file and then load it to the Gateway. + + +#### Using Tyk Gateway API + +Watch our video to learn how to add an API to Tyk's Open Source Gateway using [Postman](https://www.postman.com/downloads/). + + + +In order to use the Gateway API to create a Tyk API Definition you will need the API key for your deployment's Gateway API and then issue just one command to create the API and make it live. + +1. **Make sure you know your API secret** + + The API key to access your Tyk Gateway API is stored in your `tyk.conf` file; the property is called `secret`. You will need to provide this value in a header called `x-tyk-authorization` when making calls to the Gateway API. + +2. **Create an API** + + To create the API, let's send a Tyk API definition to the `/apis` endpoint on your Tyk Gateway. Remember to change the `x-tyk-authorization` value (API key) in the header of your API call and set the domain name and port to target your Tyk Gateway in the `curl` command. + ```curl + curl -v -H "x-tyk-authorization: {your-secret}" \ + -s \ + -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "name": "Hello-World", + "slug": "hello-world", + "api_id": "Hello-World", + "org_id": "1", + "use_keyless": true, + "auth": { + "auth_header_name": "Authorization" + }, + "definition": { + "location": "header", + "key": "x-api-version" + }, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "use_extended_paths": true + } + } + }, + "proxy": { + "listen_path": "/hello-world/", + "target_url": "http://httpbin.org", + "strip_listen_path": true + }, + "active": true + }' http://{your-tyk-host}:{port}/tyk/apis | python -mjson.tool + ``` + + If the command succeeds, you will see: + ```json + { + "key": "Hello-World", + "status": "ok", + "action": "added" + } + ``` + + + +All APIs deployed on Tyk Gateway are given a unique `API ID`; if you don't provide one in the Tyk API Definition when creating the API, then an `API ID` will be generated automatically. + + + +**What did we just do?** + +We just registered a new API on your Tyk Gateway by sending a Tyk API definition to your Gateway's `/apis` endpoint. +Tyk API definitions encapsulate all of the settings for an API within Tyk Gateway and are discussed in detail in the [API section](/api-management/gateway-config-tyk-classic) of this documentation. + +**Restart or hot reload** + +Once you have created the file, you will need to either restart the Tyk Gateway, or issue a hot reload command, lets do the latter: +```curl +curl -H "x-tyk-authorization: {your-secret}" -s http://{your-tyk-host}:{port}/tyk/reload/group | python -mjson.tool +``` + +This command will hot-reload your API Gateway(s) and the new API will be loaded, if you take a look at the output of the Gateway (or the logs), you will see that it should have loaded Hello-World API on `/hello-world/`. + +#### Create an API in File-based Mode + + + +APIs created without API ID in file based mode are invalid. + + + + +To create a file-based API definition is very easy. + +Create a file called `api1.json` and place it in the `/apps` folder of your Tyk Gateway installation (usually in `/var/tyk-gateway`), then add the following: +```json +{ + "name": "Test API", + "slug": "test-api", + "api_id": "1", + "org_id": "1", + "auth_configs": { + "authToken": { + "auth_header_name": "Authorization" + } + }, + "definition": { + "location": "header", + "key": "x-api-version" + }, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "use_extended_paths": true + } + } + }, + "proxy": { + "listen_path": "/test-api/", + "target_url": "http://httpbin.org/", + "strip_listen_path": true + }, + "active": true +} +``` + +**Restart or hot reload** + +Once you have created the file, you will need to either restart the Tyk Gateway, or issue a hot reload command, lets do the latter: +```curl +curl -H "x-tyk-authorization: {your-secret}" -s https://{your-tyk-host}:{port}/tyk/reload/group | python -mjson.tool +``` + +This command will hot-reload your API Gateway(s) and the new API will be loaded, if you take a look at the output of the Gateway (or the logs), you will see that it should have loaded Test API on `/test-api/`. + +Your API is now ready to use via the Gateway. + +## Secure an API + +A security policy encapsulates several options that can be applied to a key. It acts as a template that can override individual sections of an API key (or identity) in Tyk. + +See [What is a Security Policy?](/api-management/policies) for more details. + +### Tyk Cloud + + + +### Tyk Self Manged + + + +### Tyk Open Source + +#### Create a Policy with the Gateway + +Adding a policy to the Tyk Gateway is very easy. Polices are loaded into memory on load and so need to be specified in advanced in a file called `policies.json`. To add a policy, simply create or edit the `/policies/policies.json` file and add the policy object to the object array: + +```json +{ + "POLICYID": { + "access_rights": { + "{API-ID}": { + "allowed_urls": [], + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "versions": [ + "Default" + ] + } + }, + "active": true, + "name": "POLICY NAME", + "rate": 1000, + "per": 1, + "quota_max": 10000, + "quota_renewal_rate": 3600, + "tags": ["Startup Users"] + } +} +``` + +The above creates a new policy with a policy ID that you can define, with the rate limits, and security profile that grants access to the APIs listed in the `access_rights` section. + +- `{API-ID}`: The API ID you wish this policy to grant access to, there can be more than one of these entries. +- `{API-NAME}`: The name of the API that is being granted access to (this is not required, but helps when debugging or auditing). +- `POLICY NAME`: The name of this security policy. + +The important elements: + +- `access_rights`: A list of objects representing which APIs that you have configured to grant access to. +- `rate` and `per`: The number of requests to allow per period. +- `quota_max`: The maximum number of allowed requests over a quota period. +- `quota_renewal_rate`: how often the quota resets, in seconds. In this case we have set it to renew every hour. + +## Access an API + +### Tyk Cloud + + + +You will see a 200 response with your new key: + +```yaml +{ + "api_model": {}, + "key_id": "59bf9159adbab8abcdefghijac9299a1271641b94fbaf9913e0e048c", + "data": {...} +} +``` + +The value returned in the `key_id` parameter of the response is the access key you can now use to access the API that was specified in the `access_rights` section of the call. + +### Tyk Self Managed + + + +You will see a response with your new key: + +```json +{ + "action": "create", + "key": "c2cb92a78f944e9a46de793fe28e847e", + "status": "ok" +} +``` + +The value returned in the `key` parameter of the response is the access key you can now use to access the API that was specified in the `access_rights` section of the call. + +### Tyk Open Source + +To create an API Key, you will need the API ID that we wish to grant the key access to, then creating the key is an API call to the endpoint. + +**Prerequisite** + +- You will need your API secret, this is the `secret` property of the `tyk.conf` file. + +Once you have this value, you can use them to access the Gateway API, the below `curl` command will generate a key for one of your APIs, remember to replace `{API-SECRET}`, `{API-ID}` and `{API-NAME}` with the real values as well as the `curl` domain name and port to be the correct values for your environment. + +```curl +curl -X POST -H "x-tyk-authorization: {API-SECRET}" \ + -s \ + -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "allowance": 1000, + "rate": 1000, + "per": 1, + "expires": -1, + "quota_max": -1, + "org_id": "1", + "quota_renews": 1449051461, + "quota_remaining": -1, + "quota_renewal_rate": 60, + "access_rights": { + "{API-ID}": { + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "versions": ["Default"] + } + }, + "meta_data": {} + }' http://localhost:8080/tyk/keys/create | python -mjson.tool +``` + +The above creates a new key with the rate limits, and security profile that grants access to the APIs listed in the `access_rights` section. + +- `{API-ID}`: The API ID you wish this policy to grant access to, there can be more than one of these entries. +- `{API-NAME}`: The name of the API being granted access to (this is not required, but helps when debugging or auditing). + +The important elements: + +- `access_rights`: A list of objects representing which APIs you have configured to grant access to. +- `rate` and `per`: The number of allowed requests per period. +- `quota_max`: The maximum number of allowed requests over a quota period. +- `quota_renewal_rate`: how often the quota resets, in seconds. In this case, we have set it to renew every hour. + +You will see a response with your new key: + +```json +{ + "action": "create", + "key": "c2cb92a78f944e9a46de793fe28e847e", + "status": "ok" +} +``` + +The value returned in the `key` parameter of the response is the access key you can now use to access the API that was specified in the `access_rights` section of the call. + +## Import an API + +Tyk supports importing both API Blueprint and Swagger (OpenAPI) JSON definitions from either the Gateway or the Dashboard. Tyk will output the converted file to to `stdout`. Below are the commands you can use to get Tyk to switch to command mode and generate the respective API definitions for both API Blueprint and Swagger files. + +### API Blueprint is being deprecated + +Our support for API Blueprint is being deprecated. We have been packaging [aglio](https://github.com/danielgtaylor/aglio) in our Docker images for the Dashboard which enables rendering API Blueprint Format in the portal. This module is no longer maintained and is not compatible with newer NodeJS. If you wish to continue using this feature, you can do so by installing the module yourself in your Dockerfile. The imapct of this change is that our Docker images will no longer contain this functionality. + +As a work around, you can do the following: + +* Create API Blueprint in JSON format using the Apiary [Drafter](https://github.com/apiaryio/drafter) tool +* Convert API Blueprint to OpenAPI (Swagger) using the Apiary [API Elements CLI](https://github.com/apiaryio/api-elements.js/tree/master/packages/cli) tool. + +### Using API Blueprint + + + +See [note](#api-blueprint-is-being-deprecated) above regarding deprecation of support for API Blueprint. + + + +Tyk supports an easy way to import Apiary API Blueprints in JSON format using the command line. + +Blueprints can be imported and turned into standalone API definitions (for new APIs) and also imported as versions into existing APIs. + +It is possible to import APIs and generate mocks or to generate Allow Lists that pass-through to an upstream URL. + +All imported Blueprints must be in the JSON representation of Blueprint's markdown documents. This can be created using Apiary's [Snow Crash tool](https://github.com/apiaryio/snowcrash). + +Tyk outputs all new API definitions to `stdout`, so redirecting the output to a file is advised in order to generate new definitions to use in a real configuration. + +#### Importing a Blueprint as a new API: + +Create a new definition from the Blueprint: + +```{.copyWrapper} +./tyk --import-blueprint=blueprint.json --create-api --org-id= --upstream-target="http://widgets.com/api/" +``` + +#### Importing a definition as a version in an existing API: + +Add a version to a definition: + +```{.copyWrapper} +./tyk --import-blueprint=blueprint.json --for-api= --as-version="version_number" +``` + +#### Creating your API versions as a mock + +As the API Blueprint definition allows for example responses to be embedded, these examples can be imported as forced replies, in effect mocking out the API. To enable this mode, when generating a new API or importing as a version, simply add the `--as-mock` parameter. + +### Using Swagger (OpenAPI) + +Tyk supports importing Swagger documents to create API definitions and API versions. Swagger imports do not support mocking though, so sample data and replies will need to be added manually later. + +#### Importing a Swagger document as a new API + +Create a new definition from Swagger: + +```{.copyWrapper} +./tyk --import-swagger=petstore.json --create-api --org-id= --upstream-target="http://widgets.com/api/" +``` + + +When creating a new definition from an OAS 3.0 spec, you will have to manually add the listen path after the API is created. + + + + +#### Importing a Swagger document as a version into an existing API + +Add a version to a definition: + +```{.copyWrapper} +./tyk --import-swagger=petstore.json --for-api= --as-version="version_number" +``` + +#### Mocks + +Tyk supports API mocking using our versioning `use_extended_paths` setup, adding mocked URL data to one of the three list types (white_list, black_list or ignored). In order to handle a mocked path, use an entry that has `action` set to `reply`: + +```json +"ignored": [ + { + "path": "/v1/ignored/with_id/{id}", + "method_actions": { + "GET": { + "action": "reply", + "code": 200, + "data": "Hello World", + "headers": { + "x-tyk-override": "tyk-override" + } + } + } + } +], +``` + +See [Versioning](/api-management/gateway-config-tyk-classic#tyk-classic-api-versioning) for more details. + +### Import APIs via the Dashboard API + + + +### Import APIs via the Dashboard UI + +1. **Select "APIs" from the "System Management" section** + + API listing + +2. **Click "IMPORT API"** + + Add API button location + + Tyk supports the following import options: + + 1. From an Existing Tyk API definition + 2. From a Apiary Blueprint (JSON) file + 3. From a Swagger/OpenAPI (JSON only) file + 4. From a SOAP WSDL definition file (new from v1.9) + + To import a Tyk Definition, just copy and paste the definition into the code editor. + + For Apiary Blueprint and Swagger/OpenAPI, the process is the same. For example: + + Click the "From Swagger (JSON)" option from the pop-up + + Import popup + + For WSDL: + + Import WSDL + +3. **Enter API Information** + + You need to enter the following information: + + * Your **Upstream Target** + * A **Version Name** (optional) + * An optional **Service Name** and **Port** (WSDL only) + * Copy code into the editor + +4. **Click "Generate API"** + + Your API will appear in your APIs list. If you select **EDIT** from the **ACTIONS** drop-down list, you can see the endpoints (from the [Endpoint Designer](/api-management/endpoint-designer)) that have been created as part of the import process. + +### Creating a new API Version by importing an API Definition using Tyk Dashboard + +As well as importing new APIs, with Tyk, you can also use import to create a new version of an existing Tyk Classic API. + +1. Open the API Designer page and select Import Version from the **Options** drop-down. + + Import API Version Drop-Down + +2. Select either OpenAPI (v2.0 or 3.0) or WSDL/XML as your source API + +3. You need to add a new **API Version Name**. **Upstream URL** is optional. + + Import API Version Configuration + +4. Click **Import API**. + + Import API + +5. Select the **Versions** tab and your new version will be available. +6. Open the **Endpoint Designer** for your API and select your new version from **Edit Version**. +7. You will see all the endpoints are saved for your new version. + +Version Endpoints + +##### Import from an OpenAPI v2.0 Document + +1. From the Import API screen, select OpenAPI. + + Import OAS 2.0 API + +2. Paste your OAS v2.0 compliant definition into the code editor. + + OAS 2.0 definition in Editor + +3. Note that the Dashboard has detected that an OAS v2.0 definition has been imported and you need to specify an upstream URL field to proceed. + + Upstream URL + +4. Click **Import API**. + + Import API + + Your API will be added to your list of APIs. diff --git a/api-management/gateway-config-managing-oas.mdx b/api-management/gateway-config-managing-oas.mdx new file mode 100644 index 0000000000..8ffe723021 --- /dev/null +++ b/api-management/gateway-config-managing-oas.mdx @@ -0,0 +1,859 @@ +--- +title: "Working with Tyk OAS APIs" +description: "A guide to working with and managing Tyk OAS API definitions" +keywords: "Tyk OAS API, Create, Update, Import, Export, Versioning, API Key, Security Policy" +sidebarTitle: "Working with Tyk OAS" +--- + +## Overview + +Tyk's support for the OpenAPI Specification is designed to fit in with your existing workflows as seamlessly as possible, whether you have one of our paid offerings, or are using our free open-source Gateway. You should be able to do a huge amount in the editor of your choice. The Tyk Dashboard's API Designer will support you whether you want to create a new API from a blank slate, or just to dip into if you want a bit of help with configuring Tyk's powerful transformation middleware. + +One of the great things about working with Tyk is that the OpenAPI document containing the OAS compliant description of your service is a single file (or group of files) that you deploy throughout your workflow. You can iterate on that document within your source control system until you are totally happy. At this point, you can publish the OpenAPI description to your Developer Portal to document what a Developer needs to use the API (and nothing they don’t need to know). As the OpenAPI description is the source of truth for the Tyk OAS API definition and can be updated without impacting the Tyk Vendor Extension, you can automate deployment of updates to your API on Tyk whenever a new version is committed into your source control. This model is very popular in GitOps and CI/CD environments. + +Tyk OAS API workflow + + +### API Definition Management with Tyk + +There are three methods by which API definitions can be deployed to Tyk: using the [Tyk Dashboard API Designer](/api-management/dashboard-configuration), using the [Tyk Dashboard API](/tyk-dashboard-api) and using the [Tyk Gateway API](/tyk-gateway-api). + +The first two options provide access to the powerful licensed features of Tyk, whilst the third is used for open source deployments. Tyk provides additional tools to assist with automation when using the Tyk Dashboard API - namely [Tyk Operator](/api-management/automations/operator)(for Kubernetes deployments) and [Tyk Sync](/api-management/automations/sync) (for gitops). + +| Feature | API Designer | Tyk Dashboard API | Tyk Gateway API | +| :------------------- | :-------------- | :------------------- | :----------------- | +| Work with YAML format | ✅ | ✅ | ❌ | +| Work with JSON format | ✅ | ✅ | ✅ | +| Import an OpenAPI description | ✅ | ✅ | ✅ | +| Import a complete Tyk OAS API definition | ✅ | ✅ | ✅ | +| Import [multi-part OpenAPI descriptions](/api-management/gateway-config-managing-oas#multi-part-openapi-documents) | ✅ | ✅ | ❌ | +| Apply API [Templates](/platform-management/api-templates) | ✅ | ✅ | ❌ | +| Export the OpenAPI description | ✅ | ✅ | ✅ | +| Export the Tyk OAS API definition | ✅ | ✅ | ✅ | +| Update API with new OpenAPI description | ✅ | ✅ | ✅ | +| Manage API versions | ✅ | ✅ | ✅ | +| Assign APIs to [Categories](/platform-management/api-categories) | ✅ | ✅ | ❌ | +| Assign API [Owners](/platform-management/api-ownership) | ✅ | ✅ | ❌ | + +## Creating an API + +Tyk is designed to fit into your workflow, so has full support for you to import your existing OpenAPI descriptions as the starting point for a Tyk OAS API. Tyk can automatically configure aspects of the Gateway's API security and management functions based upon the content of the OpenAPI description, for example using the security settings to configure client authentication or the endpoint examples and schemas to configure request validation and mock response middleware. + +Alternatively, if you don't have an existing OpenAPI description, you can use the API Designer to bootstrap one for you: build your API in Tyk and then export an OAS compliant description that you can use elsewhere, for example as documentation for your new API. + +### Using Tyk Dashboard API Designer to create an API + +In this tutorial we guide you through the steps to create a new Tyk OAS API using the GUI. + +{/* - hiding this video as it is out of date */} + +1. Start by selecting **APIs** from the **API Management** section + + Add new API + +2. Now select **Add New API** and then, choose **Design from scratch** + + Start designing a new API + +3. Now complete the basic configuration for your new API following the guided steps providing: + - API name + - API type (**HTTP**) + - API style (**OpenAPI**) + - [API template](/platform-management/api-templates#working-with-api-templates-using-the-template-designer) (optional) + - Upstream URL + + Basic configuration of the new API + +4. Deploy the API to your Gateway + + - If you are using Tyk Cloud or a [sharded](/api-management/multiple-environments) deployment you will be prompted to select on which Gateways the API should be deployed + + Choose where to deploy the API + + - You need to set the **API status** (if you set this to **Active**, Tyk will accept requests to the API) + - You need to set the **Access** (set this to **External** to expose your API outside Tyk so that your clients can consume it) + - When creating a new API you will probably want to set API status to **Inactive** while you configure the rest of the API definition + + Set API Status + + Click **Save API** to create the API definition and, depending on the options you chose for API status and access, deploy the API to your gateway to start serving traffic. + + + + + You can see the URL given to your API, in the Info section displayed at the top of the page (**API URL**). + + + +5. Secure your API by configuring [client authentication](/api-management/client-authentication) + + From the API page: + + 1. Click **Edit** + 2. Scroll down to the **Server** section and enable **Authentication** + 3. Select **Auth Token** from the drop-down list + 4. For **Authentication token location** select **Use header value** + 5. Note that the default Auth key header name is *Authorization* + 6. Save your API + +6. Declare endpoints for your API + + 1. After selecting **Edit**, move to the **Endpoints** tab. + + Add new endpoint + + + 2. Click **Add Endpoint** then complete the requested details for the new endpoint: + + - Select a method from the drop-down list + - Add a path for your endpoint + - Add an optional summary and description + - select **Add Endpoint** + + Provide the details of the new endpoint + + 3. Your new endpoint will now be listed in the Endpoints tab + + List of all endpoints declared for the API + + 4. You can now add [middleware](/api-management/traffic-transformation) to your endpoint via the **Add Middleware** button. + + 5. Click **Save API** to apply the changes to your API. + +7. Test your API + + From the **Info** section, copy the [API base path](/api-management/gateway-config-managing-oas#api-base-path) and send a request to the API without providing an authorization token: + + ``` + curl --location --request GET 'http://localhost:8181/petstore/' \ + --header 'Authorization: wrongkey' + ``` + + Note that the Gateway will respond with the following error message, confirming that authentication is required: + + ```.json + { + "error": "Access to this API has been disallowed" + } + ``` + +### Using your own code editor to create Tyk OAS API definitions + +The API definition is often generated either from the codebase or using API design tools (such as [Swagger Editor](https://editor.swagger.io/), [Postman](https://www.postman.com/) and [Stoplight](https://stoplight.io/)). + +To enjoy writing a *Tyk OAS API definition* as if it is [a native programming language](https://tyk.io/blog/get-productive-with-the-tyk-intellisense-extension/), you can add the [Tyk OAS API definition schema](https://raw.githubusercontent.com/TykTechnologies/tyk-schemas/main/JSON/draft-04/schema_TykOasApiDef_3.0.x.json) to your favorite IDE or editor. We have published a Tyk VS Code extension that provides Tyk API schema validation and auto-completion (both OAS and other schemas) in the [VS Code marketplace](https://marketplace.visualstudio.com/items?itemName=TykTechnologiesLimited.tyk-schemas). You can use it to create Tyk objects in your IDE (Tyk API definitions, Key and Tyk config file). + +#### Loading the API definition into Tyk + + + + + +Armed with a Tyk OAS API definition, in YAML or JSON format, you can use this to create an API in Tyk Dashboard with only a few clicks. + +1. Start by selecting **APIs** from the **API Management** section + + Add new API + +2. Now select **Add New API** and then, choose **Import**. + + Loading the API definition into Tyk Dashboard + + Note that you can optionally apply an [API template](/platform-management/api-templates) by choosing **Start from template** as explained [here](/platform-management/api-templates#using-a-template-when-creating-a-new-api), however in this explanation we will not be applying a template. + +3. From the Import API screen, select **Tyk API** because the object you want to import to Tyk is a complete API definition. + + Choosing what to import + + + + + On the Import API screen, there are three options for Import Type, it is important to select the correct one for the object that you want to load into Tyk: + + - openAPI is used only for [OpenAPI descriptions](/api-management/gateway-config-tyk-oas#openapi-description) (without the [Tyk Vendor Extension](/api-management/gateway-config-tyk-oas#tyk-vendor-extension)) + - TykAPI is used for a full [Tyk OAS API definition](/api-management/gateway-config-tyk-oas#what-is-a-tyk-oas-api-definition) (comprising OpenAPI description plus Tyk Vendor Extension) or Tyk Classic API definition + - WSDL/XML is used for WSDL/XML content and will result in a Tyk Classic API + + + +4. Now you can paste the entire Tyk OAS API definition into the text editor. + + Loading the API definition into Tyk Dashboard + +5. Select **Import API** to complete the import and create the API based on your API definition. + + + + + +When making calls to the Tyk Dashboard API you'll need to set the domain name and port for your environment and provide credentials in the `Authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :------------------- | :------ | :---------------------- | :----------------------------- | +| Tyk Dashboard API | 3000 | `Authorization` | From Dashboard User Profile | + +You can obtain your authorization credential (Dashboard API key) from the Tyk Dashboard UI: + +- Select **Edit profile** from the dropdown that appears when you click on your username in the top right corner of the screen +- Scroll to the bottom of the page were you will see your **Tyk Dashboard API Access Credentials** + +You will also need to have ‘admin’ or ‘api:write’ permission if [RBAC](/api-management/user-management) is enabled. + +To create the API in Tyk, you simply send your Tyk OAS API Definition in the payload to the `POST /api/apis/oas` endpoint of your Tyk Dashboard API. + +| Property | Description | +| :-------------- | :-------------------------- | +| Resource URL | `/api/apis/oas` | +| Method | `POST` | +| Type | None | +| Body | Tyk OAS API Definition | +| Parameters | Query: `templateID` | + +Using [this](https://bit.ly/39jUnuq) API definition it is possible to create a Tyk OAS API on your Tyk Gateway that forwards requests to the [Swagger Petstore](https://petstore3.swagger.io) request/response service. + +``` +curl -H "Authorization: ${DASH_KEY}" -H "Content-Type: application/json" ${DASH_URL}/apis/oas -d "$(wget -qO- https://bit.ly/39jUnuq)" +``` + +**Check request response** + +If the command succeeds, you will see the following response, where `Meta` contains the unique identifier (`id`) for the API you have just created. If you did not provide a value in the `id` field, then Tyk will automatically assign one. + +``` +{ + "Status": "OK", + "Message": "API created", + "Meta": {NEW-API-ID} +} +``` + +What you have done is to send a Tyk OAS API definition to Tyk Dashboard's `/api/apis/oas` endpoint resulting in the creation of the API in your Tyk Dashboard which will automatically deploy it to your Gateway. + +You can use the optional `templateId` parameter to apply an [API Template](/platform-management/api-templates#applying-a-template-when-creating-an-api-from-a-tyk-oas-api-definition) to your API definition when creating the API. + + + + + +When making calls to the Tyk Gateway API you'll need to set the domain name and port for your environment and provide credentials in the `x-tyk-authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :----------------- | :------ | :----------------------- | :---------------------------------- | +| Tyk Gateway API | 8080 | `x-tyk-authorization` | `secret` value set in `tyk.conf` | + +To create the API in Tyk, you simply send your Tyk OAS API Definition in the payload to the `POST /tyk/apis/oas` endpoint of your Tyk Gateway API. + +Using [this](https://bit.ly/39tnXgO) minimal API definition it is possible to create a Tyk OAS API on your Tyk Gateway using only 30 lines: + +```curl +curl --location --request POST 'http://{your-tyk-host}:{port}/tyk/apis/oas' \ +--header 'x-tyk-authorization: {your-secret}' \ +--header 'Content-Type: text/plain' \ +--data-raw +'{ + "info": { + "title": "Petstore", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Petstore", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore.swagger.io/v2" + }, + "server": { + "listenPath": { + "value": "/petstore/", + "strip": true + } + } + } +}' +``` + +**Check request response** + +If the command succeeds, you will see the following response, where `key` contains the unique identifier (`id`) for the API you have just created. If you did not provide a value in the `id` field, then Tyk will automatically assign one. + +```.json +{ + "key": {NEW-API-ID}, + "status": "ok", + "action": "added" +} +``` + +What you have done is to send a Tyk OAS API definition to Tyk Gateway's `/tyk/apis/oas` endpoint resulting in the creation of the API in your Tyk Gateway. + +**Restart or hot reload** + +Once you have created your API you need to load it into the Gateway so that it can serve traffic. To do this you can either restart the Tyk Gateway or issue a [hot reload](/tyk-stack/tyk-gateway/important-prerequisites#hot-reload-is-critical-in-tyk-ce) command: + +```.curl +curl -H "x-tyk-authorization: {your-secret}" -s http://{your-tyk-host}:{port}/tyk/reload/group +``` + +You can go to the `/apps` folder of your Tyk Gateway installation (by default in `/var/tyk-gateway`) to see where Tyk has stored your Tyk OAS API Definition. + + + + + +### Importing an OpenAPI description to create an API + +Tyk will automatically update the `servers` section in the imported OpenAPI description, adding the base path URL to which requests should be sent to access the new API. It will take the existing entry and use this to generate the upstream (target) URL if none is provided. + + + + + +If you have a valid OAS 3.0 or OAS 3.1 compliant OpenAPI description, in YAML or JSON format, you can use this to create an API in Tyk Dashboard with only a few clicks. + +1. Start by selecting **APIs** from the **API Management** section + + Add new API + +2. Now select **Add New API** and then, choose **Import**. + + Loading the API definition into Tyk Dashboard + +3. From the Import API screen, select **openAPI** because the object you want to import to Tyk is an OpenAPI description. + + Choosing what to import + + + + + On the Import API screen, there are three options for Import Type, it is important to select the correct one for the object that you want to load into Tyk: + + - openAPI is used only for [OpenAPI descriptions](/api-management/gateway-config-tyk-oas#openapi-description) (without the [Tyk Vendor Extension](/api-management/gateway-config-tyk-oas#tyk-vendor-extension)) + - TykAPI is used for a full [Tyk OAS API definition](/api-management/gateway-config-tyk-oas#what-is-a-tyk-oas-api-definition) (comprising OpenAPI description plus Tyk Vendor Extension) or Tyk Classic API definition + - WSDL/XML is used for WSDL/XML content and will result in a Tyk Classic API + + + +4. Now you can choose the location of the OpenAPI description, which can be: + + - pasted into the text editor + - uploaded using a file picker + - retrieved from a file server + + Loading the API definition into Tyk Dashboard + +5. You can optionally apply an [API template](/platform-management/api-templates) from the drop-down. + + Applying a template + +6. You can configure the *listen path* and *upstream (target) URL* in the **Manual configuration options** section. Note that if you do not provide a listen path, Tyk will default to `/` and if you do not provide an upstream URL, Tyk will use the first value provided in the [servers.url](/api-management/gateway-config-managing-oas#api-base-path) section in the OpenAPI description. + + Configuring the listen path and upstream URL + +7. Tyk can automatically configure the request processing middleware chain based upon configuration defined by the OpenAPI Specification. If your OpenAPI desription contains the relevant data then select the characteristics you would like to configure. + + Configuring the listen path and upstream URL + + | Middleware | OpenAPI data used for configuration | + |------------|-------------------------------------| + | [Request validation](/api-management/traffic-transformation/request-validation#request-schema-in-openapi-specification) | Endpoints that have `requestBody` or `schema` | + | [Mock response](/api-management/traffic-transformation/mock-response#tyk-oas) | Endpoints with `examples` or `schema` | + | [Client authentication](/api-management/client-authentication#how-does-tyk-implement-authentication-and-authorization) | Defined in `security` and `securitySchemes` | + | [Allow list](/api-management/traffic-transformation/allow-list) | Restrict access only to declared endpoint paths | + +8. Select **Import API** to complete the import and create the API based on your API definition. + + + + + +When making calls to the Tyk Dashboard API you'll need to set the domain name and port for your environment and provide credentials in the `Authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :------------------- | :------ | :---------------------- | :----------------------------- | +| Tyk Dashboard API | 3000 | `Authorization` | From Dashboard User Profile | + +You can obtain your authorization credential (Dashboard API key) from the Tyk Dashboard UI: + +- Select **Edit profile** from the dropdown that appears when you click on your username in the top right corner of the screen +- Scroll to the bottom of the page were you will see your **Tyk Dashboard API Access Credentials** + +You will also need to have ‘admin’ or ‘api:write’ permission if [RBAC](/api-management/user-management) is enabled. + +To create the API in Tyk, you simply send your OpenAPI document in the payload to the `POST /api/apis/oas/import` endpoint of your Tyk Dashboard API. + +| Property | Description | +| :-------------- | :------------------------------------------ | +| Resource URL | `/api/apis/oas/import` | +| Method | `POST` | +| Type | None | +| Body | OpenAPI Document | +| Parameters | Query: `listenPath` `upstreamURL` `authentication` `allowList` `validateRequest` `mockResponse` `apiID` `templateId` | + +The optional parameters are: + +| Parameter | Effect | Default if omitted | +| :------------------- | :--------------------------------- | :-------------------- | +| `listenPath` | Set the listen path for the API | Defaults to `/` | +| `upstreamURL` | Set the upstream (target) URL | Defaults to the first URL in the `servers` section of the [OpenAPI description](/api-management/gateway-config-managing-oas#api-base-path) | +| `authentication` | Configure [client authentication](/api-management/client-authentication#how-does-tyk-implement-authentication-and-authorization) based on `security` and `securitySchemes` | Client authentication is not configured | +| `allowList` | Enable [allow list](/api-management/traffic-transformation/allow-list) middleware for all endpoints declared in the OpenAPI description | Allow list not configured | +| `validateRequest` | Configure [request validation](/api-management/traffic-transformation/request-validation#request-schema-in-openapi-specification) for all endpoints with `requestBody` or `schema` defined | Request validation not configured | +| `mockResponse` | Configure [mock response](/api-management/traffic-transformation/mock-response#tyk-oas) for all endpoints with `examples` or `schema` defined | Mock response not configured | +| `apiID` | Id to be assigned to the new API | Tyk will determine and assign a unique Id | +| `templateId` | Apply the selected [API template](/platform-management/api-templates#applying-a-template-when-creating-an-api-from-a-tyk-oas-api-definition) when creating the API | No template is applied | + +**Check request response** + +If the command succeeds, you will see the following response, where `Meta` contains the unique identifier (`id`) for the API you have just created. + +``` +{ + "Status": "OK", + "Message": "API created", + "Meta": {NEW-API-ID} +} +``` + + + + + +When making calls to the Tyk Gateway API you'll need to set the domain name and port for your environment and provide credentials in the `x-tyk-authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :----------------- | :------ | :----------------------- | :---------------------------------- | +| Tyk Gateway API | 8080 | `x-tyk-authorization` | `secret` value set in `tyk.conf` | + +To create the API in Tyk, you simply send your OpenAPI document in the payload to the `POST /tyk/apis/oas/import` endpoint of your Tyk Gateway API. + +| Property | Description | +| :-------------- | :------------------------------------------ | +| Resource URL | `/tyk/apis/oas/import` | +| Method | `POST` | +| Type | None | +| Body | OpenAPI Document | +| Parameters | Query: `listenPath` `upstreamURL` `authentication` `allowList` `validateRequest` `mockResponse` `apiID` | + +The optional parameters are: + +| Parameter | Effect | Default if omitted | +| :------------------- | :--------------------------------- | :-------------------- | +| `listenPath` | Set the listen path for the API | Defaults to `/` | +| `upstreamURL` | Set the upstream (target) URL | Defaults to the first URL in the `servers` section of the [OpenAPI description](/api-management/gateway-config-managing-oas#api-base-path) | +| `authentication` | Configure [client authentication](/api-management/client-authentication#how-does-tyk-implement-authentication-and-authorization) based on `security` and `securitySchemes` | Client authentication is not configured | +| `allowList` | Enable [allow list](/api-management/traffic-transformation/allow-list) middleware for all endpoints declared in the OpenAPI description | Allow list not configured | +| `validateRequest` | Configure [request validation](/api-management/traffic-transformation/request-validation#request-schema-in-openapi-specification) for all endpoints with `requestBody` or `schema` defined | Request validation not configured | +| `mockResponse` | Configure [mock response](/api-management/traffic-transformation/mock-response#tyk-oas) for all endpoints with `examples` or `schema` defined | Mock response not configured | +| `apiID` | Id to be assigned to the new API | Tyk will determine and assign a unique Id | + +**Check request response** + +If the command succeeds, you will see the following response, where `key` contains the unique identifier (`id`) for the API you have just created. + +```.json +{ + "key": {NEW-API-ID}, + "status": "ok", + "action": "added" +} +``` + +**Restart or hot reload** + +Once you have created your API you need to load it into the Gateway so that it can serve traffic. To do this you can either restart the Tyk Gateway or issue a [hot reload](/tyk-stack/tyk-gateway/important-prerequisites#hot-reload-is-critical-in-tyk-ce) command: + +```.curl +curl -H "x-tyk-authorization: {your-secret}" -s http://{your-tyk-host}:{port}/tyk/reload/group +``` + +You can go to the `/apps` folder of your Tyk Gateway installation (by default in `/var/tyk-gateway`) to see where Tyk has stored your Tyk OAS API Definition. + + + + + +#### API base path + +The [API base path](https://swagger.io/docs/specification/v3_0/api-host-and-base-path/) is the URL that a client should use when consuming (sending requests to) the API deployed on Tyk. This will comprise the address of the Tyk Gateway plus the API's listen path. + +**Detecting an Existing API Base Path** + +When creating an API, Tyk analyzes the `servers.url` section of the OpenAPI description to determine if it already contains a valid API base path. + +- If the first entry in `servers.url` is an address on the Tyk Gateway, then this is considered a valid API base path. +- If there is not a valid API base path, then Tyk will assume that the first value in `servers.url` is the address of the upstream service - and so will use this as the *upstream (target) URL* for the API proxy. If there are multiple entries in `servers.url` Tyk will only consider the first entry and ignore all others. + +Tyk supports [OpenAPI server variables](https://learn.openapis.org/specification/servers.html#server-variables), so if the first `servers` entry contains a parameterised URL, Tyk will fill in the parameters with the values provided in the `variables` associated with that entry. + +**Setting the API Base Path** + +If the `servers.url` section did not contain a valid *API base path* then Tyk will insert a new entry in the first location in `servers.url` with a valid API base path comprising the Tyk Gateway address plus the *listen path* for the API. + +For example, given the following fragment of the OpenAPI description and importing to a Tyk Gateway at `https://my-gateway.com` specifying a listen path of `my-api`: + +```yaml + servers: + - url: https://upstream-A.com + - url: http://upstream-B.com +``` + +Tyk will configure the Tyk OAS API with the following: + +```yaml + servers: + - url: https://my-gateway.com/my-api/ + - url: https://upstream-A.com + - url: http://upstream-B.com + + x-tyk-api-gateway: + server: + listenPath: + value: /my-api/ + upstream: + url: https://upstream-A.com +``` + + + +This can introduce a change to the "source of truth" (OpenAPI description) for the API (the addition of the API base path). We recommend that you export the modified OpenAPI description and apply this to your documentation, as it provides the address to which clients should direct their traffic. + + + +**Upstream URL Override** + +The servers section is not analyzed if an upstream (target) URL is specified during the import action. If an upstream URL was specified, that will be used as the upstream for the API. The API base path will still be constructed and added to the `servers` section of the OpenAPI description. + +**Tyk does not support relative URLs** + +If the first entry is a relative URL, or another format that Tyk cannot process, the import will fail with an error. + +For example attempting to import an OpenAPI description containing this configuration: + +```yaml + servers: + - url: /relative-url + - url: http://upstream-B.com +``` +will error with the following message: + +```json +{ + "status": "error", + "message": "error validating servers entry in OAS: Please update \"/relative-url\" to be a valid url or pass a valid url with upstreamURL query param" +} +``` + +#### Multi-part OpenAPI documents + +The OpenAPI Specification allows an OpenAPI description to be [split across multiple files](https://swagger.io/docs/specification/v3_0/using-ref/) by use of the `$ref` keyword. + +This allows you to share snippets of the API definition across multiple APIs, or to have specific ownership of elements of the API configuration owned by different teams. + +Tyk Dashboard supports the creation of Tyk OAS APIs from these multi-part OpenAPI documents. + +We consider two different types of file containing these OpenAPI descriptions: + +- the **main fragment**, which contains the `info` section +- the **secondary fragments**, which contain snippets of the OpenAPI description that are referred to using external references (`$ref`) + +Note that secondary fragments can also contain external references to other secondary fragments (but not to the main fragment). + +When creating or updating an API, you simply provide Tyk with the **main fragment** and ensure that all of the references can be resolved. + +Resolution can be: +- local, by providing a ZIP archive containing all fragments +- remote, by providing resolvable paths to the secondary fragments (this is particularly used if the main fragment is provided via URL, as all fragments can then exist on the same file server). + + + The **main fragment** must be in a file named `openapi.json` or `openapi.yaml` (depending on the format used). + + + +##### Creating the ZIP Archive + +When creating ZIP archives for the multi-part OpenAPI import feature, it's important to exclude operating system metadata files that could interfere with the import process. + +- **MacOS Users** + + When using the `zip` command on MacOS, include the `-X` flag to exclude extended attributes and hidden files: `zip -X -r archive.zip directory/` + +- **Linux Users** + + When using the `zip` command on Linux, you can exclude hidden files using: `zip -r archive.zip directory/ -x "*/\.*"` + + To exclude specific metadata files: `zip -r archive.zip directory/ -x "*/\.*" "*/Thumbs.db" "*/.DS_Store"` + +- **Windows Users** + + When using PowerShell to create ZIP archives on Windows, you can exclude hidden and system files with: `Compress-Archive -Path "directory\*" -DestinationPath "archive.zip" -CompressionLevel Optimal` + + To exclude specific metadata files (like Thumbs.db or .DS_Store) you can use: `Get-ChildItem "directory" -Recurse -File | Where-Object { $_.Name -notmatch '(^\.DS_Store$|^Thumbs\.db$)' } | Compress-Archive -DestinationPath "archive.zip"` + +- **Using GUI Tools** + + If using GUI tools like WinZip, WinRAR, or the built-in archive utilities: + - Ensure options to include hidden/system files are disabled + - Look for options like "Store Mac OS X resource forks/special files" and disable them + - Some tools have specific options to exclude .DS_Store files and other metadata + +Including these unwanted files may cause validation errors during the import process. + +## Maintaining your APIs + +Once a Tyk OAS API has been created in Tyk the Gateway will manage traffic to the exposed endpoints and proxy requests to the upstream service. + +Your service might evolve over time, with new features and endpoints being added and others retired. Tyk's flexible API versioning and update options provide you with choice for how to reflect this evolution in the APIs you expose to your clients. + +Your OpenAPI description is a living document that describes your upstream service. When this changes (for example, due to the addition of a new endpoint) you can use Tyk's [update API](/api-management/gateway-config-managing-oas#updating-an-api) feature to seamlessly apply the updated OpenAPI description, instantly extending the API proxy to handle traffic as your upstream evolves. + +Alternatively, and especially when you need to make breaking changes as your services and APIs evolve, you can create new [versions](/api-management/api-versioning) of your API and use configurable version identifiers to route traffic to the appropriate target. + +### Updating an API + +As developers working on services it can be necessary to regularly update the API when, for example, we add endpoints or support new methods. + +One of the most powerful features of working with Tyk OAS is that you can make changes to the OpenAPI description outside Tyk and then update your API with the updated details. You can simply update the OpenAPI part of the Tyk OAS API definition without having to make any changes to the [Tyk Vendor Extension](/api-management/gateway-config-tyk-oas#tyk-vendor-extension) (`x-tyk-api-gateway`). + +You can alternatively work on the full Tyk OAS API definition outside Tyk and update your existing API proxy with the new configuration, without having to create a [new version](/api-management/api-versioning) of the API. + + + + + +If you have an updated OpenAPI description or Tyk OAS API definition, in YAML or JSON format, you can use this to modify your existing API in Tyk Dashboard with only a few clicks. + +1. Start by selecting your API from the list on the **APIs** page in the **API Management** section. + +2. Now select **Update OAS** from the **Actions** dropdown. + + Select Update OAS to import the new OpenAPI description + +3. Now you can choose the location of the file that you want to use to update your API, which can be: + + - pasted into the text editor + - uploaded using a file picker + - retrieved from a file server + + Configuring the import location and options + +4. You can re-configure the *listen path* and *upstream (target) URL* in the **Manual configuration options** section, but if you do not provide these then Tyk will leave them unchanged. + +5. Tyk must select the options to automatically configure the request processing middleware chain based upon configuration defined by the OpenAPI Specification for any new endpoints added in the update. If your OpenAPI desription contains the relevant data then select the characteristics you would like to configure. + + Configuring the listen path and upstream URL + + | Middleware | OpenAPI data used for configuration | + |------------|-------------------------------------| + | [Request validation](/api-management/traffic-transformation/request-validation#request-schema-in-openapi-specification) | Endpoints that have `requestBody` or `schema` | + | [Mock response](/api-management/traffic-transformation/mock-response#tyk-oas) | Endpoints with `examples` or `schema` | + | [Client authentication](/api-management/client-authentication#how-does-tyk-implement-authentication-and-authorization) | Defined in `security` and `securitySchemes` | + | [Allow list](/api-management/traffic-transformation/allow-list) | Restrict access only to declared endpoint paths | + +8. Select **Import API** to complete the update. + + + + + +When making calls to the Tyk Dashboard API you'll need to set the domain name and port for your environment and provide credentials in the `Authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :------------------- | :------ | :---------------------- | :----------------------------- | +| Tyk Dashboard API | 3000 | `Authorization` | From Dashboard User Profile | + +You can obtain your authorization credential (Dashboard API key) from the Tyk Dashboard UI: + +- Select **Edit profile** from the dropdown that appears when you click on your username in the top right corner of the screen +- Scroll to the bottom of the page were you will see your **Tyk Dashboard API Access Credentials** + +You will also need to have ‘admin’ or ‘api:write’ permission if [RBAC](/api-management/user-management) is enabled. + +**Applying an Updated OpenAPI Description** + +To update just the OpenAPI description of your API in Tyk, you simply send the OpenAPI document in the payload to the `PATCH /api/apis/oas/{API-ID}` endpoint of your Tyk Gateway API. + +| Property | Description | +| :-------------- | :------------------------------------------ | +| Resource URL | `/api/apis/oas/{API-ID}` | +| Method | `PATCH` | +| Type | None | +| Body | OpenAPI document | +| Parameters | Path: `{API-ID}` | + +You need to specify which API to update - and do so using the `API-ID` value from the response you received from Tyk when creating the API. You can find this in the `x-tyk-api-gateway.info.id` field of the Tyk OAS API Definition stored in your main storage. + +**Applying an Updated Tyk OAS API Definition** + +To update the whole API in Tyk, you simply send the Tyk OAS API definition in the payload to the `PATCH /api/apis/oas/{API-ID}` endpoint of your Tyk Gateway API. + +| Property | Description | +| :-------------- | :------------------------------------------ | +| Resource URL | `/api/apis/oas/{API-ID}` | +| Method | `PATCH` | +| Type | None | +| Body | Tyk OAS API Definition | +| Parameters | Path: `{API-ID}` | + +You need to specify which API to update - and do so using the `API-ID` value from the response you received from Tyk when creating the API. You can find this in the `x-tyk-api-gateway.info.id` field of the Tyk OAS API Definition stored in your main storage. + +**Check request response** + +If the command succeeds, you will see the following response, where `Meta` contains the unique identifier (`id`) for the API you have just updated: + +```.json +{ + "Status": "OK", + "Message": "API modified", + "Meta": {API-ID} +} +``` + + + + + +When making calls to the Tyk Gateway API you'll need to set the domain name and port for your environment and provide credentials in the `x-tyk-authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :----------------- | :------ | :----------------------- | :---------------------------------- | +| Tyk Gateway API | 8080 | `x-tyk-authorization` | `secret` value set in `tyk.conf` | + + +**Applying an Updated OpenAPI Description** + +To update just the OpenAPI description of your API in Tyk, you simply send the OpenAPI document in the payload to the `PATCH /tyk/apis/oas/{API-ID}` endpoint of your Tyk Gateway API. + +| Property | Description | +| :-------------- | :------------------------------------------ | +| Resource URL | `/tyk/apis/oas/{API-ID}` | +| Method | `PATCH` | +| Type | None | +| Body | OpenAPI document | +| Parameters | Path: `{API-ID}` Query: `templateId` | + +You need to specify which API to update - and do so using the `API-ID` value from the response you received from Tyk when creating the API. You can find this in the `x-tyk-api-gateway.info.id` field of the Tyk OAS API Definition that Tyk has stored in the `/apps` folder of your Tyk Gateway installation. + + +**Applying an Updated Tyk OAS API Definition** + +To update the whole API in Tyk, you simply send the Tyk OAS API definition in the payload to the `PATCH /tyk/apis/oas/{API-ID}` endpoint of your Tyk Gateway API. + +| Property | Description | +| :-------------- | :------------------------------------------ | +| Resource URL | `/tyk/apis/oas/{API-ID}` | +| Method | `PATCH` | +| Type | None | +| Body | Tyk OAS API Definition | +| Parameters | Path: `{API-ID}` | + +You need to specify which API to update - and do so using the `API-ID` value from the response you received from Tyk when creating the API. You can find this in the `x-tyk-api-gateway.info.id` field of the Tyk OAS API Definition that Tyk has stored in the `/apps` folder of your Tyk Gateway installation. + + +**Check request response** + +If the command succeeds, you will see the following response, where `key` contains the unique identifier (`id`) for the API you have just updated: + +```.json +{ + "key": {API-ID}, + "status": "ok", + "action": "modified" +} +``` + +**Restart or hot reload** + +Once you have updated your API you need to load it into the Gateway so that it can serve traffic. To do this you can either restart the Tyk Gateway or issue a [hot reload](/tyk-stack/tyk-gateway/important-prerequisites#hot-reload-is-critical-in-tyk-ce) command: + +```.curl +curl -H "x-tyk-authorization: {your-secret}" -s http://{your-tyk-host}:{port}/tyk/reload/group +``` + + + + + + +### Exporting an API asset + +Each API on Tyk has an API definition comprising the OpenAPI description and the Tyk Vendor Extension. We offer the facility for you to export (download) two assets for an API - just the OpenAPI description, or the full Tyk OAS API definition. + +When using Tyk Dashboard these can be exported in either JSON or YAML format; for Tyk Gateway API users the assets can only be exported in JSON format. + + + + + +1. Start by selecting your API from the list on the **APIs** page in the **API Management** section. + +2. Select **Export API** from the **Actions** dropdown. + + Select Export API to download an asset from Tyk + +3. Now you can choose what you want to export, the filename (a default is offered which is based on the API Id) and the file format (JSON or YAML). + + Choosing what to download + +4. Finally select **Export** to save the file to your local machine. + + + + + +When making calls to the Tyk Dashboard API you'll need to set the domain name and port for your environment and provide credentials in the `Authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :------------------- | :------ | :---------------------- | :----------------------------- | +| Tyk Dashboard API | 3000 | `Authorization` | From Dashboard User Profile | + +You can obtain your authorization credential (Dashboard API key) from the Tyk Dashboard UI: + +- Select **Edit profile** from the dropdown that appears when you click on your username in the top right corner of the screen +- Scroll to the bottom of the page were you will see your **Tyk Dashboard API Access Credentials** + +You will also need to have ‘admin’ or ‘api:write’ permission if [RBAC](/api-management/user-management) is enabled. + +To export an API asset, you use the `GET /api/apis/oas/{API-ID}/export` endpoint, indicating whether you require the full Tyk OAS API definition or only the OpenAPI description using the `mode` parameter. + +| Property | Description | +| :-------------- | :-------------------------------------------------- | +| Resource URL | `/api/apis/oas/{API-ID}` | +| Method | `GET` | +| Type | None | +| Parameters | Path: `{API-ID}` Query: `mode` `Content-Type` | + +Where: +- `API-ID` is the unique `id` assigned in the Tyk Vendor Extension that identifies the API +- `mode` to identify the asset to export: `public` for the OpenAPI description (default empty for full API definition) +- `Content-Type` to select the format for the exported asset: `application/x-yaml` or `application/json` + + + + +When making calls to the Tyk Gateway API you'll need to set the domain name and port for your environment and provide credentials in the `x-tyk-authorization` field for Tyk to authorize your request, as follows: + +| Interface | Port | Authorization Header | Authorization credentials | +| :----------------- | :------ | :----------------------- | :---------------------------------- | +| Tyk Gateway API | 8080 | `x-tyk-authorization` | `secret` value set in `tyk.conf` | + +To export an API asset, you use the `GET /tyk/apis/oas/{API-ID}/export` endpoint, indicating whether you require the full Tyk OAS API definition or only the OpenAPI description using the `mode` parameter. + +| Property | Description | +| :-------------- | :-------------------------------------------- | +| Resource URL | `/tyk/apis/oas/{API-ID}` | +| Method | `GET` | +| Type | None | +| Parameters | Path: `{API-ID}` Query: `mode` | + +Where: +- `API-ID` is the unique `id` assigned in the Tyk Vendor Extension that identifies the API +- `mode=public` to export the OpenAPI description (otherwise, export the full API definition) + + + diff --git a/api-management/gateway-config-tyk-classic.mdx b/api-management/gateway-config-tyk-classic.mdx new file mode 100644 index 0000000000..bf23aef18c --- /dev/null +++ b/api-management/gateway-config-tyk-classic.mdx @@ -0,0 +1,793 @@ +--- +title: "Tyk Classic API Definition" +description: "Learn how to configure Tyk Classic API Definitions for your API management needs" +keywords: "Gateway, Configuration, Tyk Classic, Tyk Classic API Definition, Tyk Classic API Definition Object" +sidebarTitle: "Tyk Classic Reference" +--- + +import ApiDefGraphql from '/snippets/api-def-graphql.mdx'; + +## Introduction to Tyk Classic + +Tyk's legacy API definition is now called Tyk Classic and is used for GraphQL, XML/SOAP and TCP services. + +From Tyk 5.8 we recommend that any REST APIs are migrated to the newer [Tyk OAS API](/api-management/gateway-config-tyk-oas) style, in order that they can benefit from simpler configuration and future enhancements. + +For Tyk Dashboard users with an existing portfolio of Tyk Classic API definitions, we provide a [migration tool](/api-management/migrate-from-tyk-classic), available via the Dashboard API and UI. + + + +For versions of Tyk prior to 5.8 not all Gateway features can be configured using the Tyk OAS API definition, for edge cases you might need to use Tyk Classic for REST APIs, though we recommend updating to Tyk 5.8 and adopting Tyk OAS. + + + +The Tyk Classic API definition has a flat structure that does not use the `omitempty` style, requiring all fields to be present even if set to null, resulting in a larger object than that for an equivalent Tyk OAS API definition. + +Note that there are some specific differences between Tyk Classic and Tyk OAS APIs, in particular with respect to [default authentication method](#configuring-authentication-for-tyk-classic-apis) and [API versioning](#tyk-classic-api-versioning). + +## Tyk Classic API versioning + +When multiple versions of a Tyk Classic API are created, the details are stored in a single API definition - unlike with Tyk OAS where a separate API definition is created for each version. The common configuration is stored in the root, whereas the details of the different versions are stored in a dedicated `version_data` object, within the API definition. + +Whilst this allows for easy management of all the API versions, it limits the number of features that can be configured differently between versions, as not all Gateway configuration options are duplicated in `version_data`. + +Tyk enforces strict access control to specific versions of APIs if these are specified in the access token (key). If, once Tyk has identified the API to load, and has allowed the access key through, it will check the access token's session data for access permissions. If it finds none, it will let the token through. However, if there are permissions and versions defined, it will be strict in **only** allowing access to that version. + +Key things to note when configuring versioning for a Tyk Classic API: + +- you must set `version_data.not_versioned` to `false` for Tyk to treat the API as versioned +- `version_data.default_version` must contain the `name` of the version that shall be treated as default (for access control and default fallback) +- you can use `version_data.paths` to configure endpoint-level ignore, allow and block lists (which can be used to configure a mock response) +- you must use `version_data.extended_paths` to configure other endpoint-level middleware +- common versioning configuration is mostly contained within the [definition](/api-management/gateway-config-tyk-classic#common-versioning-configuration) object +- configuration for the different versions is contained within the [version_data](/api-management/gateway-config-tyk-classic#version-specific-configuration) object + - this also contains some common configuration (`not_versioned` and `default_version`) + +When you first create an API, it will not be "versioned" (i.e. `not_versioned` will be set to `true`) and there will be a single version with the name `Default` created in the `version_data` section. + +### Common versioning configuration + + +This object in the root of the Tyk Classic API definition handles information related to how Tyk should handle requests to the versioned API + + + +Used to configure where the versioning identifier should be provided, one of:`header`, `url`, `url-param`. + + + +The name of the key that contains the versioning identifier if `definition.location` is set to `header` or `url-param`. + + + +Set this to `true` to remove the versioning identifier when creating the upstream (target) URL. + + + +Set this to `true` to invoke the default version if an invalid version is specified in the request. + + + +Available from Tyk 5.5.0, if you are have set both `definition.strip_versioning_data` and `definition.fallback_to_default` to `true` and are using `definition.location=url` you can configure this with a regex that matches the format that you use for the versioning identifier (`versions.{version-name}.name`) + + +The following fields are either deprecated or otherwise not used for Tyk Classic API versioning and should be left with their default values: + +- `definition.default`: defaults to an empty string `""` +- `definition.enabled`: defaults to `false` +- `definition.name`: defaults to an empty string `""` +- `definition.strip_path`: deprecated field; defaults to `false` +- `definition.versions`: defaults to an empty array `{}` + +### Version specific configuration + + +This object contains the version status and configuration for your API + + + +Set this to `false` to treat this as a versioned API. If you are not using versioning for this API you must have a single `Default` entry in the `version_data.versions` map. + + + +Used to configure where the versioning identifier should be provided, one of:`header`, `url`, `url-param`. + + + +A list of objects that describe the versions of the API; there must be at least one (`Default`) version defined for any API (even non-versioned APIs). Each version of your API should be defined here with a unique `name`. + + + +An identifier for this version of the API, for example `Default` or `v1`. The value given here is what will Tyk will match against the value in the `definition.key`. + + + +If a value is set then Tyk will automatically deprecate access to the API after the specified timestamp. The entry here takes the form of: `"YYYY-MM-DD HH:MM"`. If this is not set the version will never expire. + + + +This object enables configuration of the basic allow list, block list and ignore authentication middleware for specific endpoints in the API version. You can also configure these and many other per-endpoint middleware using the `extended_paths` field. + + + +You can configure a different target URL here which will be used instead of the value stored in `proxy.target_url`, redirecting requests to a different hostname or domain. Note that this will also override (and so is not compatible with) upstream load balancing and Service Discovery, if configured for this API. + + + +A `key:value` map of HTML headers to inject to the request. + + + +A list of HTML headers to remove from the request. + + + +Apply a maximum size to the request body (payload) - in bytes. + + + +If this boolean flag is set to `false`, Tyk will apply case sensitive matching of requests to endpoints defined in the API definition. + + + +Set this value to `true` if you want Tyk to apply specific middleware to endpoints in this version, configured using `version_data.versions.{version-name}.extended_paths`. + + + +This field contains a list of middleware configurations and to which paths they should be applied. The available middleware are: + +``` +{ + black_list[], + white_list[], + ignore[], + track_endpoints[], + do_not_track_endpoints[], + internal[], + method_transforms[], + transform[], + transform_headers[], + transform_response[], + transform_response_headers[], + size_limits[], + validate_json[], + url_rewrites[], + virtual[], + transform_jq[], + cache[], + hard_timeouts[], + circuit_breakers[] +} +``` + +Each entry must include the method and path (identifying the endpoint) where the middleware runs. You can find full documentation for each middleware in the [Traffic Transformation](/api-management/traffic-transformation) section including configuration instructions for the Tyk Classic API definition, for example the [allow list](/api-management/traffic-transformation/allow-list#api-definition-1). When using Tyk Classic, the mock response functionality is configured via the `black_list[]`, `white_list[]` or `ignore[]` middleware. + + + +## Configuring authentication for Tyk Classic APIs + +Tyk Classic APIs *default to the auth token method* for authenticating requests. Flags in the API definition can be configured to enforce an alternative method: + +- keyless (no authentication of the client) +- basic authentication +- HMAC request signing +- Tyk as the OAuth 2.0 authorization server +- JWT authentication + + +This will switch off all key checking and open the API definition up, some analytics will still be recorded, but rate-limiting, quotas and security policies will not be possible (there is no session to attach requests to). This is a good setting for checking if Tyk works and is proxying traffic correctly. + + + +This object contains the basic configuration for the Auth (Bearer) Token method. + + + +The header name (key) where Tyk should look for the token. + + + +Set this to true to instruct Tyk to expect the token in the URL parameter with key `auth.param_name`. + + + +The name of the URL parameter key containing the auth token. Note that this is case sensitive. + + + +Set this to true to instruct Tyk to expect the token in the URL parameter with key `auth.cookie_name`. + + + +The name of the cookie containing the auth token. Note that this is case sensitive. + + + +Implement Certificate Authentication (Dynamic mTLS prior to Tyk 5.12.0) + + + +Boolean value set to `true` to enable Auth Token Signature Validation + + + +Configuration for Auth Token Signature Validation + + + +The algorithm you wish to validate the signature against. Options are: +- `MasherySHA256` +- `MasheryMD5` + + + + +Header key for attempted signature + + + +The shared secret which was used to sign the request +- this can hold a dynamic value, by referencing `$tyk_meta` or `$tyk_context` variables. +- for example: if you have stored the shared secret in the field `individual_secret` of the session token's meta-data you would use the value `"secret": "$tyk_meta.individual_secret"`. + + + +Maximum permitted deviation in seconds between UNIX timestamp of Tyk & UNIX timestamp used to generate the signed request + + + +This method will enable basic auth as specified by the HTTP spec, an API with this flag set will request for a username and password and require a standard base64 Authentication header to be let through. + + + +This disables the caching of basic authentication keys. + + + +This is the refresh period for the basic authentication key cache (in seconds). + + + +If this option is set to `true`, Tyk will implement the HMAC signing standard as proposed in the [HTTP Signatures Spec](https://web-payments.org/specs/ED/http-signatures/2014-02-01/#page-3). In particular the structure of the Authorization header and the encoding method need to be taken into account. +- this method will use a session key to identify a user and a user secret that should be used by the client to sign each request's `date` header +- it will also introduce clock skew checks, requests outside of 300ms of the system time will be rejected +- it is not recommended for Single-Page-Webapps (SPA) or Mobile apps due to the fact that secrets need to be distributed + + + +Tyk supports the following HMAC algorithms: “hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512”. You can limit which ones you want to support with this option. For example, [“hmac-sha256”] + + + +Set this value to anything larger than `0` to set the number of milliseconds that will be tolerated for clock skew. Set to `0` to prevent clock skew checks on requests (only in HMAC mode, i.e. when `enable_signature_checking` is set to `true`). + + + +This authentication method will use Tyk as the OAuth 2.0 Authorization Server. Enabling this option will cause Tyk to add OAuth2-standard endpoints to the API for `/authorize` and `/token`, these will supersede any other requests to your proxied system in order to enable the flow. + + + +This is a string array of OAuth access options depending on the OAuth grant types to be supported. Valid options are: +- `authorization_code` - client has an authorization code to request a new access token. +- `refresh_token` - client can use a refresh token to refresh expired bearer access token. + + + +This is a string array of OAuth authorization types. Valid options are: +- `code` - Client can request an authorization code which can be used to request an access code via a server request (traditionally reserved for server-side apps). +- `token` - Client can request an access token directly, this will not enable refresh tokens and all tokens have a 12 hour validity. Recommended for mobile apps and single-page webapps. + + + +The Tyk OAuth flow has a dummy (intercept) `/authorize` endpoint which basically redirects the user to your login and authentication page, it will also send along all OAuth data as part of the request (so as to mimic a regular app flow). This is the URL that the user will be sent to (via `POST`). + + + +When Tyk is used as the OAuth 2.0 Authorization Server, because it will handle access requests on your behalf once authorization codes have been issued, it will need to notify your system that these have occurred. It will `POST` key data to the URL set in these options to ensure that your system is synchronised with Tyk. + + + +Posted data to your service will use this shared secret as an authorization header. This is to ensure that messages being received are from Tyk and not from another system. + + + +The URL that will be sent the updated information - the URL will be polled up to 3 times if there is a communications failure. On a `200 OK` response it stops. + + + +This section allows definition of multiple chained authentication mechanisms that will be applied to requests to the API, with distinct authentication headers identified for the different auth modes. + +For example: + +```json +{ + "auth_configs": { + "authToken": { "auth_header_name": "My-Auth-Header-Key" }, + "basic": { "auth_header_name": "My-Basic-Auth-Header-Key" } + } +} +``` + + + +This enables multiple authentication and indicates which authentication method provides the session object that determines access control, rate limits and usage quotas. + +It should be set to one of the following: + +- `auth_token` +- `hmac_key` +- `basic_auth_user` +- `jwt_claim` +- `oidc_user` +- `oauth_key` +- `custom_auth` + + + +Set JWT as the authentication method for this API. + + + +Either HMAC or RSA - HMAC requires a shared secret while RSA requires a public key to use to verify against. Please see the section on JSON web tokens for more details on how to generate these. + + + +Must be a base64 encoded valid RSA, ECDSA or HMAC key or the full address of a JSON Web Key Set (JWKS) endpoint. This key (or the JWKS retrieved from the endpoint) will be used to validate inbound JWT and throttle them according to the centralised JWT options and fields set in the configuration. See [JWT signature validation](/api-management/authentication/jwt-signature-validation#configuring-idps-in-the-api-definition) for more details on using a JWKS endpoint. + + + +Identifies the user or identity to be used in the Claims of the JWT. This will fallback to `sub` if not found. This field forms the basis of a new "virtual" token that gets used after validation. It means policy attributes are carried forward through Tyk for attribution purposes. + +Centralised JWTs add a `TykJWTSessionID` to the session metadata on create to enable upstream hosts to work with the internalised token should things need changing. + + + +The policy ID to apply to the virtual token generated for a JWT. + + + +Prevent token rejection due to clock skew between servers for Issued At claim (seconds, default: 0) + + + +Prevent token rejection due to clock skew between servers for Expires At claim (seconds, default: 0) + + + +Prevent token rejection due to clock skew between servers for Not Before claim (seconds, default: 0) + + +## GraphQL specific fields + + + +## General features + +### API identification + + +The identifier for the API This should be unique, but can actually be any kind of string. For single-instance setups this can probably be set to `1`. It is recommended to make this a UUID. The `api_id` is used to identify the API in queries to the Tyk Gateway API or Tyk Dashboard API. + + + +Human readable name of the API. It is used for identification purposes but does not act as an index. + + + +This is an identifier that can be set to indicate ownership of an API key or of an individual API. If the Org ID is set (recommended), it is prepended to any keys generated by Tyk - this enables lookups by prefixes from Redis of keys that are in the system. + + + +The domain to bind this API to. Multiple APIs can share the same domain, so long as their listen paths are unique. +This domain will affect your API only. To set up the portal domain for your organization, please register it in the main Tyk Dashboard settings file. +Your Tyk Gateway can listen on multiple domains/subdomains through the use of regular expressions, more precisely the RE2 Syntax. They are defined using the format `{name}` or `{name:pattern}`. + * `www.example.com` Matches only if domain is www.example.com + * `{subdomain:[a-z]+}.example.com` Matches dynamic subdomain + * `{subdomain:foo|bar}.example.com` will listen on foo.example.com and bar.example.com" + + + +If set to `true` when matching the URL path for requests to this API, the case of the endpoint path will be ignored. So for an API `my-api` and the endpoint `getuser`, requests to all of the following will be matched: + + * `/my-api/getuser` + * `/my-api/getUser` + * `/my-api/GetUser` + +If set to true, this will override the endpoint level settings in [Ignore](/api-management/traffic-transformation/ignore-authentication#case-sensitivity), [Allowlist](/api-management/traffic-transformation/allow-list#case-sensitivity) and [Blocklist](/api-management/traffic-transformation/block-list#case-sensitivity) middleware. This setting can be overriden at the Tyk Gateway level, and so applied to all APIs, by setting `ignore_endpoint_case` to `true` in your `tyk.conf` file. See [ignore_endpoint_case](/tyk-oss-gateway/configuration#ignore_endpoint_case) for details. + + + +Set to true to enable batch support + + + +This is allocated by Tyk to locate the API definition in the Dashboard main storage and bears no actual relation to the identity of the API. + + + +This field is used by Tyk Dashboard to control whether the API will serve traffic. If set to `false` then on Gateway start, restart or reload, the API will be ignored and all paths and routes for that API will cease to be proxied. Any keys assigned to it will still exist, though they will not be let through for that particular API. + + + +This field controls the exposure of the API on the Gateway. When set to `true`, the API will not be made available for external access and will not be included in API listings returned by the Gateway's management APIs; it will be accessible only via [internal routing](/advanced-configuration/transform-traffic/looping). + + +### Access token management + + +The session (API access key/token) lifetime will override the expiry date if it has been set on a key (in seconds). for example, if a key has been created that never expires, then it will remain in the session cache forever unless manually deleted. If a re-auth needs to be forced or a default expiry needs to be applied to all keys, then use this feature to set the session expiry for an an entire API. + + + +If this is set to `true` and the key expiration date is less than the `session_lifetime`, the key expiration value will be set to `session_lifetime`. Don't forget that the key expiration is set in unix timestamp but `session_lifetime` is set in seconds. Also, `session_lifetime_respects_key_expiration` exists in the global config too. When the global one is set to `true`, the one set at the API level will be ignored. + + + +If set to true, when the keys are created, edited or added for this API, the quota cache in Redis will not be reset. + + +### Traffic logs + + +If this value is set to `true`, the Gateway will record the request and response payloads in traffic logs. + + + +If this value is set to `true`, the Gateway will not generate traffic logs for requests to the API. + + + +This specifies a string array of HTTP headers values which turned into tags. For example, if you include the `X-Request-ID` header to `tag_headers`, for each incoming request it will include an `x-request-id-` tag to request an analytic record. This functionality can be useful if you need analytics for request headers without the body content (Enabling detailed logging is another option, but it records the full request and response objects and consumes a lot more space). + + + +This value (in seconds) will be used to indicate a TTL (ExpireAt) for the retention of analytics created from traffic logs generated for this API that are stored in MongoDB. If using an alternative analytics storage solution that does not respect ExpireAt then you must manage the record TTL separately. + + +### OpenTelemetry + + +If this value is set to `true`, the Gateway will generate detailed OpenTelemetry spans for requests to the API. + + +### API Level Rate Limits + + +The [API-level rate limit](/api-management/rate-limit#rate-limiting-layers) aggregates the traffic coming into an API from all sources and ensures that the overall rate limit is not exceeded. It is composed of a `rate` (number of requests) and `per` (interval). If either is set to `0` then no API-level limit is applied. + + + +If set to `true`, all rate limits are disabled for the specified API (both API-level and key-level) + + +### Event handlers + + +This adds the ability to configure an API with event handlers to perform specific actions when an event occurs. + + + +Each event handler that is added to the event_handlers.events section, is mapped by the event type, and then a list of each handler configuration, defined by the handler name and the handler metadata (usually some kind of configurable options for the specific handler) + + +### Custom data + + +Context variables are extracted from the request at the start of the middleware chain, and must be explicitly enabled in order for them to be made available to your transforms. These values can be very useful for later transformation of request data, for example, in converting a Form-based POST into a JSON-based PUT or to capture an IP address as a header. + + + +You can use this field to pass custom attributes to the virtual endpoint middleware. It is a list of key:value pairs. + + +### IP Access Control + + +This works with the associated `allowed_ips` list and, when set to `true`, accepts only requests coming from the defined list of allowed IP addresses. + + + +A list of strings that defines the IP addresses (in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation)) that are allowed access via Tyk. This list is explicit and wildcards are not supported. + + + +This works with the associated `blacklisted_ips` list and, when set to `true`, rejects and requests coming from the defined list of blocked IP addresses. + + + +A list of strings that defines the IP addresses (in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation)) that are blocked access via Tyk. This list is explicit and wildcards are not supported. + + +### Cross-Origin Resource Sharing (CORS) + + +Enable CORS for the API + + + +A list of origin domains to allow access from. Wildcards are also supported, e.g. http://*.foo.com + + + +A list of HTTP methods to allow access via. + + + +Headers that are allowed within a request. + + + +Headers that are exposed back in the response. + + + +Whether credentials (cookies) should be allowed. + + + +Maximum age of credentials. + + + +Allow CORS OPTIONS preflight request to be proxied directly to upstream, without authentication and the rest of the checks. This means that pre-flight requests generated by web-clients such as SwaggerUI will be able to test the API using trial keys. If your service handles CORS natively, then enable this option. + + +### Proxy Transport Settings + + +Set to `true` to preserve the host header. If `proxy.preserve_host_header` is set to `true` in an API definition then the host header in the outbound request is retained to be the inbound hostname of the proxy. + + + +The path to listen on, e.g. `/api` or `/`. Any requests coming into the host, on the port that Tyk is configured to run on, that go to this path will have the rules defined in the API Definition applied. Versioning assumes that different versions of an API will live on the same URL structure. If you are using URL-based versioning (e.g. `/v1/function`, `/v2/function/`) then it is recommended to set up a separate non-versioned definition for each version as they are essentially separate APIs. + +Proxied requests are literal, no re-writing takes place, for example, if a request is sent to the listen path of: `/listen-path/widgets/new` and the URL to proxy to is `http://your.api.com/api/` then the *actual* request that will land at your service will be: `http://your.api.com/api/listen-path/widgets/new`. + +This behavior can be circumvented so that the `listen_path` is stripped from the outgoing request. See the section on `strip_listen_path` below. + + + +By setting this to `true`, Tyk will attempt to replace the `listen-path` in the outgoing request with an empty string. This means that in the above scenario where `/listen-path/widgets/new` and the URL to proxy to is `http://your.api.com/api/` becomes `http://your.api.com/api/listen-path/widgets/new`, actually changes the outgoing request to be: `http://your.api.com/api/widgets/new`. + + + +This defines the target URL that the request should be proxied to if it passes all checks in Tyk. + + + +This boolean option allows you to add a way to disable the stripping of the slash suffix from a URL. + + + +Set this value to `true` to have a Tyk node distribute traffic across a list of servers. **Required: ** You must fill in the `target_list` section. + + + +A list of upstream targets for load balancing (can be one or many hosts). + + + +If uptime tests are enabled, Tyk will check the hostname of the outbound request against the downtime list generated by the host checker. If the host is found, then it is skipped. + + + +The service discovery section tells Tyk where to find information about the host to proxy to. In a clustered environment this is useful if servers are coming online and offline dynamically with new IP addresses. The service discovery module can pull out the required host data from any service discovery tool that exposes a RESTful endpoint that outputs a JSON object. + +```json +{ + "enable_load_balancing": true, + "service_discovery": { + "use_discovery_service": true, + "query_endpoint": "http://127.0.0.1:4001/v2/keys/services/multiobj", + "use_nested_query": true, + "parent_data_path": "node.value", + "data_path": "array.hostname", + "port_data_path": "array.port", + "use_target_list": true, + "cache_timeout": 10 + }, +} +``` + + + +Set this to `true` to enable the discovery module. + + + +The endpoint to call. + + + +The namespace of the data path. For example, if your service responds with: + +```json +{ + "action": "get", + "node": { + "key": "/services/single", + "value": "http://httpbin.org:6000", + "modifiedIndex": 6, + "createdIndex": 6 + } +} +``` + +Then your name space would be `node.value`. + + + +Sometimes the data you are retrieving is nested in another JSON object. For example, this is how Etcd responds with a JSON object as a value key: + +```json +{ + "action": "get", + "node": { + "key": "/services/single", + "value": "{\"hostname\": \"http://httpbin.org\", \"port\": \"80\"}", + "modifiedIndex": 6, + "createdIndex": 6 + } +} +``` + +In this case, the data actually lives within this string-encoded JSON object. So in this case, you set the `use_nested_query` to `true`, and use a combination of the `data_path` and `parent_data_path` (below) + + + +This is the namespace of where to find the nested value. In the above example, it would be `node.value`. You would then change the `data_path` setting to be `hostname`. Tyk will decode the JSON string and then apply the `data_path` namespace to that object in order to find the value. + + + +In the above nested example, we can see that there is a separate PORT value for the service in the nested JSON. In this case you can set the `port_data_path` value and Tyk will treat `data_path` as the hostname and zip them together (this assumes that the hostname element does not end in a slash or resource identifier such as `/widgets/`). In the example, the `port_data_path` would be `port`. + + + +The target path to append to the host:port combination provided by the service discovery engine. + + + +If you are using load_balancing, set this value to `true` and Tyk will treat the data path as a list and inject it into the target list of your API definition. + + + +Tyk caches target data from a discovery service. In order to make this dynamic you can set a cache value when the data expires and new data is loaded. + + + +The transport section allows you to specify a custom proxy and set the minimum TLS versions and any SSL ciphers. + +This is an example of `proxy.transport` definition followed by explanations for every field. +```json +{ + "transport": { + "proxy_url": "http(s)://proxy.url:1234", + "ssl_min_version": 771, + "ssl_ciphers": [ + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" + ], + "ssl_insecure_skip_verify": true, + "ssl_force_common_name_check": false + } +} +``` + + + +Use this setting to specify your custom forward proxy and port. + + + +Use this setting to specify your minimum TLS version; note that this is limited by the version of Tyk due to underlying Golang support for legacy TLS versions. + + + +You can add `ssl_ciphers` which takes an array of strings as its value. Each string must be one of the allowed cipher suites as defined at https://golang.org/pkg/crypto/tls/#pkg-constants. This is not applicable from TLS 1.3. + + + +Boolean flag to control at the API definition whether it is possible to use self-signed certs for some APIs, and actual certs for others. This also works for `TykMakeHttpRequest` & `TykMakeBatchRequest` in virtual endpoints. + + + +Use this setting to force the validation of a hostname against the certificate Common Name. + + +### Upstream Authentication + + +When set to `true`, auth related headers will be stripped from requests proxied through the gateway. + + + +Configuration for Upstream Request Signing using HMAC or RSA algorithms. + + + +The secret used for signing (not shared with the upstream). + + + +An identifier allocated by the upstream used to identify Tyk as the requesting client. + + + +The signing algorithm to be used - one from `hmac-sha1`, `hmac-sha256`, `hmac-sha384`, `hmac-sha512`, `hmac-rsa256` + + + +A list of headers to be included in the signature calculation. + + + +The certificate ID used in the RSA signing operation. + + + +The HTTP header to be used to pass the signature to the upstream. + + +### Uptime Tests + + +This section defines the uptime tests to run for this API. + + + +A list of tests to run, which can be either short form: + +```json +{ + "uptime_tests": { + "check_list": [ + { + "url": "http://google.com/" + } + ] + } +} +``` + +or long form: + +```json +{ + "uptime_tests": { + "check_list": [ + { + "url": "http://posttestserver.com/post.php?dir=uptime-checker", + "method": "POST", + "headers": { + "this": "that", + "more": "beans" + }, + "body": "VEhJUyBJUyBBIEJPRFkgT0JKRUNUIFRFWFQNCg0KTW9yZSBzdHVmZiBoZXJl", + "timeout": 1000 + } + ] + } +} +``` + + + +The URL to be used for the uptime test. + + + +The HTML method to be used for the request to the `check_list.url` (required for long form tests). + + + +A list of headers to be applied to the request to the `check_list.url` as key:value pairs (only for long form tests). + + + +The body of the request to be sent to the `check_list.url`, this is Base64 encoded (only for long form tests). + + + +The timeout in milliseconds for the uptime check (only for long form tests). + + diff --git a/api-management/gateway-config-tyk-oas.mdx b/api-management/gateway-config-tyk-oas.mdx new file mode 100644 index 0000000000..6ced00cb3a --- /dev/null +++ b/api-management/gateway-config-tyk-oas.mdx @@ -0,0 +1,82 @@ +--- +title: "Tyk OAS" +description: "Learn how to configure Tyk OAS API Definitions to manage your APIs using the OpenAPI Specification" +keywords: "Gateway, Configuration, Tyk OAS, Tyk OAS API Definition, Tyk OAS API Definition Object" +sidebarTitle: "Tyk OAS Reference" +--- + +import XTykGateway from '/snippets/x-tyk-gateway.mdx'; + +## Introduction to Tyk OAS + +The upstream service receives requests from Tyk to the *upstream API* after processing based on the configuration applied in the Tyk API definition. Crucially the upstream service remains unaware of Tyk Gateway's processing, responding to incoming requests as it would for direct client-to-service communication. The *API proxy* deployed on Tyk is typically designed to have the same API endpoints, resources and methods that are defined for the upstream service's API. The *upstream API* will often be described according to the industry standard OpenAPI Specification - and this is where Tyk OAS comes in. + +### What is the OpenAPI Specification? + +The *OpenAPI Specification* (OAS) is a standardized framework for describing RESTful APIs in a machine-readable format (typically JSON or YAML). It defines how APIs should be documented, including details about endpoints, request/response formats, authentication, and error codes. In short, OAS is a blueprint for your API—detailing how the API behaves and how users or services can interact with it. The *OpenAPI Description* (OAD) is the actual content that adheres to this specification, essentially an object that describes the specific functionality of an API. The *OpenAPI Document* refers to a file that contains an OpenAPI description, following the OAS format. + +OpenAPI has become the de facto standard for API documentation because of its consistency, ease of use, and broad tooling support. It allows both developers and machines to interact with APIs more effectively, offering benefits like auto-generated client SDKs, server stubs, and up-to-date documentation. Tools such as Tyk also support validation, testing, and mock servers, which speeds up development and ensures consistency across API implementations. + +Tyk fully supports [OpenAPI Specification v3.0.x](https://spec.openapis.org/oas/v3.0.3). + +#### OpenAPI Specification 3.1 + +From **Tyk 5.12.0**, there is basic support for [OAS v3.1.x](https://spec.openapis.org/oas/v3.1.2.html) covering: + +- Import and validation of OpenAPI 3.1 descriptions using Tyk Dashboard to create Tyk OAS APIs +- OAS 3.1 [features](https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0) + - Full JSON Schema Support and `$schema` keyword + - The single `example` keyword is deprecated in OAS 3.1 + - `type` can be an array + - exclusiveMinimum and exclusiveMaximum keywords + +We do not yet support: + +- Conversion from OAS 3.0 to OAS 3.1 +- Reusable Path Item Objects +- New mutualTLS security scheme +- Tyk Dashboard's API Editor does not yet validate the schema, so validation is performed only when saving the API + +### What is a Tyk OAS API definition? + +Not every feature of an advanced API management platform such as Tyk is covered by the OpenAPI Specification. The *API definition* must provide Tyk with everything it needs to receive and process requests on behalf of the upstream service - so the OpenAPI description of the upstream API is not enough on its own to configure the Gateway. This is where the *Tyk Vendor Extension* comes in, allowing you to configure all the powerful features of Tyk Gateway that are not covered by OAS. + +The [Tyk Vendor Extension](#tyk-vendor-extension) follows the same architectural style as the OpenAPI Specification and is encapsulated in a single object that is appended to the OpenAPI description, creating a *Tyk OAS API definition*. + +#### OpenAPI description + +There are many great explanations of the features and capabilities of the OpenAPI Specification so we won't repeat it all here. A good place to start learning is from the maintainers of the specification: the [OpenAPI Initiative](https://learn.openapis.org/). + +Tyk treats the OpenAPI description as the source of truth for the data stored within it. This means that Tyk does not duplicate those data in the Tyk Vendor Extension but rather builds upon the basic configuration defined in the OAD. + +#### Tyk Vendor Extension + +The Tyk Vendor Extension is a JSON object (`x-tyk-api-gateway`) within the Tyk OAS API definition that encapsulates all of the Gateway configuration that is not contained within the OpenAPI description. + +It is structured in four sections: + +- `info` containing metadata used by Tyk to manage the API proxy, including name, identifiers, status, and version +- `server` contains configuration for the client-gateway integration, including listen path and authentication method. Supported scheme types under `server.authentication.securitySchemes` include `oauth2` — for external IdP token validation, scope enforcement, and token exchange. See [OAuth 2.0 (External IdP)](/api-management/authentication/oauth2-authentication). +- `middleware` contains configuration for the gateway's middleware chain, split into API-level and endpoint-level settings +- `upstream` contains configuration for the gateway-upstream integration, including targets, load balancing and rate limits + +The extension has been designed, as has OAS, to have minimal content so if a feature is not required for your API (for example, payload transformation) then this can be omitted from the API definition. Most features have an `enabled` flag which must be set for Tyk to apply that configuration. This can be used to include settings in the API definition and enable them only when required (useful during API development, testing and debug). + +In the OpenAPI Specification *paths* define the API endpoints, while *operations* specify the HTTP methods (GET, POST, PUT, DELETE) and actions for each endpoint. They describe how the API handles requests, including parameters, request bodies, responses, and status codes, providing a clear structure for API interactions. Tyk interprets this information directly from the OpenAPI description and uses the `operationID` field to link the endpoint level middleware configuration within the Tyk Vendor Extension to the appropriate endpoint. + +### Modifying the OpenAPI description + +Tyk will only make additions or modifications to the OAD when the user makes certain changes in the Tyk API Designer and as follows: + +- The URL(s) on Tyk Gateway(s) to which client requests should be sent will be added to the beginning of the `servers` list +- The OpenAPI Specification declares `paths` which describe the available endpoints (paths) and the operations that can be performed on them (such as `GET`, `POST`, `PUT`, `DELETE`). Tyk will modify this list if changes are made using the Tyk API Designer, for example if an endpoint is added. + +Where Tyk might modify the OpenAPI description, this is noted in the appropriate section of the documentation. + +If changes are made via the Tyk API Designer that impact the OpenAPI description, we recommend that you export the OAD from Tyk to store in your source of truth repository. This ensures that your records outside Tyk accurately reflect the API that is consumed by your clients (for example, if you publish documentation from the OpenAPI Specification of your API). + +Equally, if you make changes to your OpenAPI description outside Tyk, we provide a simple method to update (or patch) your Tyk API definition with the updated OAD. Alternatively you might prefer to create a new version of your API for the updated OpenAPI description, depending on the current stage of the API in its lifecycle. + + + + diff --git a/api-management/gateway-events.mdx b/api-management/gateway-events.mdx new file mode 100644 index 0000000000..45de700d1e --- /dev/null +++ b/api-management/gateway-events.mdx @@ -0,0 +1,925 @@ +--- +title: "Gateway Events" +description: "Introduction to Gateway Events" +keywords: "Gateway, Events, Async APIs, Asynchronus APIs, Event Types, Event Webhooks, Event Metadata" +sidebarTitle: "Gateway Events" +--- + +Tyk Gateway will generate asynchronous events when certain conditions are met, for example a rate limit being exceeded, an expired key attempting to access an API, or a circuit breaker triggering due to a slow or unresponsive upstream. + +Tyk has a flexible model for handling these API events. + +## Event categories + +There are four different categories of events that can be fired by Tyk: +- [API events](#api-events) +- [Token lifecycle events](#token-lifecycle-events) +- [Advanced quota usage events](#advanced-quota-usage-events) +- [Custom events](#custom-events) + +### API events + +Tyk can generate (or *fire*) a variety of built-in API events due to activity triggered by an API request, such as exceeded rate limits, depleted quotas or attempts to access using expired keys. The full list of standard API events is available [here](/api-management/gateway-events#api-events). + +### Token lifecycle events + +Alongside the events that are fired in response to API requests, Tyk will also mark the creation, update or deletion of access tokens (keys) with dedicated events as indicated [here](/api-management/gateway-events#token-lifecycle-events). + +### Advanced quota usage events + +Tyk will generate [standard quota events](/api-management/gateway-events#standard-quota-events) when a client quota has been consumed, but what if you want to have more granular notification of quota usage as your clients are approaching their quota limit? + +For this, Tyk provides [advanced quota monitoring](/api-management/gateway-events#monitoring-quota-consumption) that can be configured to trigger a dedicated event handler when the API usage exceeds different thresholds approaching the quota limit. + +### Custom events + +The event subsystem has been designed to be easily extensible, so the community can define additional events within the Tyk codebase which can then be handled using the exsiting event handling system. + +## Handling events with Tyk + +Tyk has a simple event handling system where *event handlers* are assigned (or registered) to the different [events](/api-management/gateway-events#event-types) that Tyk can generate. These handlers are assigned per-API so when an event is generated for an API and there is an *event handler* registered for that *event*, the handler will be triggered. + +Three different categories of *event handler* can be registered for each event: +- a [webhook](/api-management/gateway-events#event-handling-with-webhooks) that will call out to an external endpoint +- an [event log](/api-management/gateway-events#logging-api-events-1) that will write to the configured [log output](/api-management/logs/application-logs) +- your own [custom event handler](/api-management/gateway-events#custom-api-event-handlers) that will run in a JavaScript virtual machine on the Tyk server + + + + + Remember that quota usage monitoring has a [dedicated mechanism](/api-management/gateway-events#monitoring-quota-consumption) for handling these special events. + + + +## Event Types + +The built-in events that Tyk Gateway will generate are: + +### Rate limit events + +- `RatelimitExceeded`: the rate limit has been exceeded for a specific key +- `OrgRateLimitExceeded`: the rate limit has been exceeded for a specific organization +- `RateLimitSmoothingUp`: the [intermediate rate limit allowance](/api-management/rate-limit#rate-limit-smoothing) has been increased for a specific key +- `RateLimitSmoothingDown`: the [intermediate rate limit allowance](/api-management/rate-limit#rate-limit-smoothing) has been decreased for a specific key + +### Standard quota events + +- `QuotaExceeded`: the quota for a specific key has been exceeded +- `OrgQuotaExceeded`: the quota for a specific organization has been exceeded + +### Authentication failure events + +- `AuthFailure`: a key has failed authentication or has attempted access and was denied +- `KeyExpired`: an attempt has been made to access an API using an expired key +- `UpstreamOAuthError`: an error occurred when trying to authenticate with an upstream using an OAuth provider + +### API version events + +- `VersionFailure`: a key has attempted access to a version of an API that it does not have permission to access + +### Circuit breaker events + +- `BreakerTripped`: a circuit breaker on a path has tripped and been taken offline +- `BreakerReset`: a circuit breaker has reset and the path is available again +- `BreakerTriggered`: a circuit breaker has changed state, this is generated when either a `BreakerTripped`, or a `BreakerReset` event occurs; a status code in the metadata passed to the webhook will indicate which of these events was triggered + +### Uptime events + +- `HostDown`: the uptime checker has found that a host is down/not available +- `HostUp`: the uptime checker has found that a host is available again after being offline + +### Token lifecycle events + +- `TokenCreated`: a token has been created +- `TokenUpdated`: a token has been changed/updated +- `TokenDeleted`: a token has been deleted + +### Certificate expiry events + +- `CertificateExpiringSoon`: a certificate has been used within the expiry threshold and should be updated +- `CertificateExpired`: an expired certificate has been used in a request + +## Event Metadata + +When an event is fired, and an *event handler* is registered for that specific API and event combination, Tyk Gateway provides the handler with a rich set of [metadata](/api-management/gateway-events#event-metadata). The external system (webhook) or custom (JavaScript) code can then use this metadata to decide what action to take. + +Most events provide common metadata as follows: + +- `message` (string): a human-readable message from Tyk Gateway that provides details about the event +- `path` (string): the path of the API endpoint request that led to the event being fired +- `origin` (string): origin data for the source of the request (if this exists) +- `key` (string): the key that was used in the request +- `originating_request` (string): a Base64-encoded [raw inbound request](#raw-request-data) + +### Specific Event Metadata + +Some events provide alternative metadata specific to that event. The following sections detail the event-specific metadata provided for such events. + +
    + +
  • + +- `message` (string): a human readable message from Tyk Gateway that adds detail about the event +- `cert_id` (string): the certificate ID +- `cert_name` (string): the name of the certificate +- `expires_at` (string, RFC3339): the certificate expiry date +- `days_remaining` (integer): the remaining days until the certificate expires +- `api_id`(string): the ID of the API that triggered the event + +
  • + +
  • + +- `message` (string): a human readable message from Tyk Gateway that adds detail about the event +- `cert_id` (string): the certificate ID +- `cert_name` (string): the name of the certificate +- `expired_at` (string, RFC3339): the date when the certificate expired +- `days_since_expiry` (integer): the number of days since the certificate expired +- `api_id`(string): the ID of the API that triggered the event + +
  • + +
+ +### Using the metadata + +The metadata are exposed so that they can be used by the event handler (webhook or custom) using Go templating. For details of how each type of event handler can access these data, please see the appropriate section for [webhook](/api-management/gateway-events#webhook-payload) or [custom](/api-management/gateway-events#the-event-object) event handlers. + +### Raw Request Data + +The `OriginatingRequest` metadata is a Base64-encoded wire-protocol representation of the original request to the event handler. If you are running a service bus or queue that stores failed, throttled or other types of requests, you can decode this object and parse it in order to re-create the original intent of the request (e.g. for post-processing). + +### Logging API Events + +Tyk’s built-in logging event handler is designed primarily for debugging purposes and will store details of an API event to the configured logger output. + +The Tyk platform can be configured to log at various verbosity levels (info, debug, warn, error) and can be integrated with third-party log aggregation tools like Sentry, Logstash, Graylog, and Syslog. For full details on configuring the Tyk logger, see [this section](/api-management/logs/application-logs). + +
+ + +Logging event handlers are currently only supported by Tyk Classic APIs. + + + +### Configuring the event handler + +Registering a logging event handler to your Tyk Classic API is the same as adding any other event handler, within the `event_handlers` section of the API definition. + +The `handler_name` for the logging event handler should be set to: `eh_log_handler`. + +The `handler_meta` for the logging event handler contains a single field: +- `prefix` is a label that will be prepended to each log entry + +For example, to register event handlers to log the `AuthFailure` and `KeyExpired` events you might add the following to your API definition: + +```json +{ + "event_handlers": { + "events": { + "AuthFailure": [ + { + "handler_name": "eh_log_handler", + "handler_meta": { + "prefix": "AuthFailureEvent" + } + } + ], + "KeyExpired": [ + { + "handler_name": "eh_log_handler", + "handler_meta": { + "prefix": "KeyExpiredEvent" + } + } + ] + } + } +} +``` + +In this example +- the `AuthFailure` event will trigger the event handler to generate a log with the prefix `AuthFailureEvent` +- the `KeyExpired` event will trigger the event handler to generate a log with the prefix `KeyExpiredEvent` + +When the event handler is triggered an entry will be made in the log containing the corresponding prefix, which can be useful for monitoring and debugging purposes. + +## Event handling with webhooks + +### Overview + +A webhook is a mechanism for real-time, event-driven communication between different systems or applications over the internet. It is an HTTP callback, typically an HTTP POST request that occurs when something happens. Webhooks are real-time, automated and lightweight. Notifications are sent immediately when events occur without the need for the receiving service to poll. + +In the context of Tyk Gateway, webhooks are event handlers that can be registered against API Events. The webhook will be triggered when the corresponding event is fired and will send a customizable fixed payload to any open endpoint. + +#### When to use webhook event handlers + +There are many occasions when you might use webhooks for event handling, here are just a few examples. + +##### Rate limit violations + +When an API consumer exceeds their allocated rate limit, the `RatelimitExceeded` event will be fired. A webhook event handler can be employed to notify an upstream system to take actions such as updating a dashboard, notifying the account manager, or adjusting the client's service tier. + +##### API key lifecycle events + +When an expired API key is used to access an API, the client will receive an error and the `KeyExpired` event will be fired. A webhook event handler can be employed to notify an upstream system to take actions such as renewing the key, logging the failure in a CRM or notifying the account manager to initiate customer communication. + +##### Upstream service problems + +When an API circuit breaker triggers due to an unresponsive upstream service, the `BreakerTripped` event will be fired. A webhook event handler can be employed to update monitoring dashboards or to trigger automated recovery scripts or processes. + +#### How webhook event handlers work + +With Tyk Gateway, the webhook event handler is a process that runs asynchronously in response to an API event being fired. It will issue an HTTP request to any open endpoint and is fully configurable within the API definition. + +The HTTP method, body, header values, and target URL can all be configured in the API definition. The [request body](#webhook-payload) is generated using a Tyk template file that has access to the [event metadata](/api-management/gateway-events#event-metadata). + +The webhook event handler runs in its own process and so does not block the operation of the Gateway. + +##### Webhook cooldown + +It is very likely that an `AuthFailure` event will fire on the same endpoint more than once if the requesting client is automated. If this event triggered a webhook that caused an email to be sent, then if this event occurred 10 times a second, the email recipient would be flooded with emails. In an attempt to mitigate against events such as this, you can set a cooldown timer, in the webhook handler. This prevents the webhook from being triggered again if the event is fired again within the time period specified. + +##### Webhook payload + +When your webhook event handler is triggered, it will send an HTTP request to the configured target. For HTTP methods that support a request body, for example `POST`, the event handler will process a [Go template](/api-management/traffic-transformation/go-templates) to produce the payload. + +If no template is provided in the webhook event handler configuration in the API definition, Tyk Gateway will look for the default file `templates/default_webhook.json`. Any text file accessible to the Gateway can be used to store the Go template to be used by the event handler when constructing the payload. + +The event handler has access to the [event metadata](/api-management/gateway-events#event-metadata) and this can be accessed by the template using the `{{.Meta.XXX}}` namespace. + +The [event type](/api-management/gateway-events#event-types) that triggered the event handler can be accessed as `{{.Type}}`. + +For most event types, the default webhook template has this form: + +```json +{ + "event": "{{.Type}}", + "message": "{{.Meta.Message}}", + "path": "{{.Meta.Path}}", + "origin": "{{.Meta.Origin}}", + "key": "{{.Meta.Key}}" +} +``` + +This would generate a request body (payload) such as: +```json +{ + "event": "RatelimitExceeded", + "message": "API Rate Limit Exceeded", + "path": "/example-global-webhook/", + "origin": "99.242.139.220", + "key": "apilimiter-66336c67cb7191f791f167134b20d1f4c14b4bb5672b57f4b2813c86" +} +``` + +#### Using webhooks with Tyk Dashboard + +Webhook event handlers are configured within the API definition, which is used by Tyk Gateway to determine the appropriate action to be performed in response to a Gateway event. + +When using Tyk Dashboard, you are able to create *global webhooks* that can be re-used across multiple events and APIs, allowing you to modify the webhook configuration for a batch of APIs and/or events from one location. + +##### Local and global webhooks + +Tyk Dashboard supports the declaration of webhooks *globally* and *locally*: +- **Global webhooks** are declared outside the API definition and linked via a *webhook id*; changes to the global webhook definition will be reflected in all APIs that reference that *webhook id* +- **Local webhooks** are fully defined within the API definition; changes to the local webhook configuration will affect only the API within which it is defined + +*Global webhook definitions* are registered with the Dashboard using the [UI](#creating-a-global-webhook-definition-using-tyk-dashboard) or [Dashboard API](https://tyk.io/docs/api-reference/webhooks/list-webhooks) and assigned a unique *webhook id* that can be obtained via the [Dashboard API](https://tyk.io/docs/api-reference/webhooks/list-webhooks) or via drop-down selection within the UI. + +If you assign a global webhook definition to an API to handle an event, then Tyk Dashboard will retrieve the definition and update it in the API definition when the API is loaded (or re-loaded) to the Gateway. + +##### Creating a global webhook definition using Tyk Dashboard + +To create a global webhook definition from the Dashboard UI you should follow these steps: + +**Steps for Configuration** + +1. **Create the webhook definition** + + Select **Webhooks** from the **API Management** Menu: + + Webhooks menu item + + Click **Add Webhook**. + + Add webhook button + +2. **Configure the webhook** + + Now you need to tell Tyk how and where to send the request. You can include custom headers, for example to inform the target service that the request has come from Tyk - remember to click **ADD** to add the custom header to the configuration. + + Add webhook detail + + Click **Save** to save it. + +
+ +If you're using Tyk OAS APIs, then you can find details and examples of how to configure webhook event handlers [here](/api-management/gateway-events#webhook-event-handlers-with-tyk-oas-apis). + +If you're using Tyk Classic APIs, then you can find details and examples of how to configure webhook event handlers [here](/api-management/gateway-events#webhook-event-handlers-with-tyk-classic-apis). + +### Webhook event handlers with Tyk OAS APIs + +[Webhooks](/api-management/gateway-events#event-handling-with-webhooks) are event handlers that can be registered against API Events. The webhook will be triggered when the corresponding event is fired and will send a customizable fixed payload to any open endpoint. + +Webhooks are configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/gateway-events#webhook-event-handlers-with-tyk-classic-apis) page. + +#### Set up a webhook event handler in the Tyk OAS API Definition + +Event handling is configured by adding the `eventHandlers` object to the `server` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition. + +The `eventHandlers` object is an array containing configurations for all event handlers registered with the API. + +##### Local webhook configuration + +When using a local webhook, the event handler element in the `eventHandlers` object has the following configuration which fully declares the webhook behaviour: +- `enabled`: enable the event handler +- `trigger`: the API event that will trigger the webhook +- `type`: the type of event handler, in this case should be set to `webhook` +- `cooldownPeriod`: the [webhook cooldown](/api-management/gateway-events#webhook-cooldown) for duplicate events (in duration format, e.g. 10s, 1m30s); use this to prevent flooding of the target endpoint when multiple events are fired in quick succession +- `name`: a human readable name for the webhook, which will be displayed in Tyk Dashboard +- `url`: this is an **absolute URL** to which the request will be sent +- `method`: this can be any of `GET`, `PUT`, `POST`, `PATCH` or `DELETE` and will be the HTTP method used to send the request; methods that do not support an encoded request body will not have the event metadata provided with the request; we advise using `POST` where possible +- `bodyTemplate`: this is the path to the [webhook template](/api-management/gateway-events#webhook-payload) that will be used to construct the request body +- `headers`: a map of custom headers to be provided with the request + +For example: +```json {hl_lines=["18-33"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-local-webhook", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "components": {}, + "x-tyk-api-gateway": { + "info": { + "name": "example-local-webhook", + "state": { + "active": true + } + }, + "server": { + "eventHandlers": [ + { + "enabled": true, + "trigger": "RatelimitExceeded", + "cooldownPeriod": "1s", + "type": "webhook", + "name": "My local webhook", + "url": "https://webhook.site/", + "method": "POST", + "headers": [ + { + "name": "X-Tyk", + "value": "example-local-webhook" + } + ], + "bodyTemplate": "templates/default_webhook.json" + } + ], + "listenPath": { + "strip": true, + "value": "/example-local-webhook/" + } + }, + "upstream": { + "rateLimit": { + "enabled": true, + "per": "10s", + "rate": 2 + }, + "url": "http://httpbin.org/" + } + } +} +``` + +In this example a local webhook has been registered to trigger when the `RatelimitExceeded` event is fired. The request rate limit has been set at 2 requests per 10 seconds, so simply make three requests in quick succession to trigger the webhook. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the local webhook feature. + +Note that to test this you will need to provide a valid target URL for your webhook to send the request; we've used `http://webhook.site`. + + +##### Global webhook configuration + +When using a *global webhook*, the event handler element in the `eventHandlers` object has the following configuration, which references the externally declared webhook using its `id`: +- `enabled`: enable the event handler +- `trigger`: the API event that will trigger the webhook +- `type`: the type of event handler, in this case should be set to `webhook` +- `cooldownPeriod`: the [webhook cooldown](/api-management/gateway-events#webhook-cooldown) for duplicate events (in duration format, e.g. 10s, 1m30s); use this to prevent flooding of the target endpoint when multiple events are fired in quick succession +- `id`: the *webhook id* assigned by Tyk to the global webhook when it was created (this can be determined using the [list webhooks](https://tyk.io/docs/api-reference/webhooks/list-webhooks) endpoint in the Tyk Dashboard API) + +For example: + +```json {hl_lines=["18-24"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-global-webhook", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "components": {}, + "x-tyk-api-gateway": { + "info": { + "name": "example-global-webhook", + "state": { + "active": true + } + }, + "server": { + "eventHandlers": [ + { + "enabled": true, + "trigger": "RatelimitExceeded", + "cooldownPeriod": "1s", + "type": "webhook", + "id": "" + } + ], + "listenPath": { + "strip": true, + "value": "/example-global-webhook/" + } + }, + "upstream": { + "rateLimit": { + "enabled": true, + "per": "10s", + "rate": 2 + }, + "url": "http://httpbin.org/" + } + } +} +``` + +In this example a local webhook has been registered to trigger when the `RatelimitExceeded` event is fired. The request rate limit has been set at 2 requests per 10 seconds, so simply make three requests in quick succession to trigger the webhook. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the global webhook feature. + +Note, however, that to test this you will need to create a *global webhook* in your Tyk Dashboard and replace the value in `id` with the *webhook id* that Tyk Dashboard has allocated to your webhook. You can find this by querying the [list webhooks](https://tyk.io/docs/api-reference/webhooks/list-webhooks) endpoint in the Tyk Dashboard API. +
+
+ + +When a *global webhook* is registered to a Tyk OAS API, Tyk will create a read-only copy of the webhook [configuration](#local-webhook-configuration) (`url`, `method`, `bodyTemplate`, `headers`) within the API definition. This is so that Tyk Gateway knows how to handle the event, as it does not have access to the store of *global webhooks* registered with Tyk Dashboard. +
+
+If the global webhook is subsequently deleted from the Tyk Dashboard, the webhook will automatically be converted to a local webhook in any API definition that was using it. +
+ + + +#### Set up a webhook event handler in the Tyk Dashboard + +It is very simple to register webhooks to be triggered in response to specific API events when using Tyk OAS APIs with the Tyk Dashboard. The API Designer in the Dashboard allows you to define *local webhooks* and to register *global webhooks* to handle events. + +If you want to use a *global webhook* then you'll need to declare it first, following [these instructions](/api-management/gateway-events#creating-a-global-webhook-definition-using-tyk-dashboard). + +1. **Add event handler** + + From the **Settings** tab in the API Designer, scroll down to the **Server** section to find the **Event Handlers** pane. Select **Add Event**. + + Add an event handler from the Server section + +2. **Choose the event to be handled** + + This will add an event handler to the API. You'll need to select which event you want to handle from the drop-down list. Note that currently Tyk OAS only supports webhook event handlers, so this will default to *webhook* type. + + Choose the event that will trigger the webhook + +3. **Choose and configure global webhook** + + If you want to use a webhook that you've already registered with Tyk Dashboard, ensure that the **Webhook source** is set to **Global webhook** then select from the drop-down list. + + The only other thing you'll need to configure is the cooldown period. + + Select from the list of available global webhooks + + Note that Tyk automatically retrieves the details of the *global webhook* and displays them (read-only) in the API designer. + + A fully configured global webhook + + Don't forget to select **Save API** to apply the changes. + +4. **Configure local webhook** + + If you don't want to use a shared *global webhook* but instead want to configure a *local webhook* only available to this API/event then you should ensure that the **Webhook source** is set to **Local webhook**. + + Ready to configure a local webhook + + Now you can complete the various fields to set up your *local webhook*. If you want to add custom headers to send with the HTTP request, select **New Header** then enter the header key and value. + + A fully configured global webhook + + Don't forget to select **Save API** to apply the changes. + +### Webhook event handlers with Tyk Classic APIs + +[Webhooks](/api-management/gateway-events#event-handling-with-webhooks) are event handlers that can +be registered against API Events. The webhook will be triggered when the corresponding event is fired and will send a +customisable fixed payload to any open endpoint. + +Webhooks are configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API +Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk +OAS](/api-management/gateway-events#webhook-event-handlers-with-tyk-oas-apis) +page. + +#### Set up a webhook event handler in the Tyk Classic API Definition + +To add a webhook event handler you must add a new event handler object within the `event_handlers.events` section of the +API definition for the appropriate [API event](/api-management/gateway-events#event-types). + +The event handler object has the following configuration: + +- `handler_name`: this identifies the type of event handler and must be set to `eh_web_hook_handler` +- `handler_meta`: this structure configures the HTTP request that will be sent when the webhook is triggered + +The `handler_meta` object has the following configuration: + +- `method`: this can be any of `GET`, `PUT`, `POST`, `PATCH` or `DELETE` and will be the HTTP method used to send the + request; methods that do not support an encoded request body will not have the event metadata provided with the + request; we advise using `POST` where possible +- `target_path`: this is an **absolute URL** to which the request will be sent +- `template_path`: this is the path to the [webhook + template](/api-management/gateway-events#webhook-payload) that will be + used to construct the request body +- `header_map`: a map of custom headers to be provided with the request +- `event_timeout`: the [webhook + cooldown](/api-management/gateway-events#webhook-cooldown) for duplicate + events (in seconds); use this to prevent flooding of the target endpoint when multiple events are fired in quick succession + +For example: + +```json {linenos=true, linenostart=1} +{ + "event_handlers": { + "events": { + "AuthFailure": [ + { + "handler_name": "eh_web_hook_handler", + "handler_meta": { + "method": "POST", + "target_path": "http://posttestserver.com/post.php?dir=tyk-event-test", + "template_path": "templates/default_webhook.json", + "header_map": { "X-Tyk-Test-Header": "Tyk v1.BANANA" }, + "event_timeout": 10 + } + } + ] + } + } +} +``` + +In this example, when the `AuthFailure` event is fired, the webhook event handler will send a request to +`POST http://posttestserver.com/post.php?dir=tyk-event-test` and then start a 10 second cooldown before another webhook +request can be sent. + +The request will have one custom header `X-Tyk-Test-Header: Tyk v1.BANANA` and the body will be constructed from the +webhook template located at `templates/default_webhook.json`. + + + +This manually configured webhook event handler is private to the API within which it has been defined, it is not a +[global +webhook](/api-management/gateway-events#using-webhooks-with-tyk-dashboard). + + + +#### Set up a webhook event handler in the Tyk Dashboard + +It is very simple to register webhooks to be triggered in response to specific API events when using Tyk Classic APIs +with the Tyk Dashboard. The API Designer in the Dashboard allows you to register _global webhooks_ to handle events. + +Note that Tyk Gateway does not have access to the _global webhook_ definitions registered with Tyk Dashboard and can +only operate on the configuration within the API definition. Dashboard will manage the conversion of _global webhooks_ +to [locally defined webhook handlers](#set-up-a-webhook-event-handler-in-the-tyk-classic-api-definition) within the Tyk +Classic API definition, automatically updating the configuration in each API definition when the APIs are reloaded to +the Gateway. + +1. **Define the webhook** + + Before you can configure a webhook event handler for your API, you must first create a global webhook from the + **Webhooks** screen in the **API Management** menu, as described + [here](/api-management/gateway-events#creating-a-global-webhook-definition-using-tyk-dashboard). + +2. **Register the webhook with the event** + + From the API Designer select the **Advanced Options** tab and locate the **Webhooks** panel: + + Webhook API Details + + Now: + + - select the _API Event_ for which you want to trigger the webhook from the dropdown list + - select the _Webhook to use_ when the event fires, again from the dropdown list + - finally, configure the required _Cooldown period_ + - click **Add** + + Note that you can register multiple webhooks to be triggered in response to a single event and you can register the same + webhook with multiple API events. + + Remember to click **Save** to save your changes. + +#### Set up a webhook event handler in Tyk Operator + +Tyk Operator supports event handler integration for Tyk Classic API Definition. Configuring the `event_handlers` field +in ApiDefinition Custom Resource Definition (CRD) enables webhooks to be triggered by [specific +API events](/api-management/gateway-events#event-types). + +The process for configuring webhook event handlers using Tyk Operator is similar to that explained in +[Set up a webhook event handler in the Tyk Classic API Definition](#set-up-a-webhook-event-handler-in-the-tyk-classic-api-definition). +The example API Definition below enables the event handler by setting `spec.event_handlers`. + +```yaml {hl_lines=["14-25"],linenos=true, linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: webhook-handler +spec: + name: webhook-handler + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /webhook-handler + strip_listen_path: true + event_handlers: + events: + AuthFailure: + - handler_name: "eh_web_hook_handler" + handler_meta: + method: "POST" + name: "webhook name" + target_path: "http://posttestserver.com/post.php?dir=tyk-event-test" + template_path: "templates/default_webhook.json" + header_map: + X-Tyk-Test-Header: "Tyk v1.BANANA" + event_timeout: 10 +``` + + +## Logging API events + +Tyk’s built-in logging event handler is designed primarily for debugging purposes and will store details of an API event to the configured logger output. + +The Tyk platform can be configured to log at various verbosity levels (info, debug, warn, error) and can be integrated with third-party log aggregation tools like Sentry, Logstash, Graylog, and Syslog. For full details on configuring the Tyk logger, see [this section](/api-management/logs/application-logs). + +
+ + +Logging event handlers are currently only supported by Tyk Classic APIs. + + + +### Configuring the event handler + +Registering a logging event handler to your Tyk Classic API is the same as adding any other event handler, within the `event_handlers` section of the API definition. + +The `handler_name` for the logging event handler should be set to: `eh_log_handler`. + +The `handler_meta` for the logging event handler contains a single field: +- `prefix` is a label that will be prepended to each log entry + +For example, to register event handlers to log the `AuthFailure` and `KeyExpired` events you might add the following to your API definition: + +```json +{ + "event_handlers": { + "events": { + "AuthFailure": [ + { + "handler_name": "eh_log_handler", + "handler_meta": { + "prefix": "AuthFailureEvent" + } + } + ], + "KeyExpired": [ + { + "handler_name": "eh_log_handler", + "handler_meta": { + "prefix": "KeyExpiredEvent" + } + } + ] + } + } +} +``` + +In this example +- the `AuthFailure` event will trigger the event handler to generate a log with the prefix `AuthFailureEvent` +- the `KeyExpired` event will trigger the event handler to generate a log with the prefix `KeyExpiredEvent` + +When the event handler is triggered an entry will be made in the log containing the corresponding prefix, which can be useful for monitoring and debugging purposes. + +## Custom API event handlers + +Tyk supports you to script your own custom code in JavaScript (JS) that will be invoked in response to API events. This is executed asynchronously so you don't need to worry about it blocking the Gateway handling requests. Event handlers like this can be very powerful for automating session, user and API-level functions. + +It is important to note that unlike custom JavaScript [plugins](/api-management/plugins/javascript#), custom event handlers execute in a *global* JavaScript environment. This means that you need to be careful when naming the event handlers: if you use the same event handler name for different event handling code across two APIs, only one of them will execute, as the other will be overridden when loaded. + +Custom event handlers have access to the [JavaScript API](/api-management/plugins/javascript#javascript-api) which gives access to the session object and enables your code to make HTTP calls. This is particularly useful if you want to interface with another API with a complex request/response cycle. + +
+ + +Custom event handlers are currently only supported by Tyk Classic APIs. + + + +### Creating a custom event handler + +A custom event handler consists of a function that accepts two variables (`event` and `context`) and has no return value. + +Creating an event handler is very similar to [creating custom JS plugins](/api-management/plugins/javascript#using-javascript-with-tyk), simply invoke the correct constructors with a closure in the TykJS namespace: + +```js +// ---- Sample custom event handler ----- +var sampleHandler = new TykJS.TykEventHandlers.NewEventHandler({}); + +sampleHandler.NewHandler(function(event, context) { + // You can log to Tyk console output by calling the built-in log() function: + log("This handler does nothing, but this will appear in your terminal") + + return +}); +``` + +#### The `event` object + +This contains the [event metadata](/api-management/gateway-events#event-metadata) in the following structure: + +```json +{ + "EventType": "Event Type Code", + "EventMetaData": { + "Message": "My Event Description", + "Path": "/{{api_id}}/{{path}}", + "Origin": "1.1.1.1:PORT", + "Key": "{{Auth Key}}" + }, + "TimeStamp": "2024-01-01 23:59:59.111157073 +0000 UTC" +} +``` + +#### The `context` Variable + +Tyk injects a `context` object into your event handler giving access to more information about the request. This object has the following structure: + +```js +type JSVMContextGlobal struct { + APIID string + OrgID string +} +``` + +It is populated with the API ID and Org ID of the request that your custom function can use together with the `event` metadata to interact with the Tyk REST API functions, for example: + +```js +// Use the TykGetKeyData function to retrieve a session from the session store, use the context variable to give the APIID for the key. +var thisSession = JSON.parse(TykGetKeyData(event.EventMetaData.Key, context.APIID)) +log("Expires: " + thisSession.expires) +``` + +### Registering a custom event handler + +Registering a custom event handler to your Tyk Classic API is the same as adding any other event handler, within the `event_handlers` section of the API definition. + +The `handler_name` for a custom event handler should be set to: `eh_dynamic_handler`. + +The `handler_meta` for a custom event handler consists of two fields: +- `name` is the unique name of your middleware object +- `path` is the relative path to the file (it can be absolute) + +For example, to register a custom event handler with the name `sessionHandler` to be invoked in response to the `KeyExpired` event you would add the following to your API definition: + +```json +{ + "event_handlers": { + "events": { + "KeyExpired": [ + { + "handler_name":"eh_dynamic_handler", + "handler_meta": { + "name": "sessionHandler", + "path": "event_handlers/session_editor.js" + } + } + ] + } + } +} +``` + +### Loading custom event handlers + +The JavaScript files are loaded on API reload into the global JSVM. If a hot-reload event occurs, the global JSVM is re-set and files are re-loaded. This could cause event handlers that are currently executing to get abandoned. This is a measured risk and should not cause instability, however it should be noted that because of this, in an environment where reloads occur frequently, there is risk that event handler may not fire correctly. + +## Monitoring quota consumption + +Tyk provides the ability to actively monitor both user and organization quotas, using a dedicated webhook to notify your stakeholders, your system stack or the requesting API client when certain thresholds have been reached for a token. + +Unlike API event [webhooks](/api-management/gateway-events#event-handling-with-webhooks) the quota monitor is configured at the Gateway level. + +
+ + +Advanced quota threshold monitoring is currently only supported by Tyk Classic APIs. + + + +### Configuring the quota consumption monitor + +To enable advanced quota monitoring you will need to add a new `monitor` section to your Tyk Gateway configuration file (`tyk.conf`). + +This has the following fields: +- `enable_trigger_monitors`: set to `true` to have the monitors start to measure quota thresholds +- `configuration`: a [webhook configuration](/api-management/gateway-events#event-handling-with-webhooks) object +- `global_trigger_limit`: this is a percentage of the quota that the key must consume for the webhook to be fired +- `monitor_user_keys`: set to `true` to monitor individual tokens (this may result in a large number of triggers as it scales with the number of user tokens that are issued) +- `monitor_org_keys`: set to `true` to monitor organization quotas + +For example: + +```json +{ + "monitor": { + "enable_trigger_monitors": true, + "configuration": { + "method": "POST", + "target_path": "http://posttestserver.com/post.php?dir=tyk-monitor-drop", + "template_path": "templates/monitor_template.json", + "header_map": {"x-tyk-monitor-secret": "12345"}, + "event_timeout": 10 + }, + "global_trigger_limit": 80.0, + "monitor_user_keys": false, + "monitor_org_keys": true + } +} +``` + +With this configuration, a monitor is configured to issue a request to `POST http://posttestserver.com/post.php?dir=tyk-monitor-drop` when 80% of the API-level quota has been consumed. This request will have the `x-tyk-monitor-secret` header (set to a value of `12345`) and will provide the content of the template file found at `templates/monitor_template.json` in the request body. A minimum of 10 seconds will elapse between successive monitor webhooks being fired. + +
+ + +If you are using our [Classic Developer Portal](/tyk-developer-portal/tyk-portal-classic/portal-events-notifications), developers registered in the portal will also receive emails about quota threshold limits being reached. + + + +#### Setting advanced thresholds + +The default quota consumption monitor will be triggered at the same level of quota usage for all users. Sometimes you might want to have a more granular approach with different triggering thresholds per user or organization. Sometimes you might want to fire the event at multiple thresholds, for example when the user hits 50%, 75% and 90% of their allowed quota. + +You can set user specific trigger levels for a user by additionally adding a `monitor` section to the Key's [Session](/api-management/access-control/sessions-and-keys/understanding-sessions). This has one field, which is an array of `trigger_limits` (thresholds) that must be in *descending* order and represent the percentage of the quota that must be reached in order for the trigger to be fired, for example: + +```yaml +"monitor": { + "trigger_limits": [90.0, 75.0, 50.0] +} +``` + +If this is included in the session object, then the quota threshold event will be fired and the monitor webhook triggered when the user hits 50%, then 75%, and then again at 90% consumption. + +You can configure advanced thresholds for all users in an organization by adding the `monitor` section to the organization session object. + +### Webhook payload + +When the quota consumption monitor is fired, the webhook request that is issued will have the following payload: + +```json +{ + "event": "TriggerExceeded", + "message": "Quota trigger reached", + "org": "53ac07777cbb8c2d53000002", + "key": "", + "trigger_limit": "80", +} +``` + +- `trigger_limit` will indicate which threshold has been reached (as defined in the session object's `monitor` section). +- `org` will contain the OrgID for the user or organization that triggered the event +- `key` will contain the *raw API key* used in the request only if the event was triggered by a user quota + +*Note: if the webhook was triggered by an organization threshold, `key` will be blank.* + +
+ + +When the monitor is triggered by a user hitting their quota threshold, the raw API key is provided in the webhook payload. It is important to secure the webhook endpoint and to handle the payload securely on the receiving end. + + diff --git a/api-management/graphql.mdx b/api-management/graphql.mdx new file mode 100644 index 0000000000..b95721a125 --- /dev/null +++ b/api-management/graphql.mdx @@ -0,0 +1,2531 @@ +--- +title: "GraphQL" +description: "Learn how to configure and manage GraphQL APIs" +keywords: "GraphQL, Federation, Entities, Grapqhl Proxy, Validation, Schema, Complexity Limiting, Persisted Queries, Migration Guide, GraphQL Playground, GQL Headers" +sidebarTitle: "Overview" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + +## Overview + +Tyk has **native** GraphQL support, so it doesn’t require any external services or middleware. +It fully complies with the latest GraphQL specifications, as outlined on the [GraphQL Foundation webpage](https://spec.graphql.org/), including: + +- **[Queries](https://spec.graphql.org/October2021/#sec-Query)** – Fetching data +- **[Mutations](https://spec.graphql.org/October2021/#sec-Mutations)** – Modifying data +- **[Subscriptions](https://spec.graphql.org/October2021/#sec-Subscription)** – Real-time updates +- **[Schema Types](/api-management/graphql/graphql-schema-types)** - Defining your data structure + + +### What can you do with GraphQL and Tyk? + +You can securely expose existing GraphQL APIs using our [GraphQL core functionality](/api-management/graphql#create-a-graphql-api). + +In addition to this, you can also use Tyk's integrated GraphQL engine to build a [Universal Data Graph](/api-management/data-graph#overview). The Universal Data Graph (UDG) lets you expose existing services as one single combined GraphQL API. + +See our video on getting started with GraphQL. + + + +### What is GraphQL? + +> GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. + +source: [GraphQL Foundation website](https://graphql.org/) + +### Why would you want to use GraphQL? + +Since this is the documentation section, we won't get into a debate about GraphQL vs REST. The main benefits of using GraphQL are: +* **Reduced network traffic** One of the biggest benefits of GraphQL is that it allows clients to specify exactly what data they need. This means that you can avoid sending unnecessary data over the network, which can help reduce the amount of traffic and improve the performance of your application. +* **Flexibility** GraphQL is very flexible and can be used with many different programming languages and frameworks. It can also be used to retrieve data from multiple sources, such as databases, APIs, and even third-party services. +* **Simplified data fetching** With GraphQL, you can fetch all the data you need with a single request. This is because GraphQL allows you to specify exactly what data you need and how it should be structured, which can simplify the process of fetching data and reduce the amount of code you need to write. +* **Easy maintenance** Because GraphQL allows you to define a schema for your data, it can be easier to maintain and evolve your API over time. This is because changes to the schema can be made without breaking existing clients, as long as the changes are backward compatible. +* **Strong typing** GraphQL has a strong type system that allows you to define the shape of your data and ensure that the data you receive is of the correct type. This can help catch errors early on and make your code more reliable. +* **Better developer experience for certain use cases** Examples of those use cases mostly mentioned by developers are: APIs with multiple consumers that have very different requirements, public APIs with large groups of unknown users (like Shopify of Github), rapidly evolving APIs, backends for mobile applications, aggregating data from multiple microservices and development of data-driven products. + +Our team has also published some blog posts that go deeper into GraphQL discussions. You can check some of them here: +* [How Airbnb, Shopify, GitHub and more are winning with GraphQL](https://tyk.io/blog/how-airbnb-shopify-github-and-more-are-winning-with-graphql-and-why-you-may-need-it-too/) +* [Who is Tyk GraphQL functionality for](https://tyk.io/blog/using-tyks-new-graphql-functionality-whos-it-for-and-what-does-it-do/) +* [GraphQL: Performance is no longer a trade-off](https://tyk.io/blog/graphql-performance-is-no-longer-a-trade-off/) + +## Create a GraphQL API + +GraphQL API can be created in Tyk using: +* Tyk Dashboard UI +* Tyk Dashboard API +* Tyk Gateway API - for OSS users + +The process is very similar to [HTTP API creation](/api-management/gateway-config-managing-classic#create-an-api) with a few additional steps to cover GraphQL-specific functionalities. + +### Via Tyk Dashboard UI + +#### Prerequisites + +In order to complete the next steps, you need to have [Tyk Self Managed installed](/tyk-self-managed/install). You can also create a 5-week trial account in Tyk Cloud. + + + +#### Steps for Configuration + +1. **Select "APIs" from the "System Management" section** + + API Menu + +2. **Click "ADD NEW API"** + + Add API button location + +3. **Set up the Base Configuration for your API** + + Create GQL API + + - From the **Overview** section, add your **API Name** and your API **Type** (In this case it's GraphQL). + - From the **Details** section, add your **Target URL**. This will set the upstream origin that hosts the service you want to proxy to. As an example, you can use [https://countries.trevorblades.com/](https://countries.trevorblades.com/). + - In case your upstream GQL service is protected, tick the box next to **Upstream Protected** and provide authorization details, so that Tyk can introspect the GraphQL service. You can provide authorization details as a set of headers or a certificate. [Introspection](/api-management/graphql#introspection) of your upstream service is important for Tyk to correctly work with your GraphQL. + - If you would like to persist authorization information for future use you can tick the **Persist headers for future use** box. That way, if the upstream GQL schema changes in the future, you will be able to update it easily in Tyk. + - Click **Configure API** when you have finished + +4. **Set up the Authentication for your API** + + From the **Authentication** section: + + Authentication + + You have the following options: + + - **Authentication mode**: This is the security method to use with your API. First, you can set it to `Open(Keyless)`, but that option is not advised for production APIs. See [Client Authentication](/api-management/client-authentication) for more details on securing your API. + - **Strip Authorization Data**: Select this option to strip any authorization data from your API requests. + - **Auth Key Header Name**: The header name that will hold the token on inbound requests. The default for this is `Authorization`. + - **Allow Query Parameter As Well As Header**: Set this option to enable checking the query parameter as well as the header for an auth token. **This is a setting that might be important if your GQL includes subscription operations**. + - **Use Cookie Value**: It is possible to use a cookie value as well as the other two token locations. + - **Enable client certificate**: Select this to use Mutual TLS. See [Mutual TLS](/api-management/implement-tls#secure-hosted-apis-with-mtls) for details on implementing mutual TLS. + +5. **Save the API** + + Click **SAVE** + + Save button + + Once saved, you will be taken back to the API list, where the new API will be displayed. + + To see the URL given to your API, select the API from the list to open it again. The API URL will be displayed at the top of the editor: + + API URL location + + Your GQL API is now secured and ready to use. + +### Via Tyk Dashboard API + +#### Prerequisites + +It is possible to create GQL APIs using [Tyk Dashboard APIs](https://tyk.io/docs/api-reference/apis/get-list-of-apis). To make things easier you can use our [Postman collection](https://www.postman.com/tyk-technologies/workspace/tyk-public-workspace/overview). + +You will need an API key for your organization and one command to create a GQL API and make it live. + +#### Steps for Configuration + +1. **Obtain your Tyk Dashboard API Access Credentials key & Dashboard URL** + + From the Tyk Dashboard, select "Users" from the "System Management" section. + Click **Edit** for your user, then scroll to the bottom of the page. Your **Tyk Dashboard API Access Credentials** key is the first entry: + + API key location + + Store your Dashboard Key, Dashboard URL & Gateway URL as environment variables so you don't need to keep typing them in: + + ```bash + export DASH_KEY=db8adec7615d40db6419a2e4688678e0 + + # Locally installed dashboard + export DASH_URL=http://localhost:3000/api + + # Tyk's Cloud Dashboard + export DASH_URL=https://admin.cloud.tyk.io/api + + # Locally installed gateway + export GATEWAY_URL=http://localhost:8080 + + # Your Cloud Gateway + export GATEWAY_URL=https://YOUR_SUBDOMAIN.cloud.tyk.io + ``` + +2. **Query the `/api/apis` endpoint to see what APIs are loaded** + + ```curl + curl -H "Authorization: ${DASH_KEY}" ${DASH_URL}/apis + {"apis":[],"pages":1} + ``` + + For a fresh install, you will see that no APIs currently exist. + +3. **Create your first GQL API** + + This example API definition configures the Tyk Gateway to reverse proxy to the [https://countries.trevorblades.com/](https://countries.trevorblades.com/) public GraphQL service. + + To view the raw API definition object, you may visit: https://bit.ly/3zmviZ3 + + ```curl + curl -H "Authorization: ${DASH_KEY}" -H "Content-Type: application/json" ${DASH_URL}/apis \ + -d "$(wget -qO- https://bit.ly/3zmviZ3)" + {"Status":"OK","Message":"API created","Meta":"64270eccb1821e3a5c203d98"} + ``` + + Take note of the API ID returned in the meta above - you will need it later. + + ``` + export API_ID=64270eccb1821e3a5c203d98 + ``` + +4. **Test your new GQL API** + + ```curl + curl --location ${GATEWAY_URL}/trevorblades/ + --header 'Content-Type: application/json' + --data '{"query":"query {\n countries {\n name\n capital\n awsRegion\n }\n}","variables":{}}' + ``` + + You just sent a request to the gateway on the listen path `/trevorblades`. Using this path-based-routing, the gateway was able to identify the API the client intended to target. + + The gateway stripped the listen path and reverse-proxied the request to https://countries.trevorblades.com/ + +5. **Protect your API** + + Let's grab the API definition we created before and store the output in a file locally. + + ```curl + curl -s -H "Authorization: ${DASH_KEY}" -H "Content-Type: application/json" ${DASH_URL}/apis/${API_ID} | python -mjson.tool > api.trevorblades.json + ``` + + We can now edit the `api.trevorblades.json` file we just created, and modify a couple of fields to enable authentication. + + Change `use_keyless` from `true` to `false`. + + Change `auth_configs.authToken.auth_header_name` to `apikey`. + + Then send a `PUT` request back to Tyk Dashboard to update its configurations. + + ```curl + curl -H "Authorization: ${DASH_KEY}" -H "Content-Type: application/json" ${DASH_URL}/apis/${API_ID} -X PUT -d "@api.trevorblades.json" + {"Status":"OK","Message":"Api updated","Meta":null} + ``` + +6. **Test protected API** + + Send request without any credentials + + ```curl + curl -I ${GATEWAY_URL}/trevorblades/ \ + --header 'Content-Type: application/json' \ + --data '{"query":"query {\n countries {\n name\n capital\n awsRegion\n }\n}","variables":{}}' + + HTTP/1.1 401 Unauthorized + Content-Type: application/json + X-Generator: tyk.io + Date: Wed, 04 Dec 2019 23:35:34 GMT + Content-Length: 46 + ``` + + Send a request with incorrect credentials + + ```curl + curl -I ${GATEWAY_URL}/trevorblades/ \ + --header 'Content-Type: application/json' \ + --data '{"query":"query {\n countries {\n name\n capital\n awsRegion\n }\n}","variables":{}}' \ + -H 'apikey: somekey' + + HTTP/1.1 403 Forbidden + Content-Type: application/json + X-Generator: tyk.io + Date: Wed, 04 Dec 2019 23:36:16 GMT + Content-Length: 57 + ``` + + Congratulations - You have just created your first keyless GQL API, and then protected it using Tyk! + +### Via Tyk Gateway API + +#### Prerequisites + +In order to complete the next steps, you need to have the [Tyk OSS](/tyk-oss-gateway) installed. + + + +#### Creation Methods + +With Tyk OSS, it is possible to create GQL APIs using Tyk's Gateway API or to generate a file with the same object and store it in the `/apps` folder of the Tyk Gateway installation folder. This is demonstrated [in the file-based configuration section](/api-management/manage-apis/deploy-apis/deploy-apis-overview#file-based-configuration). + + +#### Steps for Configuration + + + +A generated API ID will be added to the Tyk API definition if it's not provided while creating a GQL API with Tyk Gateway API. + + + +See our video for adding an API to the Open Source Gateway via the Gateway API and Postman: + + + +You can also use our [Postman collection](https://www.postman.com/tyk-technologies/workspace/tyk-public-workspace/overview) to make things easier. + +In order to use the Gateway API you will need an API key for your Gateway and one command to create the API and make it live. + +1. **Make sure you know your API secret** + + Your Tyk Gateway API secret is stored in your `tyk.conf` file, the property is called `secret`, you will need to use this as a header called `x-tyk-authorization` to make calls to the Gateway API. + +2. **Create a GQL API** + + To create a GQL API, let's send a definition to the `apis` endpoint, which will return the status and version of your Gateway. Change the `x-tyk-authorization` value and `curl` domain name and port to be the correct values for your environment. + + This example API definition configures the Tyk Gateway to reverse proxy to the [https://countries.trevorblades.com/](https://countries.trevorblades.com/) public GraphQL service. + + To view the raw API definition object, you may visit: https://bit.ly/3nt8KDa + + ```curl + curl --location --request POST 'http://{your-tyk-host}:{port}/tyk/apis' \ + --header 'Content-Type: application/json' \ + --header 'Accept: application/json' \ + --header 'X-Tyk-Authorization: {your-secret}' \ + --data "$(wget -qO- https://bit.ly/3nt8KDa)" + ``` + + If the command succeeds, you will see: + ```json + { + "key": "trevorblades", + "status": "ok", + "action": "added" + } + ``` + + **What did we just do?** + + We just sent an API definition to the Tyk `/apis` endpoint. API definitions are discussed in detail in the API section of this documentation. These objects encapsulate all of the settings for an API within Tyk Gateway. + + + +Notice that when creating a GQL API you need to include your GQL service schema in the API definition. Tyk Gateway doesn't have the capacity to introspect your GQL service on its own. + +Including the correct schema allows Tyk Gateway to validate incoming requests against it. More on validation can be found [here](/api-management/graphql#validation) + + + + **Restart or hot reload** + + After generating the file, you must either restart the Gateway or initiate a hot reload through an API call to the gateway, as outlined below: + ```curl + curl -H "x-tyk-authorization: {your-secret}" -s http://{your-tyk-host}:{port}/tyk/reload/group + ``` + + This command will hot-reload your API Gateway(s) and the new GQL API will be loaded, if you take a look at the output of the Gateway (or the logs), you will see that it should have loaded [Trevorblades API](https://countries.trevorblades.com/) on `/trevorblades/`. + + Your GraphQL API is now ready to use. We recommend securing any GraphQL API before publishing it. + + Check the following docs for more on GraphQL-specific security options: + * [Field based permissions](/api-management/graphql#field-based-permissions) + * [Complexity limiting](/api-management/graphql#complexity-limiting-1) + * [Introspection](/api-management/graphql#introspection) + +## GraphQL Proxy Only + +### What is GraphQL Proxy Only + +GraphQL Proxy Only is a GraphQL API with a single data source and a read-only schema. The schema is automatically loaded from the GraphQL upstream, which must support introspection queries. +Like other APIs, the GraphQL API supports policies, but with more advanced settings. + +### Creating a GraphQL API via the Dashboard UI + +1. Log in to the Dashboard and go to APIs > Add New API > GraphQL. + +Creating GraphQL Proxy Only API + +2. Choose a name for your API and provide an upstream URL + + + + + In case your upstream URL is protected, select **Upstream Protected** and provide authorization details (either Header or Certificate information). + + + +3. In this case, the upstream is protected with Basic Authentication, so we add an Authorization header. + + + + + **Persist headers for future use** checkbox is selected. That way, you will not need to provide the auth headers anymore as they will be persisted in the API definition. + + + +Adding Auth Header for GraphQL Proxy Only API + + +4. Once done, click **Configure API**, and the Dashboard API designer will show up. + +5. Configure your API and click **save**, Your API will now be saved. + +### Managing GQL Schema + +There can be a need to update/sync the schema on your GraphQL API, say when the schema on the upstream is updated. +The Dashboard UI can show the last time your API schema was synced with the upstream schema. + +schema last updated screenshot + +If you click the **Get latest version**, the gateway will make an introspection query to your upstream to fetch the schema. +You need to click **Update** on the top right button, to update your API. + + + +If you upstream is protected, you will need to provide an Authorization Header. In the Dashboard go to your API > Advanced Options > Upstream Auth headers +and fill in your credentials + + + +### Policies, Keys, and Developer Portal + +#### Field-based permission + +You may want to allow different consumers access to your GraphQL API without exposing all data to them. So for example this could be a schema for a GraphQL API: +```graphql +type Query { + accounts: [Account!] +} + +type Account { + owner: String! + number: ID! + balance: Float! +} +``` + +and you don't want some associate with a certain key to access the `balance` field on type `Account`, the gateway will respond with: +```json +{ + "errors": [ + { + "message": "field: balance is restricted on type: Account" + } + ] +} +``` +Check the [Setup field-based permission](/api-management/graphql#setup-field-based-permissions-in-dashboard) section, to learn how to configure them. + + +#### Complexity Limiting + +The complexity of a GraphQL query is about its depth. checkout this query: +```graphql +{ + continents { + countries { + continent { + countries { + continent { + countries { + name + } + } + } + } + } + } +} +``` + +The above query has a depth of seven since the nested queries are seven. + +Tyk offers a solution to limit the depth of a query. +Check out [this link](/api-management/graphql#query-depth-limit) on how to set query depth. + +#### Developer Portal + +As of Tyk v3.0.0, you can now publish GraphQL APIs to the Tyk Developer Portal. +[This section](/tyk-developer-portal/tyk-portal-classic/graphql) will show how you can expose a GraphQL API to the developer portal. + +## Introspection + +### Overview + +A GraphQL server can provide information about its schema. This functionality is called **introspection** and is achievable by sending an **introspection query** to the GraphQL server. + +If **introspection** is a completely new concept for you, browse through the official [GraphQL Specification](https://spec.graphql.org/October2021/#sec-Introspection) published by the GrapQL Foundation to find out more. + +When [creating a GraphQL proxy](/api-management/graphql#create-a-graphql-api) in Tyk Dashboard an introspection query is used to fetch the schema from the GraphQL upstream and display it in the schema tab. + + + +When using a GraphQL proxy the introspection query is always sent to the GraphQL upstream. This means that changes in the Tyk schema won't be reflected in the introspection response. You should keep the schemas synchronised to avoid confusion. + + + +#### Introspection for protected upstreams + +When you are creating a GQL API using Tyk Dashboard and your target GQL API is protected, you need to provide authorization details, so that Tyk Gateway can obtain your schema. + +In the *Create new API* screen you have to tick the **Upstream Protected** option under your Upstream URL. + + Upstream protected + + - From the **Upstream protected by** section choose the right option for your case: Headers or Certificate. + - Choosing **Headers** will allow you to add multiple key/value pairs in *Introsopection headers* section. + - You can also **Persist headers for future use** by ticking that option. This will save information you provided in case in the future your schema changes and you need to sync it again. To understand better where this information will be saved, go to [GQL Headers](/api-management/graphql#graphql-apis-headers). To read more about schema syncing go [here](/api-management/graphql#syncing-gql-schema). +- Choosing **Certificate** will allow you to provide *Domain* details and either *Select certificate* or *Enter certificate ID*. + +#### Turning off introspection + +The introspection feature should primarily be used as a discovery and diagnostic tool for development purposes. + +Problems with introspection in production: + +* It may reveal sensitive information about the GraphQL API and its implementation details. +* An attacker can discover potentially malicious operations. + +You should note that if the *Authentication Mode* is *Open(Keyless)*, GraphQL introspection is enabled and it cannot be turned off. + +GraphQL introspection is enabled in Tyk by default. You can disable the introspection per key or security policy using: +* Tyk Dashboard +* Tyk Dashboard and Gateway API + + + + +First, check the general information on [how to create a security policy with Tyk](/api-management/gateway-config-managing-classic#secure-an-api) + +For GraphQL APIs the *API ACCESS* section will show additional, GQL-specific options that can be enabled. + +Disable introspection + +You can diable introspection by changing the switch position. + +Because introspection control in Tyk works on Policy and Key level, it means you can control each of your consumer's access to introspection. You can have keys that allow introspection, while also having keys that disallow it. + + + + +First, you need to [create an API Key](/api-management/gateway-config-managing-classic#tyk-open-source-2). If you prefer, you can follow [these instructions](/api-management/gateway-config-managing-classic#secure-an-api) to learn how to do this using a [Policy](/api-management/policies). + +Once you learn how to utilize the API to create a Policy or a Key, you can use the following snippet in the Policy or directly in the Session: + +```bash +{ + "access_rights": { + "{API-ID}": { + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "disable_introspection": true, + "allowed_types": [], + "restricted_types": [] + } + } +} +``` + +With this configuration, we set `true` to `disable_introspection` field. When you try to run an introspection query on your API, you will receive an error response *(403 Forbidden)*: + +```bash +{ + "error": "introspection is disabled" +} +``` + + + + + + +Introspection also works for the [Universal Data Graph](/api-management/data-graph#overview). + +### Introspection Queries + +Any GraphQL API can be introspected with the right introspection query. Here's some examples on what introspection queries can look like and what information you can learn about the GraphQL service using them. + +#### Introspecting all types + +This query will respond with information about all types and queries defined in the schema. Additional information like *name*, *description* and *kind* will also be provided. + +```graphql +query { + __schema { + types { + name + description + kind + } + queryType { + fields { + name + description + } + } + } + } + +``` + +#### Introspecting single type details + +If you want to know more about a certain type in the schema, you can use the following query: + +```graphql + query { + __type(name: "{type name}") { + ...FullType + } + } + + fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + + inputFields { + ...InputValue + } + + interfaces { + ...TypeRef + } + + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + + possibleTypes { + ...TypeRef + } + } + + fragment InputValue on __InputValue { + name + description + type { + ...TypeRef + } + defaultValue + } + + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + } +``` + +#### Introspecting types associated with an interface + +The query to introspect a single type can be used for any type, but you might prefer a simpler response for types such as `interface`. With this query you can get a list of objects that implements a specific `interface`. + +```graphql +query { +__type(name: "{interface name}") { + name + kind + description + possibleTypes { + name + kind + description + } +} +} +``` + +#### Introspecting ENUM values + +An `enum` type defines a set of discrete values. With this query you can get a complete list of those values for a chosen `enum`. + +```graphql +query { +__type(name: "{enum name}") { + name + kind + description + enumValues { + name + description + } +} +} +``` + +#### Introspecting query definitions + +GraphQL requires queries to be defined in a special type `Query` in the schema. You can use the below introspection query to find out more about a query operations of the graph. + +```graphql + query { + __type(name: "Query") { + ...QueryType + } + } + + fragment QueryType on __Type { + fields { + name + description + type { + name + kind + } + args { + name + description + type { + name + kind + } + } + } + } +``` + + + +You might find GQL APIs where the `Query` type is called `QueryRoot`. In those cases the above introspection query needs to be modified in line 2 to: `__type(name: "QueryRoot")` + + + +#### Introspecting mutation and subscription definitions + +You should use the same introsopection query as you would for `Query` type, just change the name argument to `Mutation` or `Subscription`. + +#### Full introspection + +If you prefer to introspect GraphQL all at once, you can do that by sending this query: + +```graphql + + query IntrospectionQuery { + __schema { + + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + + locations + args { + ...InputValue + } + } + } + } + + fragment FullType on __Type { + kind + name + description + + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } + } + + fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue + + + } + + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + } + +``` + +Tyk also allows you to block introspection queries for security reasons if you wish to do so. More information on how to do that is provided [here](/api-management/graphql#turning-off-introspection). + +## Validation + +In order to prevent errors happening during request processing or sending invalid queries to the upstream Tyk supports the validation of GraphQL queries and schemas. + +### Query Validation +Tyk's native GraphQL engine supports validating GraphQL queries based on the [GraphQL Specification](https://spec.graphql.org/October2021/). + +Both the GraphQL engine in front of your existing GraphQL API as well as any Universal Data Graph you build gets protected with a validation middleware. + +This means, no invalid request will be forwarded to your upstream. +The Gateway will catch the error and return it to the client. + +### Schema Validation +A broken schema can lead to undesired behaviors of the API including queries not being processed by the GraphQL middleware. As the search for the root cause for +such a malfunction can be tedious, Tyk provides schema validation. + + + +Schema validation is only available when using the Dashboard or Dashboard API. + + + +The schema validation will prevent you from saving or updating an API with a broken schema. This includes schemas breaking the following rules: + - No duplicated operation types (Query, Mutation, Subscription) + - No duplicated type names + - No duplicated field names + - No duplicated enum values + - No usage of unknown types + +When using the [Dashboard API](https://tyk.io/docs/api-reference/apis/get-list-of-apis) the response for a broken schema will be a *400 Bad Request* with a body containing the validation errors. For example: + +```json +{ + "Status": "Error", + "Message": "Invalid GraphQL schema", + "Meta": null, + "Errors": [ + "field 'Query.foo' can only be defined once" + ] +} +``` + +## GraphQL APIs headers + +Users can set up two kinds of headers when configuring GraphQL APIs: + +- Introspection headers +- Request headers + +Both types of headers can be set in the Advanced Options tab in Tyk Dashboard. + +### Introspection headers + +Tyk Dashboard can introspect any upstream GraphQL API and download a copy of the GQL schema. That schema will be displayed in the Schema tab. + +For protected upstreams that require authorization for introspection, Tyk allows you to persist authorization headers within the GraphQL API configuration using **Introspection headers**. + +Introspection headers + +Any header key/value pair defined in **Introspection headers** will only be used while making an introspection call from Tyk Dashboard to the upstream. Those headers will not be used while proxying requests from consumers to the upstream. + +**Introspection headers** can also be configured in the raw API definition: + +```json +... +"graphql": { + "execution_mode": "proxyOnly", + "proxy": { + "auth_headers": { + "admin-auth": "token-value" + } + } +} +``` + +### Request headers + +You can enrich any GraphQL request proxied through Tyk Gateway with additional information in the headers by configuring **Request headers** in the Tyk Dashboard. + +Request headers + +**Request headers** values can be defined as context variables. To know how to refer to request context variables check [this page](/api-management/traffic-transformation/request-context-variables). + +Any header key/value pair defined in **Request headers** will only be used to inject headers into requests proxied through the Gateway. It will not be used to introspect the upstream schema from Tyk Dashboard. + +**Request headers** can also be configured in the raw API definition: + +```bash +... +"graphql": { + "execution_mode": "proxyOnly", + "proxy": { + "request_headers": { + "context-vars-metadata": "$tyk_context.path", + "static-metadata": "static-value" + } + } +} +``` + +## Syncing GQL Schema + +A GraphQL Proxy API maintains a copy of the upstream GraphQL schema. When the upstream schema changes, these updates need to be reflected in the proxy schema. + +To manage this, Tyk Dashboard stores the timestamp of the last schema change each time a GraphQL API is updated. This timestamp helps identify whether the schema is outdated and needs to be synced with the upstream version. You can find this information above the schema editor. + +To sync the schema, click the **Resync** button. + + + + +Syncing schemas is only available for proxy-only GraphQL APIs and **not** for UDG. + + + +Sync Schema Button + +If your upstream is protected then you need to make sure you provide Tyk with the authorization details to execute the introspection query correctly. You can add those detail while [creating GQL API](/api-management/graphql#introspection-for-protected-upstreams) or using [Introspection headers](/api-management/graphql#introspection-headers) later on. + + + +## Persisting GraphQL queries + +Tyk Gateway `4.3.0` release includes a way to expose GraphQL queries as REST endpoints. For now, this can only be configured via the raw API definition, Tyk Dashboard support is coming soon. + +### How to persist GraphQL query + +The ability to expose a GraphQL query as a REST endpoint can be enabled by adding the `persist_graphql` section of the `extended_paths` on an HTTP type in any API version you intend to use to serve as the GraphQL query to REST endpoint proxy. + +Here is a sample REST API proxy for the HTTP type API: + +```json +{ + "name": "Persisted Query API", + "api_id": "trevorblades", + "org_id": "default", + "use_keyless": true, + "enable_context_vars": true, + "definition": { + "location": "header", + "key": "x-api-version" + }, + "proxy": { + "listen_path": "/trevorblades/", + "target_url": "https://countries.trevorblades.com", + "strip_listen_path": true + } +} +``` + +The target URL should point to a GraphQL upstream although this is a REST proxy. This is important for the feature to work. + +#### Adding versions + +On its own, this isn’t particularly remarkable. To enable GraphQL to REST middleware, modify the Default version like so: + +```json +{ + "name": "Persisted Query API", + "definition": { + "location": "header", + "key": "x-api-version" + }, + ... + "version_data": { + "not_versioned": true, + "default_version": "", + "versions": { + "Default": { + "name": "Default", + "expires": "", + "paths": { + "ignored": [], + "white_list": [], + "black_list": [] + }, + "use_extended_paths": true, + "global_headers": {}, + "global_headers_remove": [], + "global_response_headers": {}, + "global_response_headers_remove": [], + "ignore_endpoint_case": false, + "global_size_limit": 0, + "override_target": "", + "extended_paths": { + "persist_graphql": [ + { + "method": "GET", + "path": "/getContinentByCode", + "operation": "query ($continentCode: ID!) {\n continent(code: $continentCode) {\n code\n name\n countries {\n name\n }\n }\n}", + "variables": { + "continentCode": "EU" + } + } + ] + } + } + } + } +} +``` + +The vital part of this is the `extended_paths.persist_graphql` field. The `persist_graphql` object consists of three fields: + +`method`: The HTTP method used to access that endpoint, in this example, any GET requests to `/getContinentByCode` will be handled by the *persist graphql* middleware + +`path`: The path the middleware listens to + +`operation`: This is the GraphQL operation (`query` in this case) that is sent to the upstream. + +`variables`: A list of variables that should be included in the upstream request. + +If you run a request to your proxy, you should get a response similar to this: + +```json +{ + "data": { + "continent": { + "code": "EU", + "name": "Europe", + "countries": [ + { + "name": "Andorra" + }, + ... + ] + } + } +} +``` + +#### Dynamic variables + +We have seen support for passing static variable values via the API definition, but there will be cases where we want to extract variables from the request header or URL. More information about available request context variables in Tyk can be found [here](/api-management/traffic-transformation/request-context-variables) + +Below is an examples of using an incoming `code` header value as a variable in `persist_graphql` middleware configuration: + +```json +{ + "method": "GET", + "path": "/getCountryByCode", + "operation": "query ($countryCode: ID!) {\n country(code: $countryCode) {\n code\n name\n }\n}", + "variables": { + "countryCode": "$tyk_context.headers_Code" + } +} +``` + +Making a request to that endpoint and providing header `"code": "UK"`, should result in a response similar to this: + +```json +{ + "data": { + "country": { + "code": "UK", + "name": "United Kingdom" + } + } +} +``` + +Similarly, you can also pass variables in the request URL. Modify your `persist_graphql` block to this: + +```json +{ + "method": "GET", + "path": "/getCountryByCode/{countryCode}", + "operation": "query ($countryCode: ID!) {\n country(code: $countryCode) {\n code\n name\n }\n}", + "variables": { + "countryCode": "$path.countryCode" + } +} +``` + +If you now make a request to `/getCountryByCode/NG` you should get a result similar to this: + +```json +{ + "data": { + "country": { + "code": "NG", + "name": "Nigeria" + } + } +} +``` + +## Complexity Limiting + +Depending on the GraphQL schema an operation can cause heavy loads on the upstream by using deeply nested or resource-expensive operations. Tyk offers a solution to this issue by allowing you to control query depth and define its max value in a policy or directly on a key. + +### Deeply nested query + +Even if you have a simple GraphQL schema, that looks like this: + +```graphql +type Query { + continents: [Continent!]! +} + +type Continent { + name: String! + countries: [Country!]! +} + +type Country { + name: String! + continent: Continent! +} +``` + +There is a potential risk, that a consumer will try to send a deeply nested query, that will put a lot of load on your upstream service. An example of such query could be: + +```graphql +query { + continents { + countries { + continent { + countries { + continent { + countries { + continent { + countries { + name + } + } + } + } + } + } + } + } +} +``` + +### Query depth limit +Deeply nested queries can be limited by setting a query depth limitation. The depth of a query is defined by the highest amount of nested selection sets in a query. + +Example for a query depth of `2`: +```json +{ + continents { + name + } +} +``` + +Example for a query depth of `3`: +```json +{ + continents { + countries { + name + } + } +} +``` + +When a GraphQL operation exceeds the query depth limit the consumer will receive an error response (*403 Forbidden*): +```json +{ + "error": "depth limit exceeded" +} +``` + +#### Enable depth limits from the Dashboard + +Query depth limitation can be applied on three different levels: + +* **Key/Policy global limits and quota section. (`Global Limits and Quota`)** The query depth value will be applied on all APIs attached on a Key/Policy. + 1. *Optional:* Configure a Policy from **System Management > Policies > Add Policy**. + 2. From **System Management > Keys > Add Key** select a policy or configure directly for the key. + 3. Select your GraphQL API (marked as *GraphQL*). (if Policy is not applied on Key) + 4. Change the value for **Query depth**, from `Global Limits and Quota` by unchecking the *Unlimited query depth* checkmark and insert the maximum allowed query depth. + +query-depth-limit + +* **API limits and quota. (`Set per API Limits and Quota`)** This value will overwrite any value registered for query depth limitation on global Key/Policy level, and will be applied on all fields for Query and Mutation types defined within the API schema. + 1. *Optional:* Configure a Policy from **System Management > Policies > Add Policy**. + 2. From **System Management > Keys > Add Key** select a policy or configure directly for the key. + 3. Select your GraphQL API (marked as *GraphQL*). (if Policy is not applied on Key) + 4. Enable `Set per API Limits and Quota` section. + 5. Change the value for **Query depth**, from API level, by unchecking the *Unlimited query depth* checkmark and insert the maximum allowed query depth + +query-depth-limit + +* **API per query depth limit. (`Set per query depth limits`)** By setting a query depth limit value on a specific Query/Mutation type field, will take highest priority and all values set on first 2 steps will be overwritten. + 1. *Optional:* Configure a Policy from **System Management > Policies > Add Policy**. + 2. From **System Management > Keys > Add Key** select a policy or configure directly for the key. + 3. Select your GraphQL API (marked as *GraphQL*). (if Policy is not applied on Key) + 4. Enable `Set per query depth limits` section. + 5. Add as many queries you want to apply depth limitation on. + +query-depth-limit + + +#### Enable depth limits using Tyk APIs + +You can set the same query depth limits using the Tyk Gateway API (for open-source users) or Tyk Dashboard API. To make it easier we have [Postman collections](https://www.postman.com/tyk-technologies/workspace/tyk-public-workspace/overview) you can use. + +**Global query depth limit for Key/Policy** + +In the key/policy json you need to make sure this section has your desired `max_query_depth` set: + +```yaml +{... + "rate": 1000, + "per": 60, + "max_query_depth": 5 +...} +``` + +**Per API depth limits** + +In the key/policy json you need to make sure that this section is set correctly: + +```yaml +{ + ... + "access_rights_array": [ + { + "api_name": "trevorblades", + "api_id": "68496692ef5a4cb35a2eac907ec1c1d5", + "versions": [ + "Default" + ], + "allowed_urls": [], + "restricted_types": [], + "allowed_types": [], + "disable_introspection": false, + "limit": { + "rate": 1000, + "per": 60, + "throttle_interval": -1, + "throttle_retry_limit": -1, + "max_query_depth": 3, + "quota_max": -1, + "quota_renews": 0, + "quota_remaining": 0, + "quota_renewal_rate": -1, + "set_by_policy": false + }, + "field_access_rights": [], + "allowance_scope": "" + } + ] + ... +} +``` + +**API per query depth limits** + +If you have more than one query in your schema and you want to set different depth limits for each of those, Tyk also allows you to do that. In this case you need to make sure, that `field_access_rights` per API are set correctly: + +```yaml +{ + ... + "access_rights_array": [ + { + "api_name": "trevorblades", + "api_id": "68496692ef5a4cb35a2eac907ec1c1d5", + "versions": [ + "Default" + ], + "allowed_urls": [], + "restricted_types": [], + "allowed_types": [], + "disable_introspection": false, + "limit": null, + "field_access_rights": [ + { + "type_name": "Query", + "field_name": "continents", + "limits": { + "max_query_depth": 3 + } + }, + { + "type_name": "Query", + "field_name": "countries", + "limits": { + "max_query_depth": 5 + } + } + ], + "allowance_scope":"" + } + ] + ... +} +``` + + + +Setting the depth limit to `-1` in any of the above examples will allow *Unlimited* query depth for your consumers. + + + +## Field Based Permissions + +You may want to allow different consumers access to your GraphQL API without exposing all data to them. So for example this could be a schema for a GraphQL API: + +```graphql +type Query { + accounts: [Account!] +} + +type Account { + owner: String! + number: ID! + balance: Float! +} +``` + +For one type of consumer, it will be fine to query all data the schema exposes, while for another type of consumer it should not be allowed to retrieve the `balance` for example. + +Field access can be restricted by setting up *field based permissions* in a policy or directly on a key. + +When a field is restricted and used in a GraphQL operation, the consumer will receive an error response (*400 Bad Request*): + +```yaml +{ + "errors": [ + { + "message": "field: balance is restricted on type: Account" + } + ] +} +``` +### Field based permissions with the list of allowed types +Field access can be restricted by setting up an allowed types list in a policy or directly on a key. If new fields are added to the GraphQL schema, you don't need to update the field-based permissions. This is because the fields that are not in the list of allowed types are automatically access-restricted. + +First, you need to learn [how to create a security policy with the API](/api-management/gateway-config-managing-classic#secure-an-api) or [how to create an API Key with the API](/api-management/gateway-config-managing-classic#secure-an-api). + +Once you learn how to utilize the API to create a security policy or key, you can use the following snippet: + +```yaml +{ + "access_rights": { + "{API-ID}": { + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "allowed_types": [ + { + "name": "Query", + "fields": ["accounts"] + }, + { + "name": "Account", + "fields": ["owner"] + } + ] + } + } +} +``` +With this configuration, a consumer can only access the field called the `owner`. When any other fields are used in a GraphQL operation, the consumer will receive an error response *(400 Bad Request)*: + +```yaml +{ + "errors": [ + { + "message": "field: balance is restricted on type: Account" + } + ] +} +``` +It's important to note that once you set a list of allowed types, Tyk will use this list to control access rights and disable the list of restricted types. The same behavior will occur if an asterisk operator is used to control access. + +### Allow or restrict all fields with the asterisk operator + +You can allow or restrict all fields of a type by using an asterisk (*) operator. Any new fields of that type will be allowed or blocked by default. For example: + +```yaml +{ + "access_rights": { + "{API-ID}": { + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "allowed_types": [ + { + "name": "Query", + "fields": ["*"] + }, + { + "name": "Account", + "fields": ["*"] + } + ] + } + } +} +``` +With this configuration, the consumers are allowed to access all current and future fields of the `Query` and `Account` types. Please note that the asterisk operator does not work recursively. For example, in the example below, the asterisk operator only allows access to fields of the `Query` type. Fields of the `Account` type remain restricted. + +```yaml +{ + "access_rights": { + "{API-ID}": { + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "allowed_types": [ + { + "name": "Query", + "fields": ["*"] + } + ] + } + } +} +``` +The asterisk operator also works for the list of restricted types: + +```yaml +{ + "access_rights": { + "{API-ID}": { + "api_id": "{API-ID}", + "api_name": "{API-NAME}", + "restricted_types": [ + { + "name": "Query", + "fields": ["accounts"] + }, + { + "name": "Account", + "fields": ["*"] + } + ] + } + } +} +``` + +The configuration above restricts access to all fields of the `Account` type. + +Please note that the list of allowed types overrides the list of restricted types. + + + +### Setup field based permissions in Dashboard + +Restricted and allowed types and fields can also be set up via Tyk Dashboard. + +1. *Optional:* Configure a Policy from **System Management > Policies > Add Policy**. +2. From **System Management > Keys > Add Key** select a policy or configure directly for the key. +3. Select your GraphQL API (marked as *GraphQL*). +4. Enable either **Block list** or **Allow list**. By default, both are disabled. It's not possible to have both enabled at the same time - enabling one switch automatically disables the other. + +#### Block list + +By default all *Types* and *Fields* will be unchecked. By checking a *Type* or *Field* you will disallow to use it for any GraphQL operation associated with the key. + +For example, the settings illustrated below would block the following: +- `code` and `countries` fields in `Continent` type. +- `latt` and `longt` fields in `Coordinates` type. + +field-based-permissions + +#### Allow list + +By default all *Types* and *Fields* will be unchecked. By checking a *Type* or *Field* you will allow it to be used for any GraphQL operation associated with the key. + +For example, the settings illustrated below would only allow the following: +- `code` field in `Continent` type. +- `code` and `name` fields in `Language` type. + +Note that the `Query` type is unchecked, which indicates that all fields in `Query` type are unchecked. Subsequently, you will not be able to run any query. + +field-based-permissions + +## GraphQL Federation + +### Overview + +#### Federation Version Support + +Tyk supports Federation v1 + +#### What is federation? + +Ease-of-use is an important factor when adopting GraphQL either as a provider or a consumer. Modern enterprises have dozens of backend services and need a way to provide a unified interface for querying them. Building a single, monolithic GraphQL service is not the best option. It leads to a lot of dependencies, over-complication and is hard to maintain. + +To remedy this, Tyk, with release 4.0 offers GraphQL federation that allows you to divide GQL implementation across multiple back-end services, while still exposing them all as a single graph for the consumers. + +GraphQL federation flowchart + +#### Subgraphs and supergraphs + +**Subgraph** is a representation of a back-end service and defines a distinct GraphQL schema. It can be queried directly as a separate service or it can be federated into a larger schema of a supergraph. + +**Supergraph** is a composition of several subgraphs that allows the execution of a query across multiple services in the backend. + +#### Subgraphs examples + +**Users** +```graphql +extend type Query { + me: User +} + +type User @key(fields: "id") { + id: ID! + username: String! +} +``` + +**Products** + +```graphql +extend type Query { + topProducts(first: Int = 5): [Product] +} + +extend type Subscription { + updatedPrice: Product! + updateProductPrice(upc: String!): Product! + stock: [Product!] +} + +type Product @key(fields: "upc") { + upc: String! + name: String! + price: Int! + inStock: Int! +} +``` + +**Reviews** + +```graphql +type Review { + body: String! + author: User! @provides(fields: "username") + product: Product! +} + +extend type User @key(fields: "id") { + id: ID! @external + username: String! @external + reviews: [Review] +} + +extend type Product @key(fields: "upc") { + upc: String! @external + reviews: [Review] +} +``` + +#### Subgraph conventions + +- A subgraph can reference a type that is defined by a different subgraph. For example, the Review type defined in the last subgraph includes an `author` field with type `User`, which is defined in a different subgraph. + +- A subgraph can extend a type defined in another subgraph. For example, the Reviews subgraph extends the Product type by adding a `reviews` field to it. + +- A subgraph has to add a `@key` directive to an object’s type definition so that other subgraphs can reference or extend that type. The `@key` directive makes an object type an entity. +#### Supergraph schema + +After creating all the above subgraphs in Tyk, they can be federated in your Tyk Gateway into a single supergraph. The schema of that supergraph will look like this: + +```graphql +type Query { + topProducts(first: Int = 5): [Product] + me: User +} + +type Subscription { + updatedPrice: Product! + updateProductPrice(upc: String!): Product! + stock: [Product!] +} + +type Review { + body: String! + author: User! + product: Product! +} + +type Product { + upc: String! + name: String! + price: Int! + inStock: Int! + reviews: [Review] +} + +type User { + id: ID! + username: String! + reviews: [Review] +} +``` + +#### Creating a subgraph via the Dashboard UI + +1. Log in to the Dashboard and go to APIs > Add New API > Federation > Subgraph. +Add federation subgraph + +2. Choose a name for the subgraph and provide an upstream URL. + + + + + Note + + In case your upstream URL is protected, select **Upstream Protected** and provide authorization details (either Header or Certificate information). + + + +Add upstream URL + +3. Go to Configure API and configure your subgraph just as you would any other API in Tyk. + + + + + Note + + In v4.0, subgraphs will be set to **Internal** by default. + + + +4. Once you have configured all the options, click Save. The subgraph is now visible in the list of APIs. +Subgraph API listing + +#### Creating a supergraph via the Dashboard UI +1. Log in to the Dashboard and go to APIs > Add New API > Federation > Supergraph. +Add supergraph API + +2. In the Details section, select all the subgraphs that will be included in your supergraph. +Select subgraphs + +3. Go to Configure API and configure your supergraph just as you would any other API in Tyk. +4. Once you configure all the options, click Save. The supergraph is now available in your list of APIs. +Supergraph API listing + +#### Defining Headers +In v4.0 you can define global (Supergraph) headers. Global headers are forwarded to all subgraphs that apply to the specific upstream request. + +##### Setting a Global Header + +1. After creating your supergraph, open the API in your Dashboard. +2. From the Subgraphs tab, click Global Headers. +Global Header setup for a supergraph + +3. Enter your header name and value. You can add more headers by clicking Add Headers. +Add further Global headers in a supergraph + +4. Click **Update** to save the header. +5. On the pop-up that is displayed, click Update API. +6. If you want to delete a global header, click the appropriate bin icon for it. +7. You can update your headers by repeating steps 2-5. + +### Entities + +#### Defining the base entity + +- Must be defined with the @key directive. +- The "fields" argument of the @key directive must reference a valid field that can uniquely identify the entity. +- Multiple primary keys are possible. + +An example is provided below: + +**Subgraph 1 (base entity)** + +```graphql +type MyEntity @key(fields: "id") @key(fields: "name") { + id: ID! + name: String! +} +``` + +#### Extending entities + +Entities cannot be shared types (be defined in more than one single subgraph; see **Entity stubs** below). + +The base entity remains unaware of fields added through extension; only the extension itself is aware of them. + +Attempting to extend a non-entity with an extension that includes the @key directive or attempting to extend a base entity with an extension that does not include the @key directive will both result in errors. + +The primary key reference should be listed as a field with the @external directive. + +Below is an example extension for **MyEntity** (which was defined above in **Subgraph 1**): + +**Subgraph 2 (extension):** + +```graphql +extend type MyEntity @key(fields: "id") { + id: ID! @external + newField: String! +} +``` + +#### Entity stubs +If one subgraph references a base entity (an entity defined in another subgraph) without adding new fields, that reference must be declared as a stub. In **federation v1**, stubs appear similar to extensions but do not add any new fields. + +An entity stub contains the minimal amount of information necessary to identify the entity (referencing exactly one of the primary keys from the base entity regardless of whether there are multiple primary keys on the base entity). + +The identifying primary key should feature the @external directive. + +For example, a stub of **MyEntity** (which was defined above in **Subgraph 1**): + +**Subgraph 3 (stub):** + +```graphql +extend type MyEntity @key(fields: "id") { + id: ID! @external +} +``` + +##### What is a shared type? +Types that are identical by name and structure and feature in more than one subgraph are shared types. + +##### Can I extend a shared type? +Subgraphs are normalized before federation. This means you can extend a type if the resolution of the extension after normalization is exactly identical to the resolution of the type after normalization in other subgraphs. + +Unless the resolution of the extension in a single subgraph is exactly identical to all other subgraphs, extension is not possible. + +Here is a valid example where both subgraphs resolve to identical enums after normalization: + +**Subgraph 1:** + +```graphql +enum Example { + A, + B +} + +extend enum Example { + C +} +``` + +**Subgraph 2:** + +```graphql +enum Example { + A, + B, + C +} +``` + +Here, the enum named Example in **Subgraph 1** resolves to be identical to the enum named Example in **Subgraph 2**. + +However, if we were to include **Subgraph 3**, which does not feature the “C” value, the enum is no longer identical in all 3 subgraphs. Consequently, federation would fail. + +**Subgraph 3:** + +```graphql +enum Example { + A, + B +} +``` + + + +### Extension Orphans + +#### What is an extension orphan? + +An extension orphan is an unresolved extension of a type after federation has completed. This will cause federation to fail and produce an error. + +#### How could an extension orphan occur? + +You may extend a type within a subgraph where the base type (the original definition of that type) is in another subgraph. This means that it is only after the creation of the supergraph that it can be determined whether the extension was valid. If the extension was invalid or was otherwise unresolved, an “extension orphan” would remain in the supergraph. + +For example, the type named Person does not need to be defined in **Subgraph 1**, but it must be defined in exactly one subgraph (see **Shared Types**: extension of shared types is not possible, so extending a type that is defined in multiple subgraphs will produce an error). + +**Subgraph 1** + +```graphql +extend type Person { + name: String! +} +``` + +If the type named Person were not defined in exactly one subgraph, federation will fail and produce an error. + + + +## GraphQL WebSockets + +Tyk supports GraphQL via WebSockets using the protocols _graphql-transport-ws_ or _graphql-ws_ between client and Tyk Gateway. + +Before this feature can be used, WebSockets need to be enabled in the Tyk Gateway configuration. To enable it set [http_server_options.enable_websockets](/tyk-oss-gateway/configuration#http_server_options-enable_websockets) to `true` in your `tyk.conf` file. + + + +You can find the full documentation of the _graphql-transport-ws_ protocol itself [here](https://github.com/enisdenjo/graphql-ws/tree/master). + +In order to upgrade the HTTP connection for a GraphQL API to WebSockets by using the _graphql-transport-ws_ protocol, the request should contain following headers: + +``` +Connection: Upgrade +Upgrade: websocket +Sec-WebSocket-Key: +Sec-WebSocket-Version: 13 +Sec-WebSocket-Protocol: graphql-transport-ws +``` + +**Messages** + +The connection needs to be initialised before sending Queries, Mutations, or Subscriptions via WebSockets: + +``` +{ "type": "connection_init" } +``` + +Always send unique IDs for different Queries, Mutations, or Subscriptions. + +For Queries and Mutations, the Tyk Gateway will respond with a `complete` message, including the GraphQL response inside the payload. + +```json +{ "id": "1", "type": "complete" } +``` + +For Subscriptions, the Tyk Gateway will respond with a stream of `next` messages containing the GraphQL response inside the payload until the data stream ends with a `complete` message. It can happen infinitely if desired. + + + +Be aware of those behaviors: + - If no `connection_init` message is sent after 15 seconds after opening, then the connection will be closed. + - If a duplicated ID is used, the connection will be closed. + - If an invalid message type is sent, the connection will be closed. + + + +**Examples** + +**Sending queries** + +``` +{"id":"1","type":"subscribe","payload":{"query":"{ hello }"}} +``` + +**Sending mutations** + +``` +{"id":"2","type":"subscribe","payload":{"query":"mutation SavePing { savePing }"}} +``` + +**Starting and stopping Subscriptions** + +``` +{"id":"3","type":"subscribe","payload":{"query":"subscription { countdown(from:10) }" }} +``` +``` +{"id":"3","type":"complete"} +``` + + +In order to upgrade the HTTP connection for a GraphQL API to WebSockets by using the _graphql-ws_ protocol, the request should contain following headers: + +``` +Connection: Upgrade +Upgrade: websocket +Sec-WebSocket-Key: +Sec-WebSocket-Version: 13 +Sec-WebSocket-Protocol: graphql-ws +``` + +**Messages** + +The connection needs to be initialised before sending Queries, Mutations, or Subscriptions via WebSockets: + +``` +{ "type": "connection_init" } +``` + +Always send unique IDs for different Queries, Mutations, or Subscriptions. + +For Queries and Mutations, the Tyk Gateway will respond with a `complete` message, including the GraphQL response inside the payload. + +For Subscriptions, the Tyk Gateway will respond with a stream of `data` messages containing the GraphQL response inside the payload until the data stream ends with a `complete` message. It can happen infinitely if desired. + +**Examples** + +**Sending queries** + +``` +{"id":"1","type":"start","payload":{"query":"{ hello }"}} +``` + +**Sending mutations** + +``` +{"id":"2","type":"start","payload":{"query":"mutation SavePing { savePing }"}} +``` + +**Starting and stopping Subscriptions** + +``` +{"id":"3","type":"start","payload":{"query":"subscription { countdown(from:10) }" }} +``` +``` +{"id":"3","type":"stop"} +``` + + + +### Upstream connections + +For setting up upstream connections (between Tyk Gateway and Upstream) please refer to the [GraphQL Subscriptions Key Concept](/api-management/graphql#graphql-subscriptions). + + +## GraphQL Subscriptions + +Tyk **natively** supports also GraphQL subscriptions, so you can expose your full range of GQL operations using Tyk Gateway. Subscriptions support was added in `v4.0.0` in which *graphql-ws* protocol support was introduced. + +With the release of Tyk `v4.3.0` the number of supported subscription protocols has been extended. + +In Tyk subscriptions are using the [WebSocket transport](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) for connections between the client and Gateway. For connections between Gateway and upstream WebSockets or [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) can be used. + +### Supported transports and protocols + +| Transport | Protocol | +| :------------ | :-------------------------------------------------------------------------------------------------------------------------- | +| WebSockets | [graphql-ws](http://github.com/apollographql/subscriptions-transport-ws) (default, no longer maintained) | +| WebSockets | [graphql-transport-ws](http://github.com/enisdenjo/graphql-ws) | +| HTTP | [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) | + +#### Setting up subscription types via API definition +Subscription types or subscription transports/protocols are set inside the graphql section of the API definition. + +Depending on whether you want to configure GraphQL proxy-only, UDG, or GraphQL Federation there are different places for the configuration option. + +The values for subscription types are the same on all API types: +- `graphql-ws` +- `graphql-transport-ws` +- `sse` (Server-Sent Events) + +##### HTTP method for Server-Sent Event subscription + +When using `subscription_type=sse`, Tyk will use the HTTP `GET` method to subscribe to the upstream service. For some use cases, for example, to support larger subscription payloads or to increase security by keeping the subscription payload out of server logs, the upstream requires HTTP `POST`. In Tyk 5.9.0, we have added `POST` support for SSE with the introduction of the boolean `use_sse_post` option, which is only relevant if `subscription_type=sse`. + +```json +{ + "graphql": { + "proxy": { + "subscription_type": "sse", + "sse_use_post": true + } + } +} +``` + +If you need to use HTTP `GET` then you can omit `sse_use_post` or set it to `false`. + + +##### GraphQL Proxy + +``` +{ + ..., + "graphql": { + ..., + "proxy": { + ..., + "subscription_type": "graphql-ws" + } + } +} +``` + +##### Universal Data Graph + + +In UDG, the `subscription_type` setting only applies to data sources of kind **GraphQL**. It is not supported for REST kind data sources. REST data sources can only resolve Query and Mutation operations. If your upstream exposes subscriptions via SSE, you must configure it as a GraphQL data source. + + +``` +{ + ..., + "graphql": { + ..., + "engine": { + ..., + "data_sources": [ + ..., + { + "kind": "GraphQL", + ..., + "subscription_type": "sse" + } + ] + } + } +} +``` + +**Example: Configuring an SSE subscription with a GraphQL data source in UDG** + +The following example shows a complete data source configuration for a GraphQL upstream that uses SSE for subscriptions. The data source is of kind `GraphQL` and the `subscription_type` is set to `sse`: + +```json +{ + "graphql": { + "engine": { + "data_sources": [ + { + "kind": "GraphQL", + "name": "my-graphql-sse-upstream", + "internal": false, + "root_fields": [ + { + "type": "Subscription", + "fields": ["onMessage"] + } + ], + "config": { + "url": "https://my-graphql-upstream.example.com/graphql", + "method": "POST", + "headers": {} + }, + "subscription_type": "sse" + } + ] + } + } +} +``` + +In this configuration: +- `kind` must be `"GraphQL"` — SSE subscriptions are not available for REST data sources. +- `root_fields` maps the `Subscription` type and its fields to this data source. +- `subscription_type` is set to `"sse"` to use Server-Sent Events for the upstream connection. +- Use `"graphql-ws"` or `"graphql-transport-ws"` instead of `"sse"` if your upstream uses WebSockets. + +##### Federation + +``` +{ + ..., + "graphql": { + ..., + "supergraph": { + ..., + "subgraphs": [ + ..., + { + ..., + "subscription_type": "graphql-transport-ws" + } + ] + } + } +} +``` + + + +If the upstream subscription GraphQL API is protected please enable the authentication via query params to pass the header through. + + + +There is no need to enable subscriptions separately. They are supported alongside GraphQL as a standard. The only requirement for subscriptions to work is to [enable WebSockets](/api-management/graphql#graphql-websockets) in your Tyk Gateway configuration file. + +Here's a general sequence diagram showing how subscriptions in Tyk work exactly: + +Tyk Subscriptions workflow + +## GraphQL playground + +When you are creating or editing your GraphQL API, any change you make can be tested using Tyk Dashboard built-in GraphiQL Playground. + +Playground + +At the top of the Playground itself, you can switch between Dark and Light theme using the `Set theme` dropdown. + +There's also a built in `Explorer` to help with query building and a `Prettify` button that helps to make the typed out operation easier to read. + +The GraphiQL try-out playground comes with a series of features by default, which can be very useful while configuring the API: + 1. Syntax highlighting. + 2. Intelligent type ahead of fields, arguments, types, and more. + 3. Real-time error highlighting and reporting for queries and variables. + 4. Automatic query and variables completion. + 5. Automatically adds required fields to queries. + 6. Documentation explorer, search, with markdown support. + 7. Query History using local storage + 8. Run and inspect query results using any promise that resolves JSON results. 9. HTTPS or WSS not required. + 10. Supports full GraphQL Language Specification: Queries, Mutations, Subscriptions, Fragments, Unions, directives, multiple operations per query, etc + +### GraphQL Playgrounds in Tyk + +Tyk offers you two types of Playgrounds, depending on who should be authorized to use them. + +* **Playground** tab in `API Designer`, that's only accessible via Tyk Dashboard and is always enabled. You need to log into the Tyk Dashboard to be able to use it. +* **Public Playground** that you can enable for any GraphQL API and that is accessible for any consumer interacting with your GQL API. This playground will follow all security rules you set for your GQL API - authentication, authorization, etc. + + + + + The Public Playground relies on assets in the `playground` folder under [template_path](/tyk-oss-gateway/configuration#template_path) (default: `/opt/tyk-gateway/templates`). + If you change this path, be sure to copy the `playground` folder to the new location to preserve functionality. + + + +#### Enabling Public GraphQL Playground + + + + +To enable a Public GraphQL Playground for one of your GQL APIs follow these few simple steps: + +1. Navigate to `Core Settings` tab in `API designer` +2. Change the setting in `Enable API Playground` section. +3. Provide `Playground path`. By default, this path is set to `/playground` but you can change it. + +Headers + +Your `Public Playground` will be available at `http://{API-URL}/playground`. + + + + +To enable Public GraphQL Playground using just Tyk API definition, you need to set the following: + +```bash +... +"graphql": { + "playground": { + "enabled": true, + "path": "/playground" + } + } +... +``` + +You can choose yourself the `path` name. + +Your `Public Playground` will be available at `http://{API-URL}/playground`. + + + + +#### Query variables + +You can pass query variables in two different ways, both are fully supported in Tyk Dashboard. + +##### Using inline arguments in GraphiQL Playground + +A query or mutation string in this case, would be written like in the example below and there would be no other requirements for executing an operation like this: + +```graphql +mutation createUser { + createUser(input: { + username: "test", + email: "test@test.cz", + phone: "479332973", + firstName: "David", + lastName: "Test" + }) { + user { + id + username + email + phone + firstName + lastName + } + } +} +``` + +##### Using query variables in GraphiQL Playground + +For complex sets of variables, you might want to split the above example into two parts: GQL operation and variables. + +The operation itself would change to: + +```graphql +mutation createUser($input: CreateUserInput!) { + createUser(input: $input) { + user { + id + username + email + phone + firstName + lastName + } + } +} +``` + +The values for variables would need be provided in the `Query variables` section of the Playground like this: + +```graphql +{ + "input": { + "username": "test", + "email": "test@test.cz", + "phone": "479332973", + "firstName": "David", + "lastName": "Test" + } +} +``` + +#### Headers + +Debugging a GraphQL API might require additional headers to be passed to the requests while playing with the GraphiQL interface (i.e. `Authorization` header in case of Authentication Token protection over the API). This can be done using the dedicated headers tab in the Graphiql IDE. + +Headers + +You can also [forward headers](/api-management/graphql#graphql-apis-headers) from your client request to the upstream data sources. + + +#### Logs + + + +GraphQL request logs described below are **only available in Tyk Dashboard**. + + + +Besides the results displayed in the GraphiQL playground, Tyk also provides you with a full list of logs of the triggered request, which can help a lot when debugging the API functionality. + +Logs + +The Request Logs can be seen under the playground itself. When no logs are present, there will be no option to expand the logs, and the filter buttons (top right) will be disabled: + +Logs Bar + +After creating and sending a query, the logs will automatically expand, and the filter buttons will display the number of logs for its respective level (category). + +Logs table + +##### Contents of the logs + +There are four levels (categories) of logs: `Info`, `Debug`, `Warning`, and `Error`, and each log belongs to one of these levels. + +The first column of the table displays the color-coded `“level”` property of the log. A log should never be absent of a level. The second column displays the log `“msg”` (message) property, if any. The third column displays the `“mw” `(middleware) property, if any. + +##### Expansion/collapse of Request Logs + +The Request Logs can be expanded or collapsed, using the chevron on the left side to toggle these states. + +##### Filter buttons and states + +Filter buttons have two states: active and inactive; the default of which is active. A solid background color of the button indicates that a filter is active. + +In the below picture, the `info` and `error` filters buttons are both active. If there are no logs for a particular level of log, the button will appear as a gray and disabled, as shown by the `Warning` filter button. + +Logs navigation + +Here's an example where there is at least one log, but all the filter buttons are in the inactive state. If the cursor (not shown) hovers over an inactive filter button, the button background will change to solid, and the tooltip will display `“Show”`. + +If all filter buttons are inactive, a message asking whether the user would like to reset all filters will display. Clicking this text will activate all available filters. + +Logs empty + +## Migrating to 3.2 + +As of 3.2 GraphQL schema for Tyk API definitions (i.e `api_definition.graphql`) changed significantly, hence GraphQL API definitions created in previous beta versions are not supported via the UI and need to go through a manual migration. + + + +Before you continue, we strongly advise to simply create a new API and avoid migration of the API definition. You'll achieve results faster and can avoid typos and errors that happens with the manual migration. + + + + + +Old API definitions will continue to work for the Tyk Gateway + + + + +### The changes +- To improve performance now a single Data Source can be used to link to multiple fields instead of having an independent data source for every field hence `graphql.type_field_configurations` is now obsolete and new data sources can be defined under `graphql.engine.data_sources` (see example below). + +- Data Source kind are `REST` or `GraphQL` regardless of your API being internal or not. + +- In case of internal APIs that are accessed via `tyk://`scheme, the `graphql.engine.data_sources[n].internal` property is set to true. + +- Each dataSources needs to be defined with a unique name `graphql.engine.data_sources[n].name`. + +- Each field connected to the data source is expected to be configured for mapping under `graphql.engine.field_configs` regardless of it requiring mapping or not. + +- It is important that all new GraphQL APIs have the version `graphql.version` property set to `2`. + +### Examples + +#### Old Data Source Config + +```json +"type_field_configurations": [ + { + "type_name": "Query", + "field_name": "pet", + "mapping": { + "disabled": true, + "path": "" + }, + "data_source": { + "kind": "HTTPJSONDataSource", + "data_source_config": { + "url": "https://petstore.swagger.io/v2/pet/{{.arguments.id}}", + "method": "GET", + "body": "", + "headers": [], + "default_type_name": "Pet", + "status_code_type_name_mappings": [ + { + "status_code": 200, + "type_name": "" + } + ] + } + } + }, + { + "type_name": "Query", + "field_name": "countries", + "mapping": { + "disabled": false, + "path": "countries" + }, + "data_source": { + "kind": "GraphQLDataSource", + "data_source_config": { + "url": "https://countries.trevorblades.com", + "method": "POST" + } + } + }, +] +``` + +#### New Data Source Config + +```json +"engine": { + "field_configs": [ + { + "type_name": "Query", + "field_name": "pet", + "disable_default_mapping": true, + "path": [ + "" + ] + }, + { + "type_name": "Query", + "field_name": "countries", + "disable_default_mapping": false, + "path": [ + "countries" + ] + }, + ], + "data_sources": [ + { + "kind": "REST", + "name": "PetStore Data Source", + "internal": false, + "root_fields": [ + { + "type": "Query", + "fields": [ + "pet" + ] + } + ], + "config": { + "url": "https://petstore.swagger.io/v2/pet/{{.arguments.id}}", + "method": "GET", + "body": "", + "headers": {}, + } + }, + { + "kind": "GraphQL", + "name": "Countries Data Source", + "internal": false, + "root_fields": [ + { + "type": "Query", + "fields": [ + "countries" + ] + } + ], + "config": { + "url": "https://countries.trevorblades.com", + "method": "POST", + "body": "" + } + } + ] +}, +``` + +#### Example of new graphql definition + +``` json +"graphql" : { + "schema": "type Mutation {\n addPet(name: String, status: String): Pet\n}\n\ntype Pet {\n id: Int\n name: String\n status: String\n}\n\ntype Query {\n default: String\n}\n", + "enabled": true, + "engine": { + "field_configs": [ + { + "type_name": "Mutation", + "field_name": "addPet", + "disable_default_mapping": true, + "path": [""] + }, + { + "type_name": "Pet", + "field_name": "id", + "disable_default_mapping": true, + "path": [""] + }, + { + "type_name": "Query", + "field_name": "default", + "disable_default_mapping": false, + "path": ["default"] + } + ], + "data_sources": [ + { + "kind": "REST", + "name": "Petstore", + "internal": false, + "root_fields": [ + { + "type": "Mutation", + "fields": ["addPet"] + } + ], + "config": { + "url": "https://petstore.swagger.io/v2/pet", + "method": "POST", + "body": "{\n \"name\": \"{{ .arguments.name }}\",\n \"status\": \"{{ .arguments.status }}\"\n}", + "headers": { + "qa": "{{ .request.header.qa }}", + "test": "data" + }, + } + }, + { + "kind": "REST", + "name": "Local Data Source", + "internal": false, + "root_fields": [ + { + "type": "Pet", + "fields": ["id"] + } + ], + "config": { + "url": "http://localhost:90909/graphql", + "method": "HEAD", + "body": "", + "headers": {}, + } + }, + { + "kind": "GraphQL", + "name": "asd", + "internal": false, + "root_fields": [ + { + "type": "Query", + "fields": ["default"] + } + ], + "config": { + "url": "http://localhost:8200/{{.arguments.id}}", + "method": "POST", + } + } + ] + }, + "execution_mode": "executionEngine", + "version": "2", + "playground": { + "enabled": false, + "path": "" + }, + "last_schema_update": "2021-02-16T15:05:27.454+05:30" +} +``` + diff --git a/api-management/graphql/graphql-schema-types.mdx b/api-management/graphql/graphql-schema-types.mdx new file mode 100644 index 0000000000..47489e40c7 --- /dev/null +++ b/api-management/graphql/graphql-schema-types.mdx @@ -0,0 +1,319 @@ +--- +title: "GraphQL Schema Types" +description: "Understanding GraphQL schema types and how they work with Tyk" +keywords: "GraphQL, Schema, Types, Custom Scalars" +sidebarTitle: "Schema Types" +--- + +## Introduction + +When working with GraphQL APIs in Tyk, understanding the different schema types is important for proper API design and implementation. This page covers the standard types supported by Tyk, custom scalar types, and best practices for type definitions. + +## Standard GraphQL Types + +Tyk supports all standard GraphQL types as defined in the [GraphQL specification](https://spec.graphql.org/October2021/): + +### Scalar Types + +Scalar types are the fundamental building blocks of your schema, representing actual data values. + +- `Int`: 32-bit integer +- `Float`: Double-precision floating-point value +- `String`: UTF-8 character sequence +- `Boolean`: `true` or `false` +- `ID`: Unique identifier, serialized as a String + +### Object Types + +Object types define collections of fields and are the most common type in GraphQL schemas. They model complex entities and can include fields of any type, enabling rich, nested data structures. + +```graphql +type User { + id: ID! + name: String! + age: Int + isActive: Boolean +} +``` + +### Interface Types + +Interfaces are abstract types that define a set of fields that implementing object types must include. + +```graphql +interface Node { + id: ID! +} + +type User implements Node { + id: ID! + name: String! + email: String +} + +type Product implements Node { + id: ID! + name: String! + price: Float! +} +``` + +### Union Types + +Unions represent an object that could be one of several object types, but don't share common fields like interfaces. + +```graphql +union SearchResult = User | Product | Article + +type Query { + search(term: String!): [SearchResult!]! +} +``` + +When querying a union, you need to use inline fragments: + +```graphql +{ + search(term: "example") { + ... on User { id name } + ... on Product { id price } + ... on Article { title content } + } +} +``` + +### Input Types + +Input types are special object types used specifically for arguments. They make complex operations more manageable by grouping related arguments, particularly useful for mutations. + +```graphql +input UserInput { + name: String! + age: Int + email: String! +} +``` + +### Enum Types + +Enums restrict fields to specific allowed values, improving type safety and self-documentation in your API. + +```graphql +enum UserRole { + ADMIN + EDITOR + VIEWER +} +``` + +### List and Non-Null Types +GraphQL provides two type modifiers: + +- Non-Null (`!`): Indicates that the value cannot be null +- List (`[]`): Indicates that the value is an array of the specified type + +These modifiers can be combined: + +```graphql +type Collection { + requiredItemsRequired: [Item!]! # Non-null list of non-null items + optionalItemsRequired: [String!] # Nullable list of non-null items + requiredItemsOptional: [String]! # Non-null list of nullable items + optionalItemsOptional: [String] # Nullable list of nullable items + nestedRequiredItemsRequired: [[String!]!]! # nested non-nullable list in non-nullable list with non-null items +} +``` + +## Custom Scalar Types + +### Implementation in Tyk +Tyk supports custom scalar types through the underlying GraphQL engine. While Tyk passes custom scalar values through its system, the actual validation, parsing, and serialization of these values should be implemented in your upstream service. + +### Using the @specifiedBy Directive +The `@specifiedBy` directive allows you to provide a URL to the specification for a custom scalar type: + +```graphql +scalar DateTime @specifiedBy(url: "https://tools.ietf.org/html/rfc3339") +scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") +``` + +### Common Custom Scalar Types + +#### JSON Scalar + +The JSON scalar handles arbitrary JSON data, useful for dynamic structures without defining every possible field. + +```graphql +scalar JSON + +type Configuration { + settings: JSON +} +``` + +#### Long/BigInt +```graphql +scalar Long + +type Transaction { + amount: Long + timestamp: Long +} +``` + + + +**Note:** + +According to the [GraphQL spec](https://spec.graphql.org/), `Long/BigInt` values must be serialized as **strings** (IEEE standard). Some libraries incorrectly serialize them as numbers, which can lead to compatibility issues. + +Tyk’s GraphQL engine expects `Long` values to be serialized as strings to ensure interoperability. + + + +**Example:** + +```json +{ + "amount": "9223372036854775807", + "timestamp": "1690991344000" +} +``` + +#### DateTime +```graphql +scalar DateTime + +type Event { + startTime: DateTime + endTime: DateTime +} +``` + +## GraphQL Federation Types + +Tyk supports [GraphQL Federation v1](/api-management/graphql#graphql-federation) for building unified APIs across multiple services. + +### Entity Types with @key + +The @key directive is fundamental to federation. It identifies fields that can be used to uniquely identify entities across services: + +```graphql +# In the Users service +type User @key(fields: "id") { + id: ID! + name: String! + email: String! +} + +# In the Orders service +type User @key(fields: "id") { + id: ID! + orders: [Order!]! +} +``` + +In this example: +- The `User` type is defined in both services +- The `@key` directive specifies that `id` is the field that uniquely identifies a User +- The Users service owns the core User fields (id, name, email) +- The Orders service extends User to add the orders field + +When a client queries for a User with their orders, Tyk's federation engine knows how to fetch the core User data from the Users service and the orders data from the Orders service, then combine them into a single response. + +### Extended Types with @external + +The `@external` directive explicitly indicates that a type extends an entity defined in another service: + +```graphql +# In a service extending the User type +extend type User @key(fields: "id") { + id: ID! @external + reviews: [Review!]! +} +``` + +In this example: +- The `extend` keyword and `@external` directive indicate this is extending the User type +- The `@external` directive on the `id` field indicates this field is defined in another service +- This service adds the `reviews` field to the User type + +## Best Practices + +### Type Definition Best Practices + +1. **Use Non-Nullable Fields Wisely** + Consider future API evolution when deciding which fields should be non-nullable. + +2. **Consistent Naming Conventions** + Use PascalCase for type names, camelCase for field names, and ALL_CAPS for enum values. + +3. **Input Type Naming** + Name input types clearly to indicate their purpose (e.g., CreateUserInput, UpdateUserInput). + +4. **Scalar Type Usage** + Choose appropriate scalar types based on semantic meaning, not just data format. + +5. **Interface and Union Usage** + Use interfaces for shared fields and unions for different types that might be returned from the same field. + +### Limitations and Considerations + +1. **Custom Scalar Validation** + Ensure your upstream service properly validates custom scalar values. + +2. **Schema Evolution** + Start with nullable fields when unsure about requirements and use deprecation before removing fields. + +3. **Performance Considerations** + Limit nesting depth in types and consider pagination for list fields. + +## Type System Example + +```graphql +# Custom scalars +scalar DateTime @specifiedBy(url: "https://tools.ietf.org/html/rfc3339") +scalar JSON + +# Interfaces +interface Node { + id: ID! +} + +# Enums +enum Status { + ACTIVE + PENDING + INACTIVE +} + +# Input types +input ProductInput { + name: String! + description: String + price: Float! + metadata: JSON +} + +# Object types +type Product implements Node { + id: ID! + name: String! + description: String + price: Float! + status: Status! + createdAt: DateTime! + metadata: JSON +} + +# Query and Mutation types +type Query { + getProduct(id: ID!): Product + listProducts(status: Status): [Product!]! +} + +type Mutation { + createProduct(input: ProductInput!): Product! + updateProduct(id: ID!, input: ProductInput!): Product! +} +``` \ No newline at end of file diff --git a/api-management/implement-tls.mdx b/api-management/implement-tls.mdx new file mode 100644 index 0000000000..a18bcf6c2a --- /dev/null +++ b/api-management/implement-tls.mdx @@ -0,0 +1,731 @@ +--- +title: "Implementing TLS with Tyk" +description: "Secure access to APIs and Tyk components" +keywords: "Authentication, Tyk Authentication, Mutual TLS, mTLS, Client mTLS" +sidebarTitle: "Implement TLS" +--- + +## Introduction + +Transport Layer Security (TLS) and Mutual TLS (mTLS) can be used to secure access to Tyk components and to the hosted APIs that provide access to your upstream services. Tyk can also connect securely to those upstream services using TLS and mTLS. + +For more details on security protocols and public key cryptography, see our [TLS and Certificates](/api-management/certificates) guide. + +## Tyk Gateway as a TLS Server (Inbound Connections) + +This section covers scenarios in which the Tyk Gateway terminates TLS connections from an external client. The Gateway has two distinct interfaces where it acts as a server: + +1. *Control API Interface*: The [Tyk Gateway API](/tyk-gateway-api) used for managing the Gateway (adding APIs, hot reloads, etc.) +2. *Hosted API Interface*: The public-facing interface where API clients connect to access your APIs + +### Basic Server Configuration + +Configure TLS for these interfaces in the Gateway config file (tyk.conf) or environment variables: + +```json +{ + "http_server_options": { + "use_ssl": true, + "server_name": "api.example.com" + }, + "listen_port": 443 +} +``` + +| Parameter | Description | +| --------- | ----------- | +| `use_ssl` | Enables the use of TLS for the Gateway server; if set to true then client requests to the control and hosted APIs must use HTTPS | +| `server_name` | (optional) If provided this will be used to identify the Gateway for SNI (Server Name Indication) in the TLS handshake | +| `listen_port` | Set the port on which Tyk will listen; typically set to 443 for HTTPS | + +Optionally, expose the Control API on a separate hostname from Hosted APIs. This is required to [secure the Control API with mTLS](/api-management/implement-tls#secure-the-gateway-control-api-with-mtls). See the [Planning For Production](/planning-for-production#change-your-control-port) guide for details. + +#### Configure Server Certificates + +In the TLS handshake, Tyk Gateway proves its identity using its private key and certificate, known as the server certificate pair. + +Server certificate pairs are registered in the Gateway config file (`tyk.conf`) or equivalent environment variable: + +```json +{ + "http_server_options": { + "ssl_certificates": ["server-cert-id", "/path/to/server-cert.pem"] + } +} +``` + +| Parameter | Description | +| --------- | ----------- | +| `ssl_certificates` | An array of server certificate pairs, each specified as a [certificate reference](/api-management/certificates#certificate-management) | + +During the TLS handshake, Tyk Gateway inspects the hostname sent in the Server Name Indication (SNI) extension of the client request. It then searches its list of registered server certificates to find one with a matching Subject Alternative Name (SAN). The search prioritises certificates assigned to specific [API custom domains](/api-management/implement-tls#configure-server-certificates-for-api-custom-domains) before checking globally configured certificates. If a matching certificate pair is found, it is presented to the client. If no specific match is found, the Gateway will use the first certificate loaded from its configuration as the default to complete the handshake. + + +  The referenced server certificate pairs are used to verify Tyk's identity in the TLS handshake and so must contain both the certificate (public key) and private key. + + +#### Configure Server Certificates for API Custom Domains + +APIs hosted on Tyk can be exposed on individual hostnames (custom domains), for which separate server certificate pairs can be registered. + +Tyk Gateway must first be configured to allow the use of custom domains in the Gateway config file (`tyk.conf`) or equivalent environment variables: + +```json +{ + "enable_custom_domains": true +} +``` +| Parameter | Description | +| --------- | ----------- | +| `enable_custom_domains | Allow the use of custom domains for Hosted APIs | + +Within the API definition, the custom domain is configured in the Tyk Vendor Extension: + +```yaml + server: + customDomain: + enabled: true + name: api.example.com + certificates: + - /path/to/server-certificate-pair.pem + - server-cert-pair-id +``` + +| Parameter | Description | +| --------- | ----------- | +| `enabled` | Set to `true` to use a dedicated domain for this API | +| `name` | The domain name for which Tyk should provide server certificates | +| `certificates` | The server certificate pairs to associate with the API, each specified as a [certificate reference](/api-management/certificates#certificate-management) | + +Note: If you are using Tyk Classic APIs, the equivalent configuration fields are `domain_disabled`, `domain`, and `certificate_pairs[]` in the root of the API definition. + +### Secure the Gateway Control API with mTLS + +With [TLS enabled](/api-management/implement-tls#basic-server-configuration) for the Gateway you can enforce mutual TLS for requests to the **Control API** if it is [exposed on a different hostname](/planning-for-production#change-your-control-port) by setting the following options in the Gateway config file (`tyk.conf`) or equivalent environment variables: + +```json +{ + "security": { + "control_api_use_mutual_tls": true, + "certificates": { + "control_api": ["/path/to/ca-cert.pem", "control-api-cert-id"] + } + }, + "control_api_hostname": "gw-control.example.com", + "control_api_port": 443 +} +``` + +| Parameter | Description | +| --------- | ----------- | +| `security.control_api_use_mutual_tls` | Require clients (e.g. Tyk Dashboard) to present a valid certificate when connecting to the Gateway API | +| `security.certificates.control_api` | An array of CA certificates, each specified as a [certificate reference](/api-management/certificates#certificate-management) | +| `control_api_hostname` | The hostname to which you want to bind the Control API | +| `control_api_port` | The HTTP port to which you want to bind the Control API | + + +  The referenced certificates are used to verify the client's identity in the mTLS handshake and so will typically be the CA certificates that can be used to validate the client certificate. + + +### Secure Hosted APIs with mTLS + +Tyk takes a very flexible approach to mutual TLS for securing access to hosted APIs. + +With [TLS enabled](/api-management/implement-tls#basic-server-configuration) for the Gateway you can selectively enforce mTLS per API. + +For each API, you can determine a list of one or more *allowed* client certificates. Once the mTLS handshake completes, Tyk will check the client's certificate against the allow list for that API. The request will be authorized only if the client certificate matches. + +For complete flexibility, Tyk offers two different types of allow list: + +- a static list stored in the API definition (historically referred to by Tyk as static mTLS) +- a dynamic list in the Redis/temporal storage (historically referred to by Tyk as dynamic mTLS) + +Either approach can be combined with any authentication method or used alone to secure the API. + + + Tyk must be the server that terminates the TLS request from the client. If a load balancer sits between the client and Tyk and terminates TLS then the client certificate won't be presented to Tyk for verification. + + In this scenario, neither of Tyk's static nor dynamic allow lists can be used to secure your APIs. You can, of course, secure the connection between clients and your load balancer using mTLS but this is outside the scope of Tyk. + + +#### Using a static client certificate allow list + +The +Mutual TLS with a static allow list will be enforced for requests to an API if the Tyk Vendor Extension contains the following configuration: + +```yaml + server: + clientCertificates: + enabled: true + allowlist: + - client-cert-id1 + - client-cert-id2 +``` + +| Parameter | Description | +| --------- | ----------- | +| `clientCertificates.enabled`   | Set this to true to enable mTLS with a static allow list | +| `clientCertificates.allowlist` | A list of [certificate references](/api-management/certificates#certificate-management) for the client certificates that are authorized to access the API | + + +  If using Tyk Classic, the fields are `use_mutual_tls_auth` and `client_certificates`. + + +You can upload an issuing certificate (e.g., a CA certificate) to the Tyk Certificate Store and include it in the static allow list. Tyk will then validate client certificates signed by that authority. + +From Tyk 5.12.0, we added support for [Certificate-Token Binding](/api-management/authentication/bearer-token#client-certificate-token-binding) when using the Auth Token (also known as Bearer Token) method for authentication. This adds an additional layer of security on top of the static mTLS list by linking one or more client certificates on the list to the Authentication Token. This prevents an Auth Token from being used with certificates belonging to a different API client. + +#### Using a dynamic client certificate allow list + +Tyk's powerful dynamic client certificate allow list provides an alternative to declaring the allow list in the API definition. This is a separate authentication method historically called Dynamic mTLS but, from Tyk 5.12, referred to as [Certificate Auth](/api-management/authentication/certificate-auth). Changes were implemented with the introduction of Certificate Auth to address some [limitations](/api-management/implement-tls#legacy-dynamic-mtls-mode) in Dynamic mTLS. + +The allow list is described as *dynamic* because it is stored in Redis rather than the API definition. The entries in the list can thus be managed without making any changes to the API definition, no need to reload the Gateway to make changes, unlike the static allow list described [above](/api-management/implement-tls#using-a-static-client-certificate-allow-list). + +When a request is made to an API secured with mTLS and a dynamic allow list, the client certificate in the request is used to locate a matching session in Redis. If a match is found, the request is authorized. + + +  From Tyk 5.12, this is called Certificate Authentication. Please see the [dedicated section](/api-management/authentication/certificate-auth) for more details. + + +Prior to Tyk 5.12, Dynamic mTLS will be enforced for requests to an API if the Tyk Vendor Extension contains the following configuration: + +```yaml + server: + authentication: + enabled: true + securitySchemes: + authToken: + enabled: true + enableClientCertificate: true +``` + +| Parameter | Description | +| --------- | ----------- | +| `authToken.enableClientCertificate` | Set this to true to enable mTLS with a dynamic allow list (prior to Tyk 5.12) | + + +  The `enableClientCertificate` field will still work in Tyk 5.12 but will be deprecated in a future release. Users are encouraged to adopt Certificate Authentication. + +  If using Tyk Classic, the equivalent field is `auth_configs.authToken.useCertificate`. + + +**Adding a certificate to the dynamic allow list** + +The dynamic allow list comprises session objects in the Gateway temporal storage (typically Redis) linked to the authorized client certificates. + +The certificates must first be registered with the Tyk Certificate Store and then a session (key) created with a link to the allocated certificate ID. + +For full details, see the [Certificate Auth](/api-management/authentication/certificate-auth#registering-certificate-authentication-user-credentials) section. + +#### Legacy Dynamic mTLS mode + +Prior to Tyk 5.12, this method was called Dynamic mTLS and permitted authentication using either the client certificate or an Auth Token matching the Redis key for the session state object. If the token was presented, then Tyk treated the certificate as optional and did not enforce the mutual TLS handshake. + +A key concept of the [mTLS handshake](/api-management/certificates#mutual-tls-mtls--two-way-authentication) is that it ensures that the client has possession of the private key. Possession of the certificate (i.e., the public key) does not confirm the client's identity (since this is by its nature public information). + +With the Dynamic API, the token can be constructed if you have knowledge of the certificate ID/hash and Organization ID - so possession of the token does not guarantee possession of the private key and hence is not equivalent to completion of the mTLS handshake (which requires possession of the private key). + +From Tyk 5.12, the option to authenticate using only the token has been placed behind a Gateway configuration option [`allow_unsafe_dynamic_mtls_token`](/tyk-oss-gateway/configuration#security-allow_unsafe_dynamic_mtls_token). If this is not set (the default behavior), then requests presenting just the token will be rejected; the certificate must be presented and the mTLS handshake is enforced. + +The legacy behavior is maintained for existing Tyk users who require this method for testing, but it is strongly discouraged; hence, the default setting is to restrict authentication to the certificate only. + + +  In Tyk 5.12, the option to use only the token to authenticate has been placed behind the Gateway configuration option `allow_unsafe_dynamic_mtls_token`. + + +### Advanced Server-Side TLS Settings + +#### Controlling TLS Version and Cipher Suites + +The Transport Layer Security specification has evolved through multiple versions, the most recent being TLS 1.3 which is the fastest and most secure. TLS 1.0 and 1.1 have been deprecated due to vulnerabilities and should no longer be used; Tyk has not supported these since Tyk 5.6. + +Whilst TLS 1.3 is often recommended, some legacy systems continue to use TLS 1.2, so Tyk has full support including the specification of allowed ciphers. When using TLS 1.3 the standard only allows secure ciphers and these are automatically negotiated during the handshake. + +You can configure Tyk Gateway with a minimum and maximum TLS version, and select the cipher suites to be used with TLS 1.2 using the `http_server_options` configuration: + +```json +{ + "http_server_options": { + "min_version": 771, // TLS 1.2 + "max_version": 772, // TLS 1.3 + "ssl_ciphers": ["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] + } +} +``` + +| Parameter | Description | +| --------- | ----------- | +| `min_version` and `max_version` | Can be configured with the values 771 (representing TLS 1.2) or 772 (representing TLS 1.3) | +| `ssl_ciphers` | Indicates the allowed cipher suites when the request uses TLS 1.2 and is used to restrict the use of unsafe ciphers; the options are taken from the [Go TLS package](https://pkg.go.dev/crypto/tls#pkg-constants) documentation | + + +  Tyk uses Golang libraries to provide TLS functionality, so the range of supported TLS versions depends on the underlying library. Support for TLS 1.0 and 1.1 was removed in Go 1.22 (which was adopted in Tyk 5.3.6/5.6.0), so these are no longer supported by Tyk. + + The default minimum and maximum TLS versions are inherited from the Golang library and thus are presently 1.2 and 1.3 respectively. + + Prior to Tyk 5.8.14/5.13.0 the default for the maximum TLS version was set to 1.2. + + +#### Supporting HTTP/2 + +Tyk supports the use of [HTTP/2](https://datatracker.ietf.org/doc/html/rfc9113) for hosted APIs with compatible clients by configuring: + +```json +{ + "http_server_options": { + "enable_http2": true + } +} +``` + +#### Skipping Client CA Announcement + +When using mTLS, the server (Tyk Gateway) announces to the client which Certificate Authorities it trusts, so the client can ensure it presents a certificate signed by a mutually trusted authority. If there are many Certificate Authorities, this can add overhead to the mTLS handshake. Tyk provides an opportunity to skip the announcement (which is an optional step in the handshake) by configuring: + +```json +"http_server_options": { + "skip_client_ca_announcement": true +} +``` + + +## Tyk Gateway as a TLS Client (Outbound Connections) + +This section covers scenarios where the Tyk Gateway is initiating a TLS connection to another service, such as an upstream service, the Tyk Dashboard, or other external services like Redis. + +### Integrating with Dashboard + +If TLS is in use for the Dashboard's [Control API](/api-management/implement-tls#using-tls-with-tyk-dashboard), i.e., the Dashboard is the TLS server, then the Gateway will need to verify the server certificate presented by the Dashboard (unless configured to [skip verification](/api-management/implement-tls#skipping-server-certificate-verification) of the server certificate). + +The following must be configured in the Gateway (via `tyk.conf` or the equivalent environment variable): + +```json +{ + "security": { + "certificates": { + "dashboard_api": ["/path/to/ca-cert.pem"] + } + } +} +``` + +| Parameter | Description | +| --------- | ----------- | +| `certificates.dashboard_api` | An array of path references to CA certificates (PEM format) | + +### Connecting Securely to Upstream Services + +When communicating with upstream services, Tyk acts as the TLS client and must therefore hold both the client certificate and the private key. When mTLS is implemented with the upstream service, the Gateway will verify the upstream's identity by examining the server certificate it presents. + +- CA certificates can be directly registered with the Tyk Certificate Store +- Client certificates with a private key can be registered as a pair with the Tyk Certificate Store +    - The client certificate must be concatenated with the private key before being PEM encoded + +As usual with Tyk, some configuration is performed at the Gateway level and will be applied to all hosted APIs, whereas more granular configuration can be set in the API definition and applied to the specific API. + + +  The minimum and maximum TLS versions are both set to TLS 1.2 by default, unless set otherwise in the Gateway or API configuration. + + +#### Common Gateway-level Configuration + +The following common settings for upstream TLS connections are set in the Gateway config (tyk.conf or the equivalent environment variables): + +| Parameter | Description | +| --------- | ----------- | +| `proxy_enable_http2` | Use HTTP/2 for upstream connections | +| `proxy_ssl_insecure_skip_verify` | Do not verify server certificates presented by the upstream; not recommended for production environments  | +| `proxy_ssl_min_version` | Minimum TLS version allowed | +| `proxy_ssl_max_version` | Maximum TLS version allowed | +| `proxy_ssl_ciphers` | Allowed cipher suites (TLS 1.2 only) | +| `proxy_ssl_disable_renegotiation` | Prevent [TLS renegotiation](/api-management/implement-tls#tls-1-2-renegotiation-behavior) (TLS 1.2 only) | +| `security.pinned_public_keys` | Specify an allow list of server certificates that will be accepted; if the upstream server presents a certificate that isn't on the list then the TLS handshake will fail; see [server certificate pinning](/api-management/upstream-authentication/mtls#certificate-pinning) | +| `security.certificate.upstream` | Map of upstream domains to [certificate references](/api-management/certificates#certificate-management) for mTLS client authentication | +| `ssl_force_common_name_check` | Validate that the upstream hostname matches the Common Name (CN) in the server certificate ([legacy support](/api-management/implement-tls#common-name-cn-check)) | + +#### API-Specific Configuration + +Granular configuration of the TLS connection is controlled via the [`upstream.tlsTransport`](/api-management/gateway-config-tyk-oas#tlstransport) object in the Tyk Vendor Extension: + +```yaml + upstream: + tlsTransport: + insecureSkipVerify: true + minVersion: 1.2 + maxVersion: 1.3 + ciphers: + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + forceCommonNameCheck: true +``` + +| Parameter | Description | +| --------- | ----------- | +| `insecureSkipVerify` | Disables verification of the upstream server's certificate chain; not recommended for production environments | +| `minVersion` | Sets the minimum TLS version allowed (default: 1.2) | +| `maxVersion` | Sets the maximum TLS version allowed (default: 1.2) | +| `ciphers` | Allowed cipher suites (optional, only for TLS 1.2) | +| `forceCommonNameCheck` | Validate that the upstream hostname matches the Common Name (CN) in the server certificate ([legacy support](/api-management/implement-tls#common-name-cn-check)) | + +Full details for configuring certificates for mTLS with upstream services are in the dedicated [upstream mTLS](/api-management/upstream-authentication/mtls) section. + +#### Routing Through a Forward Proxy + +The "custom proxy" feature in Tyk lets you configure the Gateway to route requests to your upstream services via a forward proxy (also known as an HTTP proxy). This is often required in enterprise environments where outbound traffic must pass through a centralized proxy for security, logging, or policy enforcement. + +When you define an API in Tyk, you specify a *Target URL* for your upstream service. By default, the Tyk Gateway connects directly to this URL. The custom proxy feature allows you to insert an intermediary proxy for this connection. + +The configuration is handled within the [`upstream.proxy`](/api-management/gateway-config-tyk-oas#proxy) object of the Tyk Vendor Extension (`proxy` in the Tyk Classic API definition): + +```yaml + upstream: + proxy: + enabled: true + url: https://{USER}:{PASSWORD}@my-proxy.com:8080 +``` + +| Parameter | Description | +| --------- | ----------- | +| `enabled` | Set to `true` to activate the proxy redirect | +| `url`     | The URL of the forward proxy to which Tyk should direct upstream requests | + + + If you are using a secure proxy with an `https://` URL, Tyk will apply the TLS configuration for the target upstream, using the host operating system's standard root CA store to validate the proxy's server certificate. Tyk does not support mTLS connections to the forward proxy; only to the upstream target beyond the proxy. + + +### Connecting Securely to Other External Services + +Tyk Gateway can be integrated with various external services that support proxying hosted APIs, for example, Identity Providers (for authentication), webhook targets (for event handling), and storage (for example, to Redis). + +Tyk's [External Services Configuration guide](/configure/external-service) provides the details required to secure these connections using mTLS. + +### Advanced Client-Side TLS Settings + +#### Common Name (CN) Check + +Use of a server Common Name (CN) was deprecated by RFC 2818 in May 2000 in favor of Subject Alternative Names (SANs). Since Go 1.17, the standard library no longer validates hostnames against the CN field. + +When Tyk acts as a TLS client connecting to upstream services, it can perform a manual CN check to support: + +- Legacy systems that still rely on Common Name +- Internal certificates with CN but incomplete or missing SANs + +**Configuration** + +The CN check can be implemented for all hosted APIs in the Gateway config file (`tyk.conf`) or equivalent environment variable: + +```json +{ + "SSLForceCommonNameCheck": true +} +``` + +Or more granularly in the Tyk Vendor Extension in the API definition: + +```yaml + upstream: + tlsTransport: + forceCommonNameCheck: true +``` + +If using Tyk Classic, the equivalent field is `proxy.transport.ssl_force_common_name_check`. + +**Behavior** + +When the CN check is enabled, Tyk performs manual hostname verification against the certificate's Common Name field: + +- Direct connections: Tyk bypasses standard Go TLS verification and manually validates that the CN matches the upstream hostname +- [Proxy connections](/api-management/implement-tls#routing-through-a-forward-proxy): Tyk performs standard certificate chain verification then additionally validates the CN + +Security Note: This feature behaves as if `proxy.ssl_insecure_skip_verify=true` during the handshake to bypass Go's standard verification, then performs custom validation. Use only when necessary for legacy systems. + +**Recommendation** + +For modern integrations, ensure upstream certificates include proper SANs and avoid relying on CN validation. + +#### TLS 1.2 Renegotiation Behavior + +Prior to TLS 1.3, after completing the TLS handshake, either party (client or server) could request to renegotiate the session parameters (such as keys or ciphers). This introduced a potential security vulnerability, such as man-in-the-middle prefix injection attacks (e.g., CVE-2009-3555). + +The `proxy.ssl_disable_renegotiation` option in the Gateway configuration mitigates this risk. Setting it to `true` disallows renegotiation once the TLS connection has been established, which is the most secure configuration. + +By default, this option is `false`, which permits the Gateway (acting as a client) to initiate a renegotiation. This is for backward compatibility with legacy upstream services that may require it. However, even in this default mode, the Gateway will reject renegotiation requests initiated by the upstream server. + +This setting is only relevant to TLS 1.2 connections, as renegotiation is not supported in TLS 1.3. + +#### Skipping Server Certificate Verification + +In trusted environments, such as development environments, it is often easier to use self-signed certificates for expediency. During the TLS handshake, the client will be unable to verify the server's certificate, and the connection will be rejected. + +When acting as the client in the TLS handshake (for example, when connecting to Tyk Dashboard), Tyk Gateway can skip the verification of the server certificate if you configure: + +```json +{ + "http_server_options": { + "ssl_insecure_skip_verify": true + } +} +``` + +When set to `true` this: + +- Disables verification of the server's certificate chain +- Skips hostname validation against the certificate's Subject Alternative Names (SANs) +- Accepts any certificate presented by the server, regardless of its validity or trust status + + + It is important to note that this skips TLS security and so is not recommended for production environments. This setting does not affect Tyk's behaviour when interacting with your upstream API servers - there is a separate configuration for that, as explained [here](/api-management/implement-tls#connecting-securely-to-upstream-services). + + +## Using TLS with Tyk Dashboard + +### TLS Server Configuration + +The Tyk Dashboard can be configured to use HTTPS for its Control API using the `http_server_options` section in its configuration file (`tyk_analytics.conf`) or equivalent environment variables. This is similar to the section of the same name in the Gateway config, allowing configuration of TLS certificates (generic and for specific domains using SNI), the minimum TLS version, the cipher suites to be used (for TLS 1.2), and the option to skip certificate verification (recommended only for non-production deployments). + +Note however that, unlike the Gateway, the Dashboard **does not** support configuring a maximum TLS version. It relies on Go's default behavior, which automatically supports up to the highest available TLS version (currently TLS 1.3). + + + The Dashboard does not have access to the Tyk Certificate Store when configuring its own interfaces, so certificates and keys must be available on the filesystem and referenced by path. + + +```json +{ + "http_server_options": { + "use_ssl": true, + "certificates": [ + { + "domain_name": "dashboard.example.com", + "cert_file": "/path/to/server-cert.cert", + "key_file": "/path/to/server-cert.key" + } + ], + "ssl_certificates": ["/path/to/server-certificate-pair.pem"], + "min_version": 771, // TLS 1.2 + "ssl_ciphers": ["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"], + "ssl_insecure_skip_verify": false + }, + "host_config": { + "generate_secure_paths": true + } +} +``` + +| Parameter | Description | +| --------- | ----------- | +| `use_ssl` | Enables HTTPS for the Dashboard web interface | +| `certificates` | Domain-mapped server certificate pairs used for domain-specific configuration when using SNI | +| `ssl_certificates` | Array of paths to PEM-encoded server certificate pairs. Each file must contain both the certificate and its private key in a single PEM file. Unlike `certificates[]`, these certificates cannot be mapped to specific domains. | +| `min_version` | Minimum TLS version (771 = TLS 1.2, 772 = TLS 1.3) | +| `ssl_ciphers` | List of allowed cipher suites (only applicable to TLS 1.2 handshakes) | +| `ssl_insecure_skip_verify` | If `true`, the Dashboard's web server will not verify the certificates presented by connecting clients. This is insecure and not recommended for production environments. | +| `host_config.generate_secure_paths` | Set this to `true` so that Dashboard URLs will start with `HTTPS` | + + + If both `certificates[]` and `ssl_certificates[]` are provided, all certificates from both configurations will be loaded, with domain-specific certificates from `certificates[]` taking precedence for their specified domains. + + +You must set the [host_config.generate_secure_paths](/tyk-dashboard/configuration#host_config-generate_secure_paths) flag to `true` so that your Dashboard URL starts with HTTPS. This will also set the default protocol for Target URLs to `https://` when creating APIs in the Dashboard's API Designer if no protocol is explicitly set. + +### Integrating with Tyk Gateway + +For management communications with a Tyk Gateway (such as reloading APIs, managing keys, and updating configurations), where the Gateway is the TLS server, the Dashboard uses a dedicated internal client. This client is hardcoded to always skip TLS certificate verification. This behavior is not configurable and is intended to simplify setup in environments where the Gateway may use self-signed certificates. + +You do not need to configure anything for the Dashboard to skip TLS verification when communicating with a Gateway for management tasks; this is the default and the only behavior. + +### Using mTLS between Dashboard and storage + +#### Integrating with Redis + +To enable mTLS for the connection between Dashboard and Redis, add the following settings to your `tyk_analytics.conf` file: + +```json +{ + "redis_use_ssl": true, + "redis_ca_file": "/path/to/ca.cert", + "redis_cert_file": "/path/to/client.cert", + "redis_key_file": "/path/to/client.key" +} +``` + +| Parameter | Description | +|-----------|-------------| +| `redis_use_ssl` | Set to true to enable TLS for Redis connections | +| `redis_cert_file` | Path to the client certificate file that Tyk Dashboard will present to Redis | +| `redis_key_file` | Path to the client private key file corresponding to the certificate | +| `redis_ca_file` | Path to the Certificate Authority (CA) certificate file the Dashboard should use to verify the Redis server's certificate | +| `redis_tls_min_version` | Minimum TLS version to use. Options are "1.2" or "1.3". Defaults to "1.2" | +| `redis_tls_max_version` | Maximum TLS version to use. Options are "1.2" or "1.3". Defaults to "1.2" | +| `redis_ssl_insecure_skip_verify` | Set to `true` to skip verification of the Redis server's certificate; not recommended for production environments | + +**Important Notes** + +- **Certificate Format**: The cert and key files should be in PEM format +- **File Permissions**: Ensure the Dashboard process has read access to the certificate and key files +- **Redis Server Configuration**: Your Redis server must be configured to require client certificates for mTLS to work +- **Gateway Sync**: Ensure your Tyk Gateway is also configured with the same [Redis mTLS settings](/configure/external-service#service-specific-configuration) + +For more details on configuring Tyk Dashboard with Redis, see the [Database Management](/planning-for-production/database-settings) guide. + + +#### Integrating with MongoDB + +To enable mTLS for the connection between Dashboard and MongoDB persistent storage, add the following settings to your `tyk_analytics.conf` file: + +```json +{ + "storage": { + "main": { + "type": "mongo", + "connection_string": "mongodb://your-mongo-host:27017/tyk_analytics", + "mongo": { + "ssl": { + "enabled": true, + "ca_file": "/path/to/ca-cert.pem", + "key_file": "/path/to/client-cert-and-key.pem", + "insecure_skip_verify": false, + "allow_invalid_hostnames": false, + }, + "driver": "mongo-go" + } + } + } +} +``` + +| Parameter | Description | +|-----------|-------------| +| `type` | Set to `mongo` | +| `connection_string` | URL of the MongoDB instance | +| `ssl.enabled` | Set to `true` to enforce mTLS | +| `ssl.ca_file` | Path to the Certificate Authority (CA) certificate file the Dashboard should use to verify the MongoDB server's certificate | +| `ssl.key_file` | Path to file containing both the server certificate and private key for Dashboard | +| `ssl.ssl_insecure_skip_verify` | Set to `true` to skip verification of the MongoDB server's certificate; not recommended for production environments | +| `ssl.allow_invalid_hostnames` | Set to `true` to skip validation of the MongoDB server hostname | +| `driver` | Choose the [MongoDB driver](/tyk-dashboard/configuration#mongo_driver) to be used, typically `mongo-go` | + +**Important Notes** + +- **Certificate Format**: The ca and key files should be in PEM format +- **File Permissions**: Ensure the Dashboard process has read access to the certificate and key files +- **Redis Server Configuration**: Your MongoDB server must be configured to require client certificates for mTLS to work + +For more details on using Tyk Dashboard with MongoDB, see the [Database Management](/planning-for-production/database-settings) guide. + + +#### Integrating with PostgreSQL + +PostgreSQL uses connection string parameters for TLS configuration. To enable mTLS for the connection between Dashboard and PostgreSQL persistent storage, add the following settings to your `tyk_analytics.conf` file: + +```json +{ + "storage": { + "main": { + "type": "postgres", + "connection_string": "host=your-postgres-host port=5432 dbname=tyk_analytics user={USERNAME} password={PASSWORD} sslmode=verify-full sslrootcert=/path/to/ca-cert.pem sslcert=/path/to/client-cert.pem sslkey=/path/to/client-key.pem" + } + } +} +``` + +| Parameter | Description | +|-----------|-------------| +| `type` | Set to `postgres` | +| `connection_string` | PostgreSQL connection string | + + +| TLS Connection String Parameters | Description | +|------------------------------|-------------| +| `sslmode` | Set to `verify-full` for full certificate validation (recommended for production) | +| `sslrootcert` | Path to the CA certificate file | +| `sslcert` | Path to the client certificate file | +| `sslkey` | Path to the client private key file | + +Alternative values for `sslmode`: + +- `require`: Encrypts the connection but doesn't verify the server certificate +- `verify-ca`: Verifies server certificate against CA +- `verify-full`: Verifies server certificate and hostname (most secure) + +For more details on using Tyk Dashboard with PostgreSQL, see the [Database Management](/planning-for-production/database-settings) guide. + +## Using TLS with MDCB + +In a distributed deployment, MDCB is the server and the Data Plane Gateways are the clients. If you want to secure this connection using TLS you must configure both components. + +### Configuring MDCB + +MDCB has two separate servers that can be configured individually in the `tyk-sink.conf` file or environment variables: + +- `server_options`: the main RPC (Remote Procedure Call) server is the primary endpoint that Tyk Gateways connect to in order to synchronize API definitions, policies, and security keys. +- `http_server_options`: the HTTP control API server used for administrative, monitoring, and debugging purposes. + +Each server has a similar configuration to Dashboard, with settings described in the [reference documentation](/tyk-multi-data-centre/mdcb-configuration-options). + +Note that you must set `security.private_certificate_encoding_secret` in the MDCB configuration file to the same value as specified in your Control Plane Gateway and Dashboard configuration files, to ensure that MDCB can [decode private keys](/api-management/certificates#encryption-of-the-private-key) that have been added to the Tyk Certificate Store. + + + MDCB does not have access to the Tyk Certificate Store when configuring its own interfaces, so certificates and keys must be available on the filesystem and referenced by path. + + +### Configuring Data Plane Gateways + +The following must be configured in the Data Plane Gateways (via `tyk.conf` or environment variables) to set up TLS for the connection to MDCB's RPC server: + +```json +{ + "slave_options": { + "use_ssl": true, + "ssl_insecure_skip_verify": false + }, + "security": { + "certificates": { + "mdcb_api": [ + "cert-id-from-store", + "/path/to/client-cert.pem" + ] + } + } +} +``` + +| Parameter | Description | +|-----------|-------------| +| `slave_options.use_ssl` | Instructs the Gateway to use TLS in the connection to MDCB | +| `slave_options.ssl_insecure_skip_verify` | Skips the verification of the server certificate presented by MDCB; not recommended for production environments | +| `security.certificates.mdcb_api` | A list of client certificate-key pairs, each specified as a [certificate reference](/api-management/certificates#certificate-management) | + +The MDCB's server certificate will be verified against the CA certificates in the Gateway host's standard root CA store. This is not separately configurable. + +During the mTLS handshake, the server (MDCB) will advertise the Certificate Authorities it will use to verify the client certificate. The client (Data Plane Gateway) will present the first valid certificate in the `certificates.mdcb_api` list that matches one of these CAs. Multiple certificates allow rotation scenarios with overlapping validity periods. + + +## Frequently Asked Questions + + + + +You can create self-signed client and server certificates with this command: + +``` +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes +``` + + + +To get the certificate SHA256 fingerprint, use the following command: + +``` +openssl x509 -noout -fingerprint -sha256 -inform pem -in +``` + + + +If you are testing using cURL, your request to an mTLS secured API will look like: + +``` +curl --cert client_cert.pem --key client_key.pem https://localhost:8181 +``` + + + diff --git a/api-management/logs-metrics.mdx b/api-management/logs-metrics.mdx new file mode 100644 index 0000000000..1dfc51eec4 --- /dev/null +++ b/api-management/logs-metrics.mdx @@ -0,0 +1,205 @@ +--- +title: "Metrics Configuration in Tyk Gateway" +description: "Learn how to configure Tyk Gateway to export metrics using OpenTelemetry Protocol (OTLP) and legacy approaches." +keywords: "Metrics, OpenTelemetry, OTLP, New Relic, StatsD, API Observability, Monitoring, Prometheus, Grafana, Datadog" +sidebarTitle: "Overview" +--- + +## Introduction + +Metrics provide aggregated, quantitative data about the performance and behavior of your APIs over time. They offer insights into the overall health of the system. + +Tyk supports several ways to export metrics, from different components: + +| Method | Component | Status | Description | +|--------|-----------|--------|-------------| +| [OpenTelemetry Metrics](#opentelemetry-metrics) | Tyk Gateway | **Recommended** | Native OTLP metrics export. Works with any OpenTelemetry-compatible backend. | +| [Metrics Pumps](#metrics-pumps) | Tyk Pump | Legacy | Prometheus, StatsD, or DogStatsD metrics derived from traffic logs. Relevant for existing deployments. | +| [New Relic Instrumentation](#new-relic-instrumentation) | Tyk Gateway | [Deprecated](/developer-support/deprecation#understanding-deprecation-and-end-of-life-eol) | Legacy New Relic agent integration available since Gateway v2.5. | +| [StatsD Instrumentation](#statsd-instrumentation) | Tyk Gateway, Pump, Dashboard | [Deprecated](/developer-support/deprecation#understanding-deprecation-and-end-of-life-eol) | Legacy StatsD protocol export for Gateway, Pump, and Dashboard. | + +## OpenTelemetry Metrics + +### Availability + +| Component | Version | Edition | +| :-------- | :------ | :------- | +| Tyk Gateway | Available since [v5.13.0](/developer-support/release-notes/gateway#5-13-0-release-notes) | Community & Enterprise | + +### Tyk Gateway Metrics + +Tyk Gateway natively exports metrics via the OpenTelemetry Protocol (OTLP), the recommended way to collect Gateway metrics. When enabled, the Gateway pushes standard RED metrics (Rate, Errors, Duration) plus Go runtime and configuration state metrics to any OTLP-compatible backend. Some backends accept this natively; others need an OpenTelemetry Collector in between, see [Supported Backends](#supported-backends) below. + +For a complete reference, see: + +- [Default Metrics](/api-management/metrics/default-metrics): all metrics exported automatically (RED, Go runtime, configuration state) +- [Custom Metrics](/api-management/metrics/custom-metrics): defining custom counters and histograms with your own dimensions + +### Supported Backends + +The metrics exporter sends data using standard OTLP, so any OTLP-compatible backend works. Some common setups: + +**Via OTel Collector (recommended for self-managed):** Route metrics from the OTel Collector to Prometheus, Grafana Mimir, Splunk, or other backends. The Collector also handles batching, retries, and fanout to multiple destinations. + +**Direct OTLP:** Send directly to backends that natively accept OTLP, such as: + +- New Relic (`otlp.nr-data.net:4317`) +- Dynatrace (via the Dynatrace OTLP endpoint) +- Grafana Cloud Mimir + +Standalone Prometheus doesn't accept OTLP by default: use the OTel Collector path above, unless you're running Prometheus v3.0+ with its native OTLP receiver explicitly enabled. + +## Configuration Options + +Metrics can be enabled independently; distributed tracing does not need to be configured. To configure distributed tracing alongside metrics, see [Distributed Tracing](/api-management/traces). + +### Enable Metrics + +If you only need metrics export, configure the `metrics` sub-object directly: + +```json +{ + "opentelemetry": { + "metrics": { + "enabled": true, + "endpoint": "otel-collector:4317" + } + } +} +``` + +### Traces and metrics together + +When both are configured, `metrics.endpoint` defaults to the `traces.endpoint` value if not set explicitly: + +```json +{ + "opentelemetry": { + "traces": { + "enabled": true, + "endpoint": "otel-collector:4317" + }, + "metrics": { + "enabled": true + } + } +} +``` + +This is enough to start exporting [default Gateway metrics](/api-management/metrics/default-metrics) to the same OTLP endpoint configured for traces. + +### Reference + +| Field | Description | Default | +|-------|-------------|---------| +| [opentelemetry.metrics.enabled](/tyk-oss-gateway/configuration#opentelemetry-metrics-enabled) | Enable OTLP metrics export | `false` | +| [opentelemetry.metrics.exporter](/tyk-oss-gateway/configuration#opentelemetry-metrics-exporter) | Export protocol: `grpc` or `http` | `grpc` | +| [opentelemetry.metrics.endpoint](/tyk-oss-gateway/configuration#opentelemetry-metrics-endpoint) | OTLP collector endpoint. Defaults to `opentelemetry.traces.endpoint` if not set | — | +| [opentelemetry.metrics.connection_timeout](/tyk-oss-gateway/configuration#opentelemetry-metrics-connection_timeout) | Connection timeout in seconds | `1` | +| [opentelemetry.metrics.headers](/tyk-oss-gateway/configuration#opentelemetry-metrics-headers) | Additional HTTP headers sent with each OTLP export request | — | +| [opentelemetry.metrics.tls](/tyk-oss-gateway/configuration#opentelemetry-metrics-tls) | TLS configuration for the OTLP connection | — | +| [opentelemetry.metrics.export_interval](/tyk-oss-gateway/configuration#opentelemetry-metrics-export_interval) | How often metrics are pushed to the backend, in seconds | `60` | +| [opentelemetry.metrics.shutdown_timeout](/tyk-oss-gateway/configuration#opentelemetry-metrics-shutdown_timeout) | Maximum time allowed for a single export request, in seconds | `30` | +| [opentelemetry.metrics.cardinality_limit](/tyk-oss-gateway/configuration#opentelemetry-metrics-cardinality_limit) | Maximum unique attribute combinations tracked per instrument. Set to `0` to disable | `2000` | +| [opentelemetry.metrics.runtime_metrics](/tyk-oss-gateway/configuration#opentelemetry-metrics-runtime_metrics) | Export Go runtime metrics (memory, goroutines, GC). Set to `false` to disable | `true` | +| [opentelemetry.metrics.api_metrics](/tyk-oss-gateway/configuration#opentelemetry-metrics-api_metrics) | Custom metric instrument definitions. See [Custom Metrics](/api-management/metrics/custom-metrics) | — | + +All fields are also documented in the [Tyk Gateway Configuration Reference](/tyk-oss-gateway/configuration#opentelemetry). + + +Root-level configuration such as `opentelemetry.enabled` and `opentelemetry.endpoint` still work for traces but are deprecated. Use `opentelemetry.traces.*` for new deployments. + + +### Cardinality Control + +Each unique combination of dimension values creates a separate time series in your metrics backend. For example, a metric with 100 APIs × 5 methods × 10 status codes = 5,000 time series. Adding dimensions that have many possible values (such as user IDs or free-text fields) can create millions of time series, causing high storage costs and potential out-of-memory crashes. + +The `opentelemetry.metrics.cardinality_limit` field caps how many unique attribute combinations are tracked per metric instrument: + +- **Default limit:** 2,000 combinations per instrument +- **When the limit is reached:** New attribute combinations are aggregated into an overflow bucket marked with `otel.metric.overflow=true` +- **Existing time series are not affected**; only new combinations are blocked +- **To disable the limit:** Set to `0` (not recommended for production) + +You can alert on cardinality overflow using this PromQL expression: + +```promql +increase(some_metric{otel_metric_overflow="true"}[5m]) > 0 +``` + +This lets you catch runaway cardinality before costs escalate. + +## Tyk Cloud + +On Tyk Cloud, metrics export is enabled per environment via the **Telemetry** settings in the Tyk Cloud UI. A **Telemetry entitlement** is required; contact your account team if it is not available on your plan. + +**Supported export destinations on Tyk Cloud:** + +- New Relic +- Elastic (Elasticsearch / OpenSearch) +- Dynatrace +- Custom OTLP endpoint (any OTLP-compatible backend) + +Tyk Cloud exports the [default gateway metrics](/api-management/metrics/default-metrics) through the telemetry pipeline. Custom metric configuration is not currently self-service on Tyk Cloud; contact Tyk support to request it. + +## Metrics Pumps + +[Tyk Pump](/api-management/tyk-pump) can expose metrics derived from the traffic logs that the Gateway stores in Redis, the same traffic logs used for [Dashboard Analytics](/api-management/dashboard-analytics). This is an older, separate mechanism: computed by Tyk Pump after the fact from traffic log records, not by Tyk Gateway in real time, so it can only produce traffic-derived counters and histograms (status codes, latency, and whatever traffic log fields you tag). It has no visibility into Gateway's own runtime health or configuration state. + +There are three Metrics Pumps, [documented here](/api-management/metrics/metrics-pumps), that target different backends: + +- Prometheus +- DogStatsD (Datadog) +- StatsD (not to be confused with the native Gateway [StatsD instrumentation](#statsd-instrumentation)) + +Metrics Pumps aren't deprecated, and existing deployments can keep using them, but we recommend [OpenTelemetry Metrics](#opentelemetry-metrics) where you can: it's more powerful and flexible, and doesn't require the Redis and Tyk Pump hop to get metrics out. + +## Deprecated Backends + +### New Relic Instrumentation + + +This instrumentation method is deprecated. New Relic users should migrate to the [OpenTelemetry metrics exporter](/api-management/logs-metrics#opentelemetry-metrics) with a New Relic OTLP endpoint. The OTel approach provides richer metrics, more dimensions, and does not require a New Relic agent license. + + +Tyk Gateway has been instrumented for New Relic metrics since v2.5. Add the following to `tyk.conf` to enable it: + +```json +{ + "newrelic": { + "app_name": "", + "license_key": "" + } +} +``` + +### StatsD Instrumentation + + +This instrumentation method is deprecated. We recommend using [OpenTelemetry metrics](/api-management/logs-metrics#opentelemetry-metrics) instead, which provides richer metrics and works with any modern observability backend. + + +Tyk Gateway, Pump, and Dashboard have been instrumented for [StatsD](https://github.com/etsy/statsd) monitoring. StatsD is a network daemon that listens for statistics sent over UDP or TCP and forwards aggregates to pluggable backend services. + + +Not to be confused with Tyk Pump's `statsd`/`dogstatsd` [Metrics Pumps](#metrics-pumps) above: those export traffic-log-derived request metrics, a different mechanism from this internal system instrumentation. + + +#### Configuring StatsD + +To enable StatsD instrumentation, set the environment variable `TYK_INSTRUMENTATION=1` and configure `statsd_connection_string` in each component's config file. This field specifies how to connect to the StatsD server (host, port, and optional configuration). + +You can also set `statsd_prefix` to a custom value to differentiate metrics between environments (for example, separate prefixes for production and staging). + +For Tyk Pump specifically, both fields sit at the top level of `pump.conf`, alongside `log_level` and the other [global settings](/api-management/tyk-pump#configuring-tyk-pump). + +#### StatsD Keys + +| Key | Description | +|-----|-------------| +| `gauges..Load.rps` | API traffic handled by Gateway (requests per second) | +| `counters..SystemAPICall.called.count` | Gateway API call count | +| `timers..SystemAPICall.success` | Gateway API response time | +| `counters..SystemAPICall.SystemCallComplete.count` | Dashboard API request count | +| `counters..DashSystemAPIError.*` | Dashboard API error reporting | +| `counters..record.count` | Number of records processed by Pump | diff --git a/api-management/logs.mdx b/api-management/logs.mdx new file mode 100644 index 0000000000..dfd55e80f3 --- /dev/null +++ b/api-management/logs.mdx @@ -0,0 +1,185 @@ +--- +title: "Logs" +description: "Learn about the different types of logs in Tyk, how to configure them and how to integrate with third-party log management tools for effective API observability." +keywords: "Logs, Application Logs, System Logs, API Traffic, Access Logs, Traffic Logs, Datadog, Dynatrace, New Relic, Elastic Search, Jaeger, Monitoring, Observability" +sidebarTitle: "Overview" +--- + +A log is a timestamped text record, either structured (recommended) or unstructured, with some metadata. + +## Types of Logs + +Tyk generates four types of logs: + +- **[Application Log](/api-management/logs/application-logs):** Internal system events such as health-checks, configuration changes, and errors. +- **[API Traffic Log](/api-management/dashboard-analytics#where-the-data-comes-from):** Gateway A record of every API request, written into Redis and processed by Tyk Pump for analytics and reporting. +- **[Access Log](/api-management/logs/access-logs):** Gateway Per-request server logs intended for external log aggregators. Similar to API Traffic Logs in that both record individual requests, but Access Logs are lightweight and real-time whereas API Traffic Logs are richer and processed asynchronously. +- **[Audit Log](/api-management/logs/audit-logs):** Dashboard A record of user actions in Tyk Dashboard, such as API changes and login events. + + +## Configuring Application and Access Logs + +Tyk Gateway, Tyk Pump, Tyk Dashboard, Tyk MDCB, and Tyk Developer Portal each write an **Application Log** to `stderr`, handled in a typical installation by the service manager running the process. + +Tyk Gateway's **Access Log** uses the same underlying logger, so every setting that applies to Tyk Gateway's Application Log also affects the Access Log. + +Three aspects can be configured: + +- **verbosity**: which severity levels are written +- **format**: the structure and content of each entry +- **log output**: where logs are sent, for example to a third-party aggregator + +Both verbosity and format can be controlled globally across all components, or per component for finer control. Global settings take priority over component-specific ones. + +### Global Settings + +Two environment variables apply across multiple components and override any component-specific setting: + +| Setting | Environment variable | Applies to | +| :--- | :--- | :--- | +| Verbosity | `TYK_LOGLEVEL` | All components except Tyk Developer Portal | +| Format | `TYK_LOGFORMAT` | Tyk Gateway, Tyk Dashboard, Tyk Pump | + +### Component Settings + +When global variables are not set, each component can be configured individually using environment variables or the equivalent `log_level` and `log_format` settings in its configuration file. + +All components default to `info` verbosity and `text` format. Tyk Developer Portal is an exception: it defaults to `prod` format, which is equivalent to `json`. + +| Tyk component | Log level env var | Log format env var | Supported formats | +| :--- | :--- | :--- | :--- | +| [Tyk Gateway](/tyk-oss-gateway/configuration#log_level) | `TYK_GW_LOGLEVEL` | `TYK_GW_LOGFORMAT` | `text`, `json`, `legacy` | +| [Tyk Pump](/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables#log_level) | `TYK_PMP_LOGLEVEL` | `TYK_PMP_LOGFORMAT` | `text`, `json`, `legacy` | +| [Tyk Dashboard](/tyk-dashboard/configuration) | `TYK_DB_LOGLEVEL` (from `v5.14.0`) | `TYK_DB_LOGFORMAT` | `text`, `json` | +| [Tyk MDCB](/tyk-multi-data-centre/mdcb-configuration-options#log_level) | `TYK_MDCB_LOGLEVEL` | N/A | Legacy text only | +| [Tyk Developer Portal](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_log_level) | `PORTAL_LOG_LEVEL` | `PORTAL_LOG_FORMAT` | `text`, `json` | + + +The `legacy` format was introduced in Tyk Gateway 5.14.0 and Tyk Pump 5.14.0 to maintain backward compatibility for existing users with log pipelines reliant on the precise log content. See [legacy format](/api-management/logs#legacy-format). + + +### Severity and Verbosity + +The severity of a log is an indication of its likely importance to the system administrator. The system will generate logs at four different levels of severity: + +| Severity | Purpose | Example | +|:---------|:--------|:--------| +| Error | Conditions that require immediate attention, such as a component being unreachable or a request failing due to a system fault. | Failed to connect to Redis | +| Warning | Potential issues or degraded behavior that may require investigation, but do not prevent the system from operating. | Configuration value out of bounds, using default | +| Information | Normal operational events confirming the system is functioning as expected. This is the default level. | Gateway started, API loaded, access log entries | +| Debug | Detailed diagnostic output useful for troubleshooting, such as middleware execution steps and request routing decisions. | Middleware execution details | + +Each component can individually be configured to output only the logs generated at or above a given severity level by setting the **log level** as follows: + +- `error`: only errors are logged +- `warn`: warnings and errors are logged +- `info`: errors, warnings, and informational messages are logged (default) +- `debug`: all of the above, plus detailed diagnostic output + + +Access Logs have `info` level severity. Setting Tyk Gateway's log level to `warn` or `error` therefore suppresses Access Log output. There are additional controls for the generation and content of access logs as described in [Access Logs](/api-management/logs/access-logs). + + + +Debug log level generates a significant amount of data and is not recommended unless debugging. + + +### Format Options + +Log format controls the structure and timestamp style of application and access logs generated by a component. The supported values are `text` (default), `json` (recommended), and `legacy`. + + +As a general performance tip, the `json` output format incurs less memory allocation overhead than the `text` format. For optimal performance, it's recommended to configure logging in the JSON format. + + + + + +``` +time="2024-09-05T09:04:12Z" level=info message="Tyk API Gateway v5.14.0" prefix=main +``` + + + + +```json +{"level":"info","message":"Tyk API Gateway v5.14.0","prefix":"main","time":"2024-09-05T09:04:12Z"} +``` + + + + +Preserves the previous timestamp format and `msg` field key: + +``` +time="Sep 05 09:04:12" level=info msg="Tyk API Gateway v5.14.0" prefix=main +``` + + + + +#### Legacy Format + +From Tyk Gateway 5.14.0 and Tyk Pump 5.14.0, the `text` and `json` formats use RFC3339 timestamps and a standardized `message` field. + +If your log pipeline relies on the previous timestamp format (`Dec 12 13:50:45`) or the `msg` field key, set `log_format` to `legacy` to preserve the old behavior. + +### Log Output + +By default, application and access log output is written to `stderr`. + +Tyk Gateway and Dashboard can also forward logs to a third-party aggregator, in addition to `stderr`: + +| Data Sink | Gateway | Dashboard | +| :--- | :---: | :---: | +| Sentry | ✅ | ✅ | +| Logstash | ✅ | ❌ | +| Graylog | ✅ | ❌ | +| Syslog | ✅ | ❌ | + +Add the relevant settings to `tyk.conf` for Tyk Gateway, or `tyk_analytics.conf` for Tyk Dashboard (or use the equivalent environment variables): + + + + +Gateway Dashboard + +- `use_sentry`: Set to `true` to enable output to [Sentry](https://sentry.io/product/logs/). +- `sentry_code`: The Sentry-assigned DSN (endpoint URL) to which the logs are sent. + + + + +Gateway Only + +- `use_logstash`: Set to `true` to enable output to [Logstash](https://www.elastic.co/logstash). +- `logstash_transport`: Set this to `"tcp"`. +- `logstash_network_addr`: The network address of the Logstash server, in the form `hostname:port`. + + + + +Gateway Only + +- `use_graylog`: Set to `true` to enable output to [Graylog](https://graylog.org/). +- `graylog_network_addr`: The network address of the Graylog server, in the form `hostname:port`. + + + + +Gateway Only + +- `use_syslog`: Set to `true` to enable output to syslog. +- `syslog_transport`: Set to `"udp"` to send to a remote syslog server, or leave empty to use the local Unix socket. +- `syslog_network_addr`: The network address of the syslog server, in the form `hostname:port`. + + + + +## Configuring Traffic Logs + +Traffic Logs are generated by Tyk Gateway and written to Redis, not `stderr`, so none of the settings above apply to them. Enable them by setting [`enable_analytics`](/tyk-oss-gateway/configuration#enable_analytics) in the Gateway configuration; see [Dashboard Analytics](/api-management/dashboard-analytics) for how Tyk Pump then processes and routes them from Redis. + +## Configuring Audit Logs + +Audit Logs are generated by Tyk Dashboard and configured through its own `audit.*` settings block, unrelated to the Application Log settings above. See [Audit Logs](/api-management/logs/audit-logs) for the full configuration reference, or [Enable and View Audit Logs in Tyk Dashboard](/api-management/enable-audit-logs-dashboard) for a step-by-step guide. diff --git a/api-management/logs/access-logs.mdx b/api-management/logs/access-logs.mdx new file mode 100644 index 0000000000..22fa5fee40 --- /dev/null +++ b/api-management/logs/access-logs.mdx @@ -0,0 +1,439 @@ +--- +title: "Gateway Access Logs" +description: "Learn how to configure Tyk Gateway to generate API access logs" +keywords: "Logs, Logging, Tyk Gateway, Access Log, API Request" +sidebarTitle: "Access Logs" +--- + +## Introduction + +An Access Log is a single log line written at `info` severity to `stderr` for every request Tyk Gateway handles, giving you a lightweight, real-time record of individual requests. This is a different mechanism from an [API Traffic Log](/api-management/logs/traffic-logs), which also records individual requests but is written to Redis and processed asynchronously by Tyk Pump, carrying richer detail as a result. + +Access Logs are generated by the same logger as Tyk Gateway's Application Log, so their severity, format, and third-party output are all controlled by the same shared settings: + +- **verbosity**: which severity levels are written (must be `info` or `debug` for Access Logs to be recorded at all) +- **format**: the structure and timestamp style of each entry +- **log output**: where logs are sent, for example to a third-party aggregator + +See [Configuring Application and Access Logs](/api-management/logs#configuring-application-and-access-logs) for details of these and how to configure them. + +Access Logs have two additional settings of their own, covered below: whether they're generated at all ([Enabling Access Logs](#enabling-access-logs)), and which fields they include ([Access Log Content](#access-log-content)). + +## Availability + +| Component | Version | Edition | +| :-------- | :------ | :------- | +| Tyk Gateway | Available since [v5.8.0](/developer-support/release-notes/gateway#5-8-0-release-notes) | Community & Enterprise | + + +Access Logs are not available on Tyk Cloud. + + +## Enabling Access Logs + +Access Logs are disabled by default. Set `access_logs.enabled` to `true` to turn them on: + + + + +Configuration using `tyk.conf`: + +```json +{ + "access_logs": { + "enabled": true + } +} +``` + + + + +Configuration using environment variables: + +``` +TYK_GW_ACCESSLOGS_ENABLED=true +``` + + + + +## Access Log Content + +Each Access Log entry contains: + +- timestamp +- the log severity (always `info`) +- `prefix`, always set to `access-log`; this identifies the entry as an Access Log, since Application Log entries are written to the same output but use other `prefix` values. +- a configurable list of key-value pairs giving details of the request (for example API ID or HTTP response status code) + +For example: +``` +time="2025-01-29T08:27:09Z" level=info api_id=b1a41c9a89984ffd7bb7d4e3c6844ded api_key=00000000 api_name=httpbin api_type=oas client_ip="::1" host="localhost:8080" latency_gateway=1 latency_total=62 method=GET org_id=678e6771247d80fd2c435bf3 original_path=/get path=/get prefix=access-log protocol=HTTP/1.1 remote_addr="[::1]:63251" status=200 trace_id=4bf92f3577b34da6a3ce929d0e0e4736 upstream_addr="http://httpbin.org/get" upstream_latency=61 user_agent=PostmanRuntime/7.43.0 +``` + +The keys that will be included in the logs are configured using an [access log template](#access-log-templates). If no template is configured, then all available fields will be included in each log entry. The `prefix` field is always included, whether or not a template is configured. + +The full list of available fields that can be included in an access log is [here](#access-log-field-reference). + + +Field order is predictable, not arbitrary: `time` and `level` always come first, followed by every other field in alphabetical order. + + +### Access Log Templates + +You can define an access log template to reduce the size of the access log by including only a subset of the available fields. This is a simple list of field names declared in the Tyk Gateway configuration as a `template` array within the `access_logs` object. + +For example: + + + + +Configuration using `tyk.conf` + +```json +{ + "access_logs": { + "enabled": true, + "template": [ + "api_key", + "remote_addr", + "upstream_addr" + ] + } +} +``` + + + + +Configuration using environment variables: + +``` +TYK_GW_ACCESSLOGS_ENABLED=true +TYK_GW_ACCESSLOGS_TEMPLATE="api_key,remote_addr,upstream_addr" +``` + + + +Output: + +``` +time="2025-01-29T08:27:48Z" level=info api_key=00000000 prefix=access-log remote_addr="[::1]:63270" upstream_addr="http://httpbin.org/get" +``` + +Only the three templated fields appear, plus `prefix`, which is always included regardless of the template. + +### MCP-Specific Fields + +When the log is generated for a request to an MCP Proxy, MCP-specific fields are added to the access log: + +- [`mcp_method`](#param-mcp-method) +- [`mcp_primitive_type`](#param-mcp-primitive-type) +- [`mcp_primitive_name`](#param-mcp-primitive-name) +- [`mcp_error_code`](#param-mcp-error-code) + +These fields are omitted when empty, so non-MCP logs are unaffected. + +**Examples** + + + + +When an AI client calls a tool via an MCP Proxy: + +``` +time="2025-04-07T10:15:30Z" level=info api_id=mcp-weather-api api_name=weather-mcp api_type=mcp + latency_total=143 mcp_method=tools/call + mcp_primitive_name=get_current_weather mcp_primitive_type=tool + method=POST path=/mcp prefix=access-log status=200 +``` + +Key fields: +- `api_type=mcp` identifies this as an MCP Proxy request. +- `mcp_method=tools/call` indicates the JSON-RPC method invoked. +- `mcp_primitive_type=tool` and `mcp_primitive_name=get_current_weather` identify the specific tool that was called. + + + + +When the upstream MCP server returns a JSON-RPC error: + +``` +time="2025-04-07T10:16:02Z" level=info api_id=mcp-weather-api api_name=weather-mcp api_type=mcp + latency_total=38 mcp_error_code=-32602 mcp_method=tools/call + mcp_primitive_name=get_forecast mcp_primitive_type=tool + method=POST path=/mcp prefix=access-log status=200 +``` + +Note that JSON-RPC errors are typically returned with HTTP 200 (as per the JSON-RPC spec). The `mcp_error_code` field identifies the error, and `response_flag` will reflect the gateway classification. + + + + +### Access Log Field Reference + + + ID of the requested API definition. Present whenever the request matched an API. + + + + Obfuscated or hashed API key used in the request. + + + + Name of the requested API definition. Present whenever the request matched an API. + + + + API type classification. One of `mcp`, `graphql`, `oas`, or `classic`. Always present. + + + + Circuit breaker state (for example, `OPEN`), only present on circuit breaker errors. + + + + IP address of the client making the request. + + + + Gateway component that generated the error, only present on error requests. + + + + Upstream address being accessed, only present on upstream or proxy errors. + + + + Hostname of the request. + + + + Time spent in Tyk Gateway's own processing, separate from the upstream round trip. + + + + Total time taken to process the request, including upstream latency and additional gateway processing. + + + + JSON-RPC error code when the MCP request fails (for example, `-32001` for Authentication required, `-32002` for Access denied). Only present when an MCP error occurs. + + + + JSON-RPC method called on an MCP Proxy (for example, `tools/call`, `initialize`, `resources/read`). Only present on MCP Proxy requests. + + + + Name of the MCP tool, resource, or prompt that was invoked. Only present on MCP Proxy requests. + + + + MCP primitive type invoked: `tool`, `resource`, or `prompt`. Only present on MCP Proxy requests. + + + + HTTP method used in the request (for example, GET or POST). + + + + Organisation ID of the requested API definition. Present whenever the request matched an API. + + + + The client's request path, captured before Tyk Gateway applies any path-modifying middleware, such as stripping the API's listen path. Equal to `path` unless something changes the path during processing. + + + + URL path of the request. + + + + Protocol used in the request (for example, HTTP/1.1). + + + + Remote address of the client. + + + + Detailed error description in snake_case, only present on error requests. + + + + Error classification code, only present on error requests. See [Error Classification](#error-classification). + + + + HTTP response status code. + + + + TLS certificate expiration date in RFC 3339 format, only present on TLS certificate errors. + + + + TLS certificate subject (for example, `CN=api.example.com`), only present on TLS certificate errors. + + + + The OpenTelemetry trace ID for the request (32-character hex W3C trace ID). Only present when OpenTelemetry is enabled and a trace ID is available. Use this to navigate from an access log entry to the corresponding trace in your observability backend. + + + + Full upstream address, including scheme, host, and path. + + + + Round-trip duration between the gateway sending the request to the upstream service and receiving the response. + + + + HTTP status code returned by the upstream, only present when the upstream responds with a 5XX status. + + + + User agent string provided by the client. + + +## Error Classification + +Access logs include a small set of additional fields when a request fails, so you can diagnose problems, such as TLS certificate expiry, connection refusal, or authentication errors, directly from the log without cross-referencing application or API Traffic Logs. Whether a given field appears depends on the specific error; each field's exact conditions are noted in [Access Log Field Reference](#access-log-field-reference) above. Two fields anchor this classification: `response_flag` and `error_source`. + + +Error classification fields are omitted entirely, not set to an empty string or zero, when they don't apply. This keeps successful requests and errors that don't trigger a given field unchanged. + + +### Response Flags + +A response flag is a short, fixed code, such as `UCF` or `AKI`, that classifies the specific error condition Tyk Gateway encountered while processing the request. `response_flag` values are shared with Tyk Gateway's [error response customization](/api-management/custom-error-responses) feature, which uses the same flags to match errors you want to override. See the [Flag Reference](/api-management/custom-error-responses#flag-reference) there for the full list, what each one means, and its typical HTTP status. + +### Error Source + +`error_source` identifies the Gateway component that generated the error, helping you locate where in the request pipeline the failure occurred. It's present alongside `response_flag` on every error entry. + +| error_source | Component | Typical flags | +| :--- | :--- | :--- | +| `ReverseProxy` | Upstream proxy and transport layer | `UCF`, `TLE`, `URT`, `DNS`, `CBO`, `NHU` | +| `Upstream` | The upstream server itself (responded with error) | `URS` | +| `AuthKey` | Auth Token middleware | `AMF`, `AKI`, `TKE` | +| `Oauth2KeyExists` | OAuth2 middleware | `AMF`, `AKI`, `EAD` | +| `JWTMiddleware` | JWT middleware | `AMF`, `TKI`, `TCV`, `TKE` | +| `BasicAuthMiddleware` | Basic Auth middleware | `AMF`, `IHD`, `BIV` | +| `RateLimitAndQuotaCheck` | Rate limiting and quota middleware | `RLT`, `QEX` | +| `APIRateLimitMiddleware` | API-level rate limiting | `RLT` | +| `RequestSizeLimitMiddleware` | Request size limit middleware | `BTL`, `CLM` | +| `ValidateJSONMiddleware` | JSON schema validation middleware | `BIV` | + +### Examples + + + + +When the upstream server refuses the TCP connection, the access log shows: + +``` +time="2025-02-10T14:23:01Z" level=info api_id=abc123 api_name=my-api api_type=oas + error_source=ReverseProxy error_target=api.backend.com:443 + method=GET path=/users prefix=access-log + response_code_details=connection_refused response_flag=UCF status=500 +``` + +Key fields: +- `response_flag=UCF` identifies the error as an Upstream Connection Failure. +- `error_target=api.backend.com:443` shows the specific upstream that refused the connection. +- `upstream_status` is absent because no HTTP response was received from the upstream. + + + + + +When the upstream's TLS certificate has expired: + +``` +time="2025-02-10T14:25:33Z" level=info api_id=abc123 api_name=my-api api_type=oas + error_source=ReverseProxy error_target=api.backend.com:443 + method=GET path=/users prefix=access-log + response_code_details=tls_certificate_expired response_flag=TLE status=500 + tls_cert_expiry=2024-01-15T00:00:00Z tls_cert_subject="CN=api.backend.com" +``` + +The `tls_cert_expiry` and `tls_cert_subject` fields help you identify exactly which certificate needs renewal and when it expired. + + + + + +When a [circuit breaker](/planning-for-production/ensure-high-availability/circuit-breakers) trips for an endpoint, the access log shows: + +``` +time="2025-02-10T14:30:00Z" level=info api_id=abc123 api_name=my-api api_type=oas + circuit_breaker_state=OPEN error_source=ReverseProxy error_target=api.backend.com:443/health + method=GET path=/health prefix=access-log + response_code_details=circuit_breaker_open response_flag=CBO status=503 +``` + + + + + +When a client exceeds the configured rate limit, the access log shows: + +``` +time="2025-02-10T14:31:22Z" level=info api_id=abc123 api_name=my-api api_type=oas + error_source=RateLimitAndQuotaCheck method=POST path=/orders prefix=access-log + response_code_details=session_rate_limited response_flag=RLT status=429 +``` + +Note that `error_target` is absent for gateway-level errors (authentication, rate limiting, validation) because the request did not reach the upstream. + + + + + +When the upstream receives the request but responds with an error status, the access log shows: + +``` +time="2025-02-10T14:32:45Z" level=info api_id=abc123 api_name=my-api api_type=oas + error_source=Upstream error_target=api.backend.com:443 + method=GET path=/health prefix=access-log + response_code_details=upstream_response_5xx response_flag=URS status=503 + upstream_status=503 +``` + +Key distinction: `error_source=Upstream` and `upstream_status=503` indicate that the upstream returned an error, unlike a connection failure, where no response was received. + + + + + +## Performance Considerations + +Enabling access logs introduces some performance overhead: + +- **Latency:** Increases consistently by approximately 4%-13%, depending on CPU allocation and configuration. +- **Memory Usage:** Memory consumption increases by approximately 6%-7%. +- **Allocations:** The number of memory allocations increases by approximately 5%-6%. + + +While the overhead of enabling access logs is noticeable, the impact is relatively modest. These findings suggest the performance trade-off may be acceptable depending on the criticality of logging to your application. + + +## Troubleshooting + + + + + +A common troubleshooting question is whether the upstream received the request or if the connection itself failed. The error classification fields make this distinction clear: + +| Scenario | `error_source` | `upstream_status` | `error_target` | Example flags | +| :--- | :--- | :--- | :--- | :--- | +| Connection failed (upstream never received request) | `ReverseProxy` | absent (0) | present | `UCF`, `UCT`, `DNS`, `TLE` | +| Upstream responded with error | `Upstream` | present (for example, 503) | present | `URS` | +| Gateway rejected request (never proxied) | middleware name | absent | absent | `RLT`, `AMF`, `BTL` | + + + + diff --git a/api-management/logs/application-logs.mdx b/api-management/logs/application-logs.mdx new file mode 100644 index 0000000000..32c0ca1b99 --- /dev/null +++ b/api-management/logs/application-logs.mdx @@ -0,0 +1,69 @@ +--- +title: "Application Logs" +description: "Learn how to configure Tyk's application logs" +keywords: "Logs, Logging, Tyk Gateway, Tyk Dashboard, Tyk Pump, Tyk MDCB, Application Log, System Log" +sidebarTitle: "Application Logs" +--- + +## Introduction + +Every Tyk component writes an Application Log, capturing internal events of the system, such as health-checks, status, configuration changes, and errors, which are typically used for monitoring and debugging. + +**Example** + +``` +time="2025-02-16T22:48:39Z" level=info message="Using Policies from Dashboard Service" prefix=main +time="2025-02-16T22:48:39Z" level=info message="Calling dashboard service for policy list" prefix=policy +time="2025-02-16T22:48:39Z" level=info message="Processing policy list" prefix=policy +time="2025-02-16T22:48:39Z" level=info message="Policies found (5 total):" prefix=main +``` + +Three aspects can be configured: + +- **verbosity**: which severity levels are written +- **format**: the structure and content of each entry +- **log output**: where logs are sent, for example to a third-party aggregator + +See [Configuring Application and Access Logs](/api-management/logs#configuring-application-and-access-logs) for details of these and how to configure them. + +The rest of this page covers behavior specific to **Tyk Gateway's Application Log**. + +## OpenTelemetry Trace and Span IDs + +Gateway Only + +When OpenTelemetry tracing is enabled, Tyk Gateway automatically injects `trace_id` and `span_id` fields into all request-scoped application log entries, including middleware execution, error handling, and debug output. This lets you correlate an application log line with the corresponding span in your distributed trace. + +``` +time="2025-02-16T22:48:39Z" level=error message="Rate limit exceeded" prefix=rate-limit api_id=b1a41c9a89984ffd7bb7d4e3c6844ded trace_id=4bf92f3577b34da6a3ce929d0e0e4736 span_id=00f067aa0ba902b7 +``` + +Non-request-scoped entries (startup, configuration reload, health-checks) do not carry trace or span IDs because they are not associated with a specific request. + +## Tracking HTTP 404 Errors + +Gateway Only + +By default, requests that do not match any configured API listen path return HTTP 404 and are not logged. Because these requests never enter the proxy pipeline, they are not recorded in Access Logs or API Traffic Logs, which only cover requests that are routed and processed by the Gateway. + +To track them in the application log, set [`track_404_logs`](/tyk-oss-gateway/configuration#track_404_logs) to `true` in `tyk.conf` (or the equivalent environment variable). + +When enabled, a log entry is written at `error` level for each unmatched request. + +From **Tyk Gateway 5.14.0**, the entry includes a `host` field when using the `text` or `json` log format. Because Tyk Gateway exposes both a proxy API and a control API, and these can be [configured on a separate hostname and port](/planning-for-production#change-your-control-port), the `host` field identifies which one received the unmatched request: + +``` +time="2025-07-15T10:25:30Z" level=error message="Not Found" prefix=gateway request="GET /nonexistent/path HTTP/1.1" origin=192.168.1.5:54321 host=api.example.com:9696 +``` + +In `legacy` format, the `host` field is omitted: + +``` +time="Jul 15 10:25:30" level=error msg="Not Found" prefix=gateway request="GET /nonexistent/path HTTP/1.1" origin=192.168.1.5:54321 +``` + +## Event Log Handlers + +Gateway Only + +[Event log handlers](/api-management/gateway-events#logging-api-events) can be registered against Gateway events to write a log entry each time a specific event fires, such as an authentication failure or rate limit being exceeded. diff --git a/api-management/logs/audit-logs.mdx b/api-management/logs/audit-logs.mdx new file mode 100644 index 0000000000..fef3c4a56a --- /dev/null +++ b/api-management/logs/audit-logs.mdx @@ -0,0 +1,88 @@ +--- +title: "Dashboard Audit Logs" +description: "Learn how to configure Tyk Dashboard to log user activity" +keywords: "Logs, Logging, Tyk Dashboard, Audit Log" +sidebarTitle: "Audit Logs" +--- + +## Introduction + +Tyk Dashboard's audit log system captures detailed records of all requests made to endpoints under the `/api` route, providing a record of user activity via Dashboard API or in the UI. The audit logs can be written to local files or to the Control Plane's persistent storage database, providing flexible options for log management and retrieval. + +See [Enable and View Audit Logs in Tyk Dashboard](/api-management/enable-audit-logs-dashboard) for a step-by-step guide on using the audit log feature. + +### Enabling the Audit Log + +The audit log is configured within the Tyk Dashboard config file (`tyk_analytics.conf`) or using the equivalent environment variables. + +```json +{ + "audit": { + "enabled": true, + "format": "json", + "path": "/tmp/audit.log", + "detailed_recording": false, + "store_type": "file" + } +} +``` + +| Field | Description | Default | +| :-------- | :--- | :--- | +| `enabled` | Enable audit logging. | `false` | +| `format` | Specifies the log format used when `store_type` is `file`. Valid values are `json` and `text`; ignored when `store_type` is `db`. | `text` | +| `path` | Path to the audit log file on the Dashboard's server. | | +| `detailed_recording` | Enable detailed records in the audit log. If set to `true`, audit log records will contain the http-request (without body) and full http-response including the body. | `false` | +| `store_type` | Specifies the storage in which audit logs will be written. Valid values are `file` and `db`. | `file` | + +Please consult [Tyk Dashboard Configuration Options](/tyk-dashboard/configuration#audit) for equivalent configuration with environment variables. + + +Enabling `detailed_recording` captures the complete HTTP response body from every Dashboard API call. Depending on the endpoint, that response can include sensitive data, such as a newly generated API key, OAuth client secret, or other credentials returned by the call. This data is stored as-is in the audit log, wherever `store_type` points it. Enable this only when you need it, and make sure the resulting audit log has appropriate access controls. + + + +The legacy setting [`security.audit_log_path`](/tyk-dashboard/configuration#security-audit_log_path) can also be used to enable audit logging. This accepts a file path and combines the functionality of `audit.enabled` and `audit.path`. The value configured in `audit.path` will overrule anything set in `security.audit_log_path`. + + +### Audit Log Storage + +If `audit.store_type` is set to `file`, the logs will be added to the file identified using `audit.path`, in JSON or text format depending on the configuration of `audit.format`. In `text` format, each field is written on its own line, in the order listed in the [table below](#audit-log-content). + + +If hosting Tyk Dashboard within a Kubernetes cluster, please ensure that the configured log file path is valid and writeable. + + +If `audit.store_type` is set to `db`, the logs will be added to a collection or table named `audit_records` in the Control Plane's persistent storage, and `audit.format` is ignored: records are always stored in their native structured form, not as `json` or `text`. Database storage has been available since [Tyk Dashboard v5.7.0](/developer-support/release-notes/dashboard#5-7-0-release-notes). + + +Tyk does not manage the size of the audit log file or collection. + + +## Audit Log Content + +For each request made to the Tyk Dashboard API's `/api/*` endpoints, a log will be generated containing the following data: + +| Field | Description | +| :------- | :---- | +| `_id` | Database identifier for the record, assigned when `store_type` is `db`. In `file` storage the record is never inserted into a database, so `json` format includes `_id` as an empty value and `text` format omits it entirely. | +| `req_id` | Unique request ID | +| `org_id` | Organisation ID | +| `date` | Date in *RFC1123* format | +| `timestamp` | UNIX timestamp | +| `ip` | IP address the request originated from | +| `user` | Dashboard user who performed the request | +| `action` | Description of the action performed (for example, Update User) | +| `method` | HTTP request method | +| `url` | URL of the request | +| `status` | HTTP response status of the request | +| `diff` | Provides a diff of changed fields (available only for PUT requests) | +| `request_dump` | HTTP request copy (available if `detailed_recording` is set to `true`) | +| `response_dump` | HTTP response copy (available if `detailed_recording` is set to `true`) | + +## Viewing and Retrieving Audit Logs + +Tyk Dashboard's **System Management > Audit Logs** screen provides a searchable and filterable view of the collected audit logs. + +The [List Audit Logs](https://tyk.io/docs/api-reference/auditlogs/list-audit-logs) endpoint, available since [Tyk Dashboard v5.7.0](/developer-support/release-notes/dashboard#5-7-0-release-notes), provides access to logs stored in the persistent storage. + diff --git a/api-management/logs/external-data-sinks.mdx b/api-management/logs/external-data-sinks.mdx new file mode 100644 index 0000000000..1f03507e4b --- /dev/null +++ b/api-management/logs/external-data-sinks.mdx @@ -0,0 +1,713 @@ +--- +title: "External Data Sinks (Legacy)" +description: "Configure Tyk Pump to forward traffic logs to external tools such as Splunk, Datadog, Elasticsearch, Moesif, and Logz.io." +keywords: "Tyk Pump, Splunk, Datadog, Elasticsearch, Moesif, Logzio, CSV, Legacy" +sidebarTitle: "External Data Sinks (Legacy)" +--- + + +This page describes forwarding traffic logs to external tools using Tyk Pump. [OpenTelemetry tracing](/api-management/traces) is an alternative for new integrations: it exports per-request span data directly from Tyk Gateway, without the Redis and Tyk Pump hop. It's not a like-for-like replacement, though: OTel spans use a different schema from Tyk's traffic log fields, and not every destination on this page has an OTel exporter. Use this page if you need Tyk's traffic log format, a destination without OTel support, or already have an existing deployment built around Tyk Pump. + + +Tyk Pump can forward traffic logs to a range of external tools, each its own pump type, in parallel with, or instead of, the [pumps that populate Tyk Dashboard Analytics](/api-management/dashboard-analytics). Every pump on this page is declared the same way, under the `pumps` object in `pump.conf`, and every pump also accepts the [common pump settings](/api-management/tyk-pump#common-pump-settings) (`filters`, `timeout`, and so on) in addition to its own fields shown below. + +Every pump on this page forwards each traffic log largely as-is, mapped into that destination's native format. + + +None of the pumps on this page ever receive aggregated analytics, whichever way you deploy them. Aggregation only ever feeds Tyk Dashboard's Traffic Analytics graphs; see [How Aggregation Works](/api-management/dashboard-analytics#how-aggregation-works). External sinks only ever see individual traffic logs. + + +This page covers every External Data Sink pump. StatsD, DogStatsD, and Prometheus are metrics pumps, not sink pumps, so they're covered on [Metrics Pumps](/api-management/metrics/metrics-pumps) instead; see the [pump type catalog](/api-management/tyk-pump#pump-type-catalog) for the full breakdown. + +## Combined vs Distributed Deployments + +In a combined control and data plane, these pumps work exactly like the ones that feed Tyk Dashboard: Tyk Pump reads from the shared Redis and writes to each configured destination in parallel. + +In a distributed deployment, with separate control and data planes connected via Tyk MDCB, you have two options, and they're not equivalent: + +- **Run the sink pump directly on the data plane**, alongside the [Hybrid Pump](/api-management/dashboard-analytics/data-plane-pump), reading from the same local Redis. This is the simpler option. +- **Run the sink pump on the control plane**, as a [Control Plane Pump](/api-management/dashboard-analytics/control-plane-pumps), receiving traffic logs the Hybrid Pump has forwarded via Tyk MDCB. This requires `forward_analytics_to_pump: true`. See [Data Plane Pump](/api-management/dashboard-analytics/data-plane-pump#how-tyk-mdcb-handles-the-data) for that mechanism in full. + +## CSV + +Tyk Pump can create or append to a CSV file to track API traffic logs. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `csv` pump accepts the following `meta` field: + +```json +{ + "pumps": { + "csv": { + "type": "csv", + "meta": { + "csv_dir": "./your_directory_here" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `csv_dir` | - | Directory the CSV file is written to; created automatically if it doesn't exist. **Required** | + +Limitations: + +- Writes one file per hour, named `YYYY-Month-DD-HH.csv`, inside `csv_dir`. +- Every traffic log field is written; there's no `fields` filtering support. + +## Stdout + +Writes traffic logs to standard output as JSON, useful for sidecar log collectors such as the Datadog logging agent in Kubernetes. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `stdout` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "stdout": { + "type": "stdout", + "meta": { + "log_field_name": "tyk-analytics-record", + "format": "json", + "use_legacy_payload_format": false + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `log_field_name` | `tyk-analytics-record` | Root field name the traffic log is nested under. | +| `format` | `text` | `text` or `json`. With `json`, every Tyk Pump log written to stdout becomes JSON, not just this pump's output. | +| `use_legacy_payload_format` | `false` | With `format: json`, renders `raw_request`/`raw_response` as legacy escaped strings instead of the newer JSON-aware format. | + +## Syslog + +Sends traffic logs to a syslog daemon. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `syslog` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "syslog": { + "type": "syslog", + "meta": { + "transport": "udp", + "network_addr": "localhost:5140", + "log_level": 6, + "tag": "syslog-pump" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `transport` | `udp` | `udp`, `tcp`, or `tls`. | +| `network_addr` | `localhost:5140` | Host and port of your syslog daemon. | +| `log_level` | - | Severity level, an integer from 0-7, following the [syslog severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_level) standard. | +| `tag` | `syslog-pump` | Prefix tag. | + +Limitations: + +- If working with FluentD, use a [FluentD parser](https://docs.fluentd.org/input/syslog) matching your OS so FluentD reads the logs correctly. + +## Open Source Data Sinks + +These sinks are open source software you run and manage yourself. + +### Elasticsearch + +[Elasticsearch](https://www.elastic.co/) is a highly scalable, distributed search engine for large volumes of data. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `elasticsearch` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "elasticsearch": { + "type": "elasticsearch", + "meta": { + "index_name": "tyk_analytics", + "elasticsearch_url": "http://localhost:9200", + "use_sniffing": false, + "document_type": "tyk_analytics", + "rolling_index": false, + "extended_stats": false, + "version": "3", + "disable_bulk": false, + "bulk_config": {}, // only if disable_bulk: false + "use_ssl": false, + "ssl_insecure_skip_verify": false, + "ssl_cert_file": "", // only if use_ssl: true + "ssl_key_file": "", // only if use_ssl: true + "ssl_ca_file": "", // only if use_ssl: true + "generate_id": false, + "decode_base64": false, + "mcp_index_name": "", + "auth_api_key_id": "", // alternative to auth_basic_username/auth_basic_password + "auth_api_key": "", + "auth_basic_username": "", // alternative to auth_api_key_id/auth_api_key + "auth_basic_password": "" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `index_name` | `tyk_analytics` | Index for all analytics data. | +| `elasticsearch_url` | `http://localhost:9200` | Target URL, used when `use_sniffing` is `false`. | +| `use_sniffing` | `false` | If `true`, uses `elasticsearch_url` to discover all cluster node addresses, then uses those instead. | +| `document_type` | `tyk_analytics` | The document type created in Elasticsearch. | +| `rolling_index` | `false` | Appends the date to the index name, for example `tyk_analytics-2016.02.28`, so each day is a separate index. | +| `extended_stats` | `false` | If `true`, includes `Raw Request`, `Raw Response`, and `User Agent` fields. | +| `version` | `3` | The Elasticsearch major version: `3`, `5`, `6`, or `7`. | +| `disable_bulk` | `false` | Disables batch writing. | +| `bulk_config` | - | Batch writing triggers, evaluated as an OR of each other: `workers` (default `1`), `flush_interval` in seconds (disabled by default), `bulk_actions` (default `1000`, `-1` to disable), and `bulk_size` in bytes (default 5MB, `-1` to disable). | +| `use_ssl` | `false` | Enables an SSL connection. | +| `ssl_insecure_skip_verify` | `false` | Skips verifying the Elasticsearch server's certificate chain and host name. | +| `ssl_cert_file`, `ssl_key_file` | - | Paths to the PEM client certificate and private key for authenticating with the Elasticsearch server. | +| `ssl_ca_file` | - | Path to the PEM file of trusted CA certificates used to verify the Elasticsearch server's certificate. | +| `generate_id` | `false` | Generates a deterministic document ID per record, to prevent duplicates if a record is retried after a failed write. | +| `decode_base64` | `false` | Decodes the `raw_request`/`raw_response` fields from base64 before sending to Elasticsearch. | +| `auth_api_key_id`, `auth_api_key` | - | Authenticate to Elasticsearch using an API key, sent via the `Authorization` header. | +| `auth_basic_username`, `auth_basic_password` | - | Authenticate to Elasticsearch using HTTP basic auth. | +| `mcp_index_name` | (none) | When set, MCP records are written to this index instead of `index_name`, and follow the same `rolling_index` date-suffix behavior. Leave unset to keep MCP records in the same index as everything else. | + + +`ssl_insecure_skip_verify` disables all TLS certificate validation for this connection. Only use it for local development or testing, never in production, it exposes the connection to man-in-the-middle attacks. + + +### Graylog + +Sends traffic logs to [Graylog](https://graylog.org/). + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `graylog` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "graylog": { + "type": "graylog", + "meta": { + "host": "10.60.6.15", + "port": 12216, + "tags": ["path", "method", "response_code", "api_name"] + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `host` | `localhost` | Graylog host. | +| `port` | `1000` | Graylog port. | +| `tags` | (none) | Traffic log fields to send. If left empty, nothing is sent. Available values: `path`, `method`, `response_code`, `api_key`, `api_version`, `api_name`, `api_id`, `org_id`, `oauth_id`, `raw_request`, `raw_response`, `request_time`, and `ip_address`. | + +### InfluxDB + +Tyk Pump can write traffic logs to [InfluxDB](https://www.influxdata.com/) 1.x. For InfluxDB 2.x, use [Influx2](#influx2) below instead. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `influx` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "influx": { + "type": "influx", + "meta": { + "database_name": "tyk_analytics", + "address": "http://localhost:8086", + "username": "", + "password": "", + "fields": ["request_time"], + "tags": ["method", "path", "response_code"] + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `database_name` | - | The InfluxDB database name. | +| `address` | - | The InfluxDB host. | +| `username`, `password` | - | InfluxDB credentials. | +| `fields` | all available | Which traffic log fields to send as point fields. Available values, all included by default: `method`, `path`, `response_code`, `api_key`, `time_stamp`, `api_version`, `api_name`, `api_id`, `org_id`, `oauth_id`, `raw_request`, `request_time`, `raw_response`, and `ip_address`. | +| `tags` | (none) | Which of those same fields act as tags on the point instead. | + +Limitations: + +- Points are written to a fixed measurement, `analytics`; this isn't configurable. + +### Influx2 + +For InfluxDB 1.x, use [InfluxDB](#influxdb) above instead. Uses the official Go client library for InfluxDB 2.x. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `influx2` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "influx2": { + "type": "influx2", + "meta": { + "organization": "my-org", + "bucket": "my-bucket", + "address": "http://localhost:8086", + "token": "my-super-secret-auth-token", + "create_missing_bucket": false, + "new_bucket_config": {}, // only if create_missing_bucket: true + "fields": ["request_time"], + "tags": ["method", "path", "response_code"], + "flush": false + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `organization` | - | The InfluxDB organization name. | +| `bucket` | - | The InfluxDB bucket analytics data is stored in. | +| `address` | - | The InfluxDB host. | +| `token` | - | The InfluxDB auth token. | +| `create_missing_bucket` | `false` | Creates the bucket if it doesn't exist. | +| `new_bucket_config` | - | Used when `create_missing_bucket` is `true`: `description` (shown in the Influx UI) and `retention_rules`, a list of rules such as `{"every_seconds": 100000, "shard_group_duration_seconds": 0, "type": "expire"}` to expire data after a set period. | +| `fields` | - | Which traffic log fields to send as point fields. | +| `tags` | - | Which fields act as tags on the time series. | +| `flush` | `false` | Writes each record to InfluxDB as soon as Tyk Pump receives it, rather than batching. | + +### Kafka + +Publishes traffic logs to a [Kafka](https://kafka.apache.org/) topic. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `kafka` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "kafka": { + "type": "kafka", + "meta": { + "broker": ["localhost:9092"], + "topic": "tyk-pump", + "client_id": "tyk-pump", + "timeout": 60, + "use_ssl": false, + "ssl_insecure_skip_verify": false, + "ssl_cert_file": "", // only if use_ssl: true + "ssl_key_file": "", // only if use_ssl: true + "ssl_ca_file": "", // only if use_ssl: true + "compressed": false, + "batch_bytes": 0, + "meta_data": {}, + "sasl_mechanism": "", // enables SASL auth: "plain" or "scram" + "sasl_username": "", // only if sasl_mechanism is set + "sasl_password": "", // only if sasl_mechanism is set + "sasl_algorithm": "sha-256" // only if sasl_mechanism is "scram" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `broker` | - | List of brokers used to discover the partitions available on the Kafka cluster, for example `localhost:9092`. | +| `client_id` | - | Unique identifier for connections established with Kafka. | +| `topic` | - | The topic messages are produced to. | +| `timeout` | - | Maximum time, in seconds, to wait for a connect or write to complete. | +| `compressed` | `false` | Compresses Kafka messages using the Snappy codec. | +| `meta_data` | - | Custom metadata to attach to each Kafka message. | +| `batch_bytes` | `0` | Maximum request size, in bytes, before it's sent to a partition. If `0`, uses the `kafka-go` library's own default (1MB). | +| `use_ssl` | `false` | Enables an SSL connection. | +| `ssl_insecure_skip_verify` | `false` | Skips verifying the Kafka server's certificate chain and host name. | +| `ssl_cert_file`, `ssl_key_file` | - | PEM client certificate and private key for authenticating with the Kafka server. | +| `ssl_ca_file` | - | Path to the PEM file of trusted CA certificates used to verify the Kafka server's certificate. | +| `sasl_mechanism` | - | Enables SASL authentication: `plain` or `scram`. | +| `sasl_username`, `sasl_password` | - | SASL credentials. | +| `sasl_algorithm` | `sha-256` | Hash algorithm for SCRAM: `sha-256` or `sha-512`. | + + +`ssl_insecure_skip_verify` disables all TLS certificate validation for this connection. Only use it for local development or testing, never in production, it exposes the connection to man-in-the-middle attacks. + + + + Kafka brokers commonly auto-create a topic on its first use. If you point Tyk Pump at a topic that doesn't exist yet, its first write attempt can fail with `Unknown Topic Or Partition` while the broker creates it, silently dropping that batch. Create the topic before starting Tyk Pump against it, so no records are lost. + + +#### Working Example + +The example below connects to a Kafka cluster secured with SASL\_SSL and SCRAM-SHA-256 authentication, the setup used by Confluent Cloud, Amazon MSK, and most managed Kafka clusters: + + + +```json +{ + "pumps": { + "kafka": { + "type": "kafka", + "meta": { + "broker": ["your-cluster.kafka.example.com:9092"], + "topic": "tyk-pump", + "client_id": "tyk-pump", + "timeout": 30, + "use_ssl": true, + "ssl_ca_file": "/etc/tyk-pump/certs/ca.pem", + "sasl_mechanism": "scram", + "sasl_username": "tyk-pump", + "sasl_password": "", + "sasl_algorithm": "sha-256" + } + } + } +} +``` + + +```bash +TYK_PMP_PUMPS_KAFKA_TYPE=kafka +TYK_PMP_PUMPS_KAFKA_META_BROKER=your-cluster.kafka.example.com:9092 +TYK_PMP_PUMPS_KAFKA_META_TOPIC=tyk-pump +TYK_PMP_PUMPS_KAFKA_META_CLIENTID=tyk-pump +TYK_PMP_PUMPS_KAFKA_META_TIMEOUT=30 +TYK_PMP_PUMPS_KAFKA_META_USESSL=true +TYK_PMP_PUMPS_KAFKA_META_SSLCAFILE=/etc/tyk-pump/certs/ca.pem +TYK_PMP_PUMPS_KAFKA_META_SASLMECHANISM=scram +TYK_PMP_PUMPS_KAFKA_META_USERNAME=tyk-pump +TYK_PMP_PUMPS_KAFKA_META_PASSWORD=your-password +TYK_PMP_PUMPS_KAFKA_META_ALGORITHM=sha-256 +``` +Set `TYK_PMP_OMITCONFIGFILE=true` if you're configuring Tyk Pump purely from environment variables, with no `pump.conf` at all. + + + + +## Commercial and SaaS Data Sinks + +These sinks are commercial products or hosted SaaS platforms. + +### Logz.io + +[Logz.io](https://logz.io/) is a cloud-based log management and analytics platform built on Elasticsearch, Logstash, and Kibana. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `logzio` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "logzio": { + "type": "logzio", + "meta": { + "token": "", + "url": "https://listener.logz.io:8071", + "queue_dir": "", + "drain_duration": "3s", + "disk_threshold": 98, + "check_disk_space": true + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `token` | - | Token for sending data to your Logz.io account. **Required** | +| `url` | `https://listener.logz.io:8071` | Use a non-default Logz.io URL, for example when proxying. | +| `queue_dir` | A generated path under the OS temp directory | Directory used for the on-disk send queue. | +| `drain_duration` | `3s` | How often queued logs are flushed to disk. | +| `disk_threshold` | `98` | Disk usage percentage at which the sender stops enqueuing received logs. | +| `check_disk_space` | `true` | Whether the sender checks disk usage against `disk_threshold`. | + +### Moesif + +[Moesif](https://www.moesif.com/solutions/track-api-program?language=tyk-api-gateway&utm_medium=docs&utm_campaign=partners&utm_source=tyk) provides API analytics and usage-based billing. With the Moesif pump, traffic logs are sent to Moesif asynchronously, including payloads, and Moesif collects the authenticated user (Alias ID or OAuth ID) to identify customers. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `moesif` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "moesif": { + "type": "moesif", + "meta": { + "application_id": "Your Moesif Application Id", + "user_id_header": "", + "company_id_header": "", + "request_header_masks": [], + "response_header_masks": [], + "request_body_masks": [], + "response_body_masks": [], + "authorization_header_name": "authorization", + "authorization_user_id_field": "sub", + "disable_capture_request_body": false, + "disable_capture_response_body": false, + "enable_bulk": false + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `application_id` | - | Moesif Application ID. Multiple Tyk API IDs are logged under the same app ID. **Required** | +| `user_id_header` | token alias | Header identifying the user. By default, Moesif identifies the customer from the authenticated user (Alias ID or OAuth ID); override with a header such as `X-Consumer-Id`. | +| `company_id_header` | - | Header identifying the company or account, to link a user to a company. | +| `request_header_masks`, `response_header_masks` | - | Mask a specific request or response header field. | +| `request_body_masks`, `response_body_masks` | - | Mask a specific request or response body field. | +| `disable_capture_request_body`, `disable_capture_response_body` | `false` | Disable logging of the request or response body. | +| `authorization_header_name` | `authorization` | Request header used to identify the user in Moesif. | +| `authorization_user_id_field` | `sub` | Field used to parse the user from the authorization header. | +| `enable_bulk` | `false` | Enables the `bulk_config` batching options: `event_queue_size` (default `10000`), `batch_size` (default `200`), and `timer_wake_up_seconds` (default `2`). | + +Limitations: + +- Moesif needs [detailed recording](/api-management/logs/traffic-logs#detailed-recording) enabled to log HTTP headers and bodies. +- See [Moesif's documentation on identifying customers](https://www.moesif.com/docs/getting-started/identify-customers/?utm_medium=docs&utm_campaign=partners&utm_source=tyk) for `user_id_header`/`company_id_header`. + +### Resurface.io + +[Resurface](https://resurface.io/) captures every API call as a durable transaction in a purpose-built data lake, for attack and failure triage, root cause analysis, and usage monitoring. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `resurfaceio` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "resurfaceio": { + "type": "resurfaceio", + "meta": { + "capture_url": "http://localhost:7701/message", + "rules": "include debug" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `capture_url` | - | The Resurface database's [capture endpoint URL](https://resurface.io/docs/#getting-capture-url). **Required** | +| `rules` | - | An [active set of rules](https://resurface.io/logging-rules) controlling what's logged and how sensitive data is masked. **Required** | + +Limitations: + +- Requires [detailed recording](/api-management/logs/traffic-logs#detailed-recording) enabled to capture full API call details. + +### Segment + +Tyk Pump can send traffic logs to [Segment](https://segment.com/) as `Track` events. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `segment` pump accepts the following `meta` field: + +```json +{ + "pumps": { + "segment": { + "type": "segment", + "meta": { + "segment_write_key": "" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `segment_write_key` | - | Your Segment source's write key. **Required** | + +Limitations: + +- Every traffic log field is sent as the event's properties; there's no `fields`/`tags` filtering support. +- Each event is named `Hit`, with `AnonymousId` set to the request's API key. + +### Splunk + +Sends traffic logs to [Splunk](https://www.splunk.com/) via its HTTP Event Collector (HEC). + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `splunk` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "splunk": { + "type": "splunk", + "meta": { + "collector_token": "", + "collector_url": "https://localhost:8088/services/collector/event", + "fields": [], + "obfuscate_api_keys": false, + "obfuscate_api_keys_length": 0, + "ignore_tag_prefix_list": [], + "enable_batch": false, + "batch_max_content_length": 838860800, + "max_retries": 0, + "ssl_insecure_skip_verify": false, + "ssl_cert_file": "", // only for mutual TLS to the HEC endpoint + "ssl_key_file": "", // only for mutual TLS to the HEC endpoint + "ssl_ca_file": "", // only for mutual TLS to the HEC endpoint + "ssl_server_name": "" // only if it differs from the hostname in collector_url + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `collector_token` | - | The HEC token from your Splunk collector. **Required** | +| `collector_url` | - | Your Splunk HEC endpoint, for example `https://splunk:8088/services/collector/event`. **Required** | +| `obfuscate_api_keys` | `false` | Masks the API key in each event. | +| `obfuscate_api_keys_length` | `0` | With `obfuscate_api_keys: true`, keeps this many characters from the end of the API key rather than masking it fully. | +| `fields` | all available | Restricts which traffic log fields are included in each Splunk event. Defaults to `method`, `path`, `response_code`, `api_key`, `time_stamp`, `api_version`, `api_name`, `api_id`, `org_id`, `oauth_id`, `raw_request`, `request_time`, `raw_response`, and `ip_address`. | +| `ignore_tag_prefix_list` | (none) | Excludes tags, by prefix, from the event. | +| `enable_batch` | `false` | Sends traffic logs to Splunk in batches rather than individually. | +| `batch_max_content_length` | `838860800` (~800MB) | Maximum batch size in bytes, matching Splunk's own `max_content_length` default. If purged traffic logs don't reach this size before the next purge loop, they're sent anyway. | +| `max_retries` | `0` | Maximum retry attempts for failed requests to the Splunk HEC. Connection, network, timeout, rate-limit, and server errors are all retried. | +| `ssl_insecure_skip_verify` | `false` | Skips verifying the Splunk server's certificate chain and host name. | +| `ssl_cert_file`, `ssl_key_file` | - | PEM client certificate and private key for authenticating with the Splunk server. | +| `ssl_ca_file` | - | Path to the PEM file of trusted CA certificates used to verify the Splunk server's certificate. | +| `ssl_server_name` | - | Server name used for the TLS handshake, if it differs from the hostname in `collector_url`. | + + +`ssl_insecure_skip_verify` disables all TLS certificate validation for this connection. Only use it for local development or testing, never in production, it exposes the connection to man-in-the-middle attacks. + + +## AWS Pumps + +Timestream, SQS, and Kinesis all authenticate using the official AWS Go SDK's default credential chain: environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, or `AWS_WEB_IDENTITY_TOKEN_FILE`), the shared credentials file under `.aws` in your home directory, an ECS task role, or an EC2 instance role, in that order. See the [AWS SDK documentation](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials) for details. Without credentials from one of these sources, the pump can't connect. The SQS pump additionally accepts explicit `aws_key`/`aws_secret`/`aws_token` fields in its own config, which override the default credential chain when set. + +### Kinesis + +Publishes traffic logs to an [Amazon Kinesis](https://aws.amazon.com/kinesis/) stream. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `kinesis` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "kinesis": { + "type": "kinesis", + "meta": { + "stream_name": "my-stream", + "region": "eu-west-2", + "batch_size": 100, + "kms_key_id": "" + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `stream_name` | - | The name of your Kinesis stream in the specified AWS region. **Required** | +| `region` | - | The AWS region your Kinesis stream is located in, for example `eu-west-2`. **Required** | +| `batch_size` | `100` | Maximum records per batch. Kinesis limits each batch to 5MiB; lower this if your records are large enough to risk failed delivery. | +| `kms_key_id` | (none) | AWS KMS key ID used to encrypt records in the stream. Records are unencrypted if omitted. | + +### SQS + +Publishes traffic logs to an [Amazon SQS](https://aws.amazon.com/sqs/) queue. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `sqs` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "sqs": { + "type": "sqs", + "meta": { + "aws_queue_name": "access-logs-queue.fifo", + "aws_region": "us-east-1", + "aws_key": "", // overrides the default AWS credential chain + "aws_secret": "", // overrides the default AWS credential chain + "aws_token": "", // overrides the default AWS credential chain + "aws_endpoint": "", + "aws_message_group_id": "message_group_id", + "aws_sqs_batch_limit": 10, + "aws_message_id_deduplication_enabled": false, + "aws_delay_seconds": 0 + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `aws_queue_name` | - | The name of the AWS SQS queue messages are sent to. **Required** | +| `aws_region` | - | The AWS region where the SQS queue is located. **Required** | +| `aws_key`, `aws_secret`, `aws_token` | - | Explicit AWS credentials, overriding the default credential chain above when set. | +| `aws_endpoint` | - | Custom endpoint URL for AWS SQS, if applicable. | +| `aws_message_group_id` | - | The SQS message group ID, for ordered processing. | +| `aws_sqs_batch_limit` | - | Maximum number of messages per batch sent to the queue. | +| `aws_message_id_deduplication_enabled` | `false` | Deduplicates messages by unique ID to prevent unintended duplicates. | +| `aws_delay_seconds` | `0` | Delay, in seconds, before a sent message becomes available for processing. | + +### Timestream + +Writes traffic logs to [Amazon Timestream](https://aws.amazon.com/timestream/). + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `timestream` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "timestream": { + "type": "timestream", + "meta": { + "aws_region": "us-east-1", + "timestream_table_name": "tyk-pump-table", + "timestream_database_name": "tyk-pump", + "write_rate_limit": false, + "read_geo_from_request": false, + "write_zero_values": false, + "dimensions": ["Method", "Host", "Path", "APIKey"], + "measures": ["ResponseCode", "RequestTime", "Latency.Total"], + "field_name_mappings": {} + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `aws_region` | - | The AWS region that contains the Timestream database. **Required** | +| `timestream_database_name` | - | The Timestream database name that contains the table being written to. **Required** | +| `timestream_table_name` | - | The table name where the data is written. **Required** | +| `dimensions` | - | Which traffic log fields to write as Timestream dimensions. Available values: `Method`, `Host`, `Path`, `RawPath`, `APIKey`, `APIVersion`, `APIName`, `APIID`, `OrgID`, `OauthID`. **Required** | +| `measures` | - | Which traffic log fields, including nested ones such as `Latency.Total`, to write as Timestream measures. Available values: `ContentLength`, `ResponseCode`, `RequestTime`, `NetworkStats.*`, `Latency.Total`, `Latency.Upstream`, `IPAddress`, `UserAgent`, `RawRequest`, `RawResponse`, `RateLimit.*`, and `GeoData.*`. **Required** | +| `write_rate_limit` | `false` | Records `RateLimit` measures extracted from the response headers. | +| `read_geo_from_request` | `false` | Reads geolocation information from the request headers, if not already present on the traffic log. | +| `write_zero_values` | `false` | If `true`, numerical values equal to zero are recorded rather than omitted. | +| `field_name_mappings` | - | Renames traffic log fields to your preferred Timestream field names. | diff --git a/api-management/logs/traffic-logs.mdx b/api-management/logs/traffic-logs.mdx new file mode 100644 index 0000000000..ba66a22da4 --- /dev/null +++ b/api-management/logs/traffic-logs.mdx @@ -0,0 +1,485 @@ +--- +title: "Traffic Logs" +description: "Learn about Tyk Gateway's API traffic logs, and get a full field-by-field reference for every field they contain." +keywords: "Logs, Logging, Tyk Gateway, Tyk Pump, Traffic Log, API Request, Fields Reference" +sidebarTitle: "Traffic Logs" +--- + +## Introduction + +A Traffic Log is a structured record of an individual API request and response, generated by Tyk Gateway and written to Redis. + +[Tyk Pump](/api-management/tyk-pump) is used to read these from Redis and distribute them to Tyk Dashboard's [Traffic Analytics and Log Browser](/api-management/dashboard-analytics), and to [external analytics backends](/api-management/logs/external-data-sinks). + +This page covers how Traffic Logs are generated and stored, and contains a full reference for every field they contain. + +### Traffic Log vs Access Log + +Both Traffic Logs and [Access Logs](/api-management/logs/access-logs) record individual requests, but serve different purposes: + +| | Traffic Log | Access Log | +| :--- | :--- | :--- | +| Written to | Redis, then onto Tyk Pump's configured destinations | `stderr` directly | +| Timing | Asynchronous: read in batches from Redis by Tyk Pump | Real-time: written as the request completes | +| Detail | Richer: full field set, with optional request/response bodies | Lightweight: limited to a configured template of fields | +| Typical use | Tyk Dashboard's Traffic Analytics and Log Browser | External log aggregators, real-time troubleshooting | + +Use a Traffic Log when you need Tyk Dashboard's Traffic Analytics or Log Browser, or want request data routed to an external analytics backend via Tyk Pump. + +Use an Access Log when you want a lightweight, real-time per-request line in your existing log pipeline. + +## How Traffic Logs Are Generated + +When a client makes a request, Tyk Gateway generates a Traffic Log capturing the request and response details, and writes it to Redis when the request completes. + +Redis is a temporary, high-throughput buffer, not intended as a long-term store. [Tyk Pump](/api-management/tyk-pump) reads and purges the logs from there; see [Dashboard Analytics](/api-management/dashboard-analytics#where-the-data-comes-from) for what happens to the data after that. + +Enable traffic logging by setting [`enable_analytics`](/tyk-oss-gateway/configuration#enable_analytics) in the Gateway configuration: + +```json +{ + "enable_analytics": true +} +``` + +To exclude specific APIs or endpoints entirely, use the [do-not-track middleware](/api-management/traffic-transformation/do-not-track). + +Individual request and response headers are not captured by default, aside from `user_agent`. The full set of headers, and the request and response bodies, are only captured when [Detailed Recording](#detailed-recording) is enabled. + +### Controlling Which Endpoints Are Tracked + +The [`track_path`](#param-track-path) field is set on each Traffic Log by the **Track Endpoint** middleware (`trackEndpoint` in Tyk OAS, `track_endpoints` in Tyk Classic), enabled per endpoint on the API definition: + +- Endpoints with Track Endpoint enabled get `track_path: true`. +- Endpoints without it get `track_path: false`. + +For Tyk OAS APIs, enable it under the operation's ID in the `x-tyk-api-gateway.middleware.operations` section of the API definition. For example, to enable tracking for the `GET /status/200` operation: + +```yaml +x-tyk-api-gateway: + middleware: + operations: + status/200get: + trackEndpoint: + enabled: true +``` + + +This only determines the value of the `track_path` field. It's [Tyk Dashboard's aggregation](/api-management/dashboard-analytics#controlling-which-endpoints-are-tracked) that uses this field to decide whether an endpoint is broken out individually in its per-endpoint breakdowns. Despite the similar name, this is unrelated to [Do Not Track](/api-management/traffic-transformation/do-not-track), which suppresses the Traffic Log entirely. + + +### Detailed Recording + +Tyk Gateway does not usually include the full request and response, with headers and body, in Traffic Logs, to keep them small and to avoid capturing sensitive data. + +If this level of detail is required, for example when debugging an API, you can use Tyk's **detailed recording** option. With this enabled, the complete request and response are included in wire format, base64-encoded, in the Traffic Log's [`raw_request`](#param-raw-request) and [`raw_response`](#param-raw-response) fields respectively. + +**Traffic logs generated for GraphQL APIs automatically include full request and response**, regardless of these config settings. See [GraphQL Fields](#graphql-fields). + + +Detailed recording captures the complete, unmodified request and response, including headers and body. + +If your APIs handle sensitive data, such as personally identifiable information, payment details, or authentication tokens and credentials, that data is captured too, and only base64-encoded, not encrypted, so it's trivially readable by anyone with access to the Traffic Log: in Tyk Dashboard's Log Browser, in Redis and persistent storage, or at any external destination Tyk Pump forwards it to. This can have real implications for regulatory compliance, such as GDPR or HIPAA. + +In production environments, ensure that you scope the use of detailed recording as narrowly as possible, at the [API](#api-level) or [Key](#key-level) level rather than Gateway-wide, and use [`omit_detailed_recording`](/api-management/tyk-pump#omit-detailed-recording) on any pump forwarding to a destination that doesn't need this data. + +Enabling detailed recording also significantly increases record size and storage requirements. Tyk Cloud users can enable it per API using the instructions below, or at the Gateway level through a support request; traffic logs are subject to the subscription's storage quota. + + +There are three levels of granularity at which detailed recording of logs can be configured: + +1. [API level](#api-level) to capture detailed records for all requests made to a specific API +2. [Key level](#key-level) to capture detailed records for all requests made by a specific access token +3. [Gateway level](#gateway-level) to capture detailed records for all requests to APIs on the Gateway + + + + +Set [`server.detailedActivityLogs.enabled`](/api-management/gateway-config-tyk-oas#detailedactivitylogs) in the Tyk Vendor Extension, or use the **Record payload in traffic logs** option in the API Designer. + +Enabling detailed activity logs for a Tyk OAS API + +If using Tyk Classic APIs, set the equivalent [`enable_detailed_recording`](/api-management/gateway-config-tyk-classic#param-enable-detailed-recording), or use **Enable Detailed Logging** in **Core Settings** in the Tyk Classic API Designer. + +Enabling detailed activity logs for a Tyk Classic API + +With Tyk Operator, set `spec.enable_detailed_recording` to `true`: + +```yaml {linenos=true, linenostart=1, hl_lines=["10-10"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + enable_detailed_recording: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + + + + +Enable **Enable Detailed Logging** on the **2. Configurations** tab of the Key Designer to record detailed request and response data for transactions using that key (Session) only, useful for debugging a specific consumer. + +Alternatively, add the following to the root of the Session's JSON, for example when creating or updating keys via the [Keys API](/tyk-dashboard-api): + +```json +"enable_detailed_recording": true +``` + + +This setting is not available on [Policies](/api-management/policies), only directly on individual [Sessions](/api-management/access-control/sessions-and-keys/understanding-sessions). + + + + + +Enable [detailed recording](/tyk-oss-gateway/configuration#analytics_config-enable_detailed_recording) in `tyk.conf`, which affects all APIs on the Gateway: + +```json +{ + "enable_analytics": true, + "analytics_config": { + "enable_detailed_recording": true + } +} +``` + + + + + +Tyk Pump has additional, pump-specific options to give you granular control over whether and how this recorded data reaches its destination: see [Omit Detailed Recording](/api-management/tyk-pump#omit-detailed-recording) and [Decode Raw Request and Raw Response](/api-management/tyk-pump#decode-raw-request-and-raw-response). + + +### Custom Tags + +Tyk Gateway can tag a Traffic Log with the value of any HTTP request header, such as `X-Account-ID`. This is useful when you need to distinguish traffic beyond the [standard fields](#traffic-log-field-reference), for example when several sub-accounts share a single API key and you need to differentiate between them in the logs. + +Configure custom tags by adding the header name to `middleware.global.trafficLogs.tagHeaders` in the Tyk Vendor Extension. For example: + +```yaml +x-tyk-api-gateway: + middleware: + global: + trafficLogs: + tagHeaders: + - x-account-id +``` + +This will tag every Traffic Log for a request that includes an `x-account-id` header, using the value `x-account-id-`. + +A request sent with `--header "x-account-id: 1234"` therefore produces a Traffic Log tagged `x-account-id-1234`, added to the [`tags`](#param-tags) field. + +If using Tyk Classic APIs, the equivalent field is [tag_headers](/api-management/gateway-config-tyk-classic#traffic-logs), also available in the Tyk Classic API Designer under **Advanced Options**: + +Tag Headers option in the Tyk Classic API Designer + +With Tyk Operator, set `spec.tag_headers`: + +```yaml {linenos=true, linenostart=1, hl_lines=["10-12"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-tag-headers +spec: + name: httpbin-tag-headers + use_keyless: true + protocol: http + active: true + tag_headers: + - Host + - User-Agent + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-tag-headers + strip_listen_path: true +``` + + +Tyk Dashboard's Aggregate Pumps can compute hourly aggregates per distinct tag value observed; see [Custom Aggregation Tags](/api-management/dashboard-analytics#custom-aggregation-tags) for that usage. + + +## Traffic Log Content + +Tyk Gateway writes each Traffic Log as a structured record, which Tyk Pump then serializes into whatever format a given [Pump](/api-management/tyk-pump) requires. An illustrative JSON representation, using the field names documented below, looks like: + +```json +{ + "method": "GET", + "host": "tyk.io", + "path": "/foo/bar", + "raw_path": "/foo/bar", + "original_path": "/api/v1/users", + "listen_path": "/api/v1/", + "content_length": 10, + "user_agent": "curl/7.86.0", + "raw_request": "", + "response_code": 200, + "raw_response": "", + "request_time": 3, + "latency": {"total": 4, "upstream": 3, "gateway": 1}, + "api_id": "727dad853a8a45f64ab981154d1ffdad", + "api_name": "Foo API", + "api_version": "1", + "org_id": "5e9d9544a1dcd60001d0ed20", + "api_key": "6129dc1e8b64c6b4", + "oauth_id": "", + "alias": "my-key-alias", + "timestamp": "2022-11-16T03:01:54.648+00:00", + "day": 16, + "month": 11, + "year": 2022, + "hour": 3, + "expireAt": "2022-11-23T07:26:25.762+00:00", + "ip_address": "172.18.0.1", + "geo": {"country": {"iso_code": "SG"}, "city": {}, "location": {}}, + "network": {"open_connections": 0, "closed_connections": 0, "bytes_in": 0, "bytes_out": 0}, + "tags": ["key-00000000", "org-5e9d9544a1dcd60001d0ed20", "api-accbdd1b89e84ec97f4f16d4e3197d5c"], + "track_path": true +} +``` + + +SQL-based Pumps (Postgres) use different column names, and don't store `api_name`, `day`, `month`, `year`, `hour`, `api_schema`, `graphql_stats`, or `mcp_stats` at all. + + +## Traffic Log Field Reference + +### Request + + + Request method. + + **Example:** `GET`, `POST`. + + + + Request `Host` header, including the host and optional port number of the server the request was sent to. + + **Example:** `tyk.io`, or `tyk.io:8080` if a port is included. + + + + Request path, in decoded form. + + **Example:** `/foo/bar` for `/foo%2Fbar` or `/foo/bar`. + + + + Request path, decoded but otherwise unchanged from the original request. + + **Example:** `/foo/bar` for `/foo%2Fbar` or `/foo/bar`. + + + + The client's request path, captured before Tyk Gateway applies any path-modifying middleware, such as stripping the API's listen path. Equal to `path` unless something changes the path during processing. + + **Example:** `/api/v1/users`, when `path` is `/users` because the API's listen path `/api/v1/` was stripped. + + + + The API's configured listen path. + + **Example:** `/api/v1/`. + + + + Request `Content-Length` header: the number of bytes in the request body. + + **Example:** `10` for a request body of `0123456789`. + + + + Request `User-Agent` header. + + **Example:** `curl/7.86.0`. + + + + Base64-encoded copy of the request sent from Tyk Gateway to the upstream server. Empty unless [detailed recording](#detailed-recording) is enabled (automatic for GraphQL APIs). A pump can remove the base64 encoding via [`raw_request_decoded`](/api-management/tyk-pump#decode-raw-request-and-raw-response), or omit this field entirely via [`omit_detailed_recording`](/api-management/tyk-pump#omit-detailed-recording). + + **Example:** `R0VUIC9nZXQgSFRUUC8xLjEKSG9zdDogdHlrLmlv`. + + +### Response + + + The integer response code, generated by either Tyk Gateway or the upstream server depending on how the request was handled. + + **Example:** `200` for `200 OK`. + + + + Base64-encoded copy of the response sent from Tyk Gateway to the client. Empty unless [detailed recording](#detailed-recording) is enabled (automatic for GraphQL APIs). A pump can remove the base64 encoding via [`raw_response_decoded`](/api-management/tyk-pump#decode-raw-request-and-raw-response), or omit this field entirely via [`omit_detailed_recording`](/api-management/tyk-pump#omit-detailed-recording). + + **Example:** `SFRUUC8xLjEgMjAwIE9LCkNvbnRlbnQtTGVuZ3RoOiAxOQpEYXRlOiBXZWQsIDE2IE5vdiAyMDIyIDA2OjIxOjE2IEdNVApTZXJ2ZXI6IGd1bmljb3JuLzE5LjkuMAoKewogICJmb28iOiAiYmFyIgp9Cg==`. + + + + Duration of the upstream round trip, equal to `latency.total`. + + **Example:** `3` for a 3ms round trip. + + + + Contains three fields: `upstream`, the round trip between Tyk Gateway sending the request and receiving a response; `gateway`, additional Gateway-side processing outside that round trip, such as writing analytics; and `total`, the sum of both. + + **Example:** `{"total":4,"upstream":3,"gateway":1}`. + + + Measured from Tyk Gateway's reverse proxy: the sum of leaving Tyk, reaching the upstream, and the response returning to Tyk, plus Tyk Gateway's own processing time. + + + +### API and Authentication + + + The ID of the requested API definition. + + **Example:** `727dad853a8a45f64ab981154d1ffdad`. + + + + The name of the requested API definition. Not stored by SQL-based Pumps. + + **Example:** `Foo API`. + + + + The version of the requested API definition, or `Not Versioned` if the API is unversioned. + + **Example:** `1`, `b`, or `Not Versioned`. + + + + The Organisation ID of the requested API definition. + + **Example:** `5e9d9544a1dcd60001d0ed20`. + + + + The authentication key provided in the request. If no key was provided, Tyk Gateway substitutes a default value. + + **Example:** an unhashed `auth_key`, a hashed value such as `6129dc1e8b64c6b4`, or `00000000` if no authentication was provided. + + + By default, Tyk Gateway stores the raw, unhashed API key in this field. + + Anyone with access to Traffic Logs, whether in Tyk Dashboard's Log Browser, Redis, persistent storage, or any external destination Tyk Pump forwards them to, can read and reuse that key. + + Enable [key hashing](/api-management/access-control/sessions-and-keys/key-hashing) so that this field contains a hash instead. + + + + + The OAuth client ID, or an empty string if OAuth was not used. + + **Example:** `my-oauth-client-id`. + + + + The alias of the authenticated identity, blank if unset or the request is unauthenticated. + + **Example:** `my-key-alias`. + + +### Timestamps + + + Generated by Tyk Gateway when it finishes handling the request and writes the traffic log, not when it first received the request. For most requests the difference is small, roughly the request's own `request_time`, but it's after the upstream round trip, not before it. + + **Example:** `2022-11-16T03:01:54.648+00:00`. + + + + Day of the month, derived from `timestamp`. Not stored by SQL-based Pumps. + + **Example:** `16` for `2022-11-16T03:01:54Z`. + + + + Month, derived from `timestamp`. Not stored by SQL-based Pumps. + + **Example:** `11` for `2022-11-16T03:01:54Z`. + + + + Year, derived from `timestamp`. Not stored by SQL-based Pumps. + + **Example:** `2022` for `2022-11-16T03:01:54Z`. + + + + Hour of the day, derived from `timestamp`. Not stored by SQL-based Pumps. + + **Example:** `3` for `2022-11-16T03:01:54Z`. + + + + A future expiry date, used to implement [automated data expiry](/api-management/dashboard-analytics/analytics-storage-management#ttl-indexes) where the storage backend supports it. + + **Example:** `2022-11-23T07:26:25.762+00:00`. + + +### Client + + + Client IP address, taken from the `X-Real-IP` or `X-Forwarded-For` header if set, otherwise determined by Tyk Gateway from the request. + + **Example:** `172.18.0.1`. + + + + Client geolocation, calculated from the IP address using the MaxMind database. + + **Example:** `{"country":{"iso_code":"SG"},"city":{"geonameid":0,"names":{}},"location":{"latitude":0,"longitude":0,"timezone":""}}`. + + + + Network statistics. Not currently populated by Tyk Gateway. + + +### Metadata + + + Session context tags, which can refer to the Gateway, API key, Organisation, API definition, and other dimensions. + + **Example:** `["key-00000000","org-5e9d9544a1dcd60001d0ed20","api-accbdd1b89e84ec97f4f16d4e3197d5c"]`. + + + + `true` if the requested endpoint is configured to be tracked, otherwise `false`. See [Controlling Which Endpoints Are Tracked](#controlling-which-endpoints-are-tracked). + + +### GraphQL Fields + +Only populated for requests to a GraphQL API; absent or empty for other requests. Not stored by SQL-based Pumps. + + + Base64-encoded copy of the API's GraphQL schema. + + **Example:** `dHlwZSBRdWVyeSB7IGhlbGxvOiBTdHJpbmd9`. + + + + Details about the GraphQL operation: `Variables`, `RootFields`, `Types`, `Errors`, `OperationType` (`0` Unknown, `1` Query, `2` Mutation, `3` Subscription), `HasErrors`, and `IsGraphQL`. + + **Example:** `{"IsGraphQL":true,"OperationType":1,"RootFields":["hello"],"HasErrors":false}`. + + +### MCP Fields + +Only populated for requests to an [MCP](/ai-management/mcp-gateway/overview) Proxy; absent or empty for other requests. Not stored by SQL-based Pumps. + + + Details about the MCP request: `is_mcp`, `jsonrpc_method`, `primitive_type` (`tool`, `resource`, or `prompt`), and `primitive_name`. + + **Example:** `{"is_mcp":true,"jsonrpc_method":"tools/call","primitive_type":"tool","primitive_name":"get_current_weather"}`. + diff --git a/api-management/manage-apis/deploy-apis/deploy-apis-overview.mdx b/api-management/manage-apis/deploy-apis/deploy-apis-overview.mdx new file mode 100644 index 0000000000..3053acc266 --- /dev/null +++ b/api-management/manage-apis/deploy-apis/deploy-apis-overview.mdx @@ -0,0 +1,56 @@ +--- +title: "API Creation Methods" +description: "Different ways to create and manage APIs in Tyk" +keywords: "API Management, API Configuration, Dashboard, Tyk Sync, Tyk Operator" +sidebarTitle: "API Creation Methods" +--- + +This page explains the different methods available for creating and managing APIs in Tyk, each suited to different use cases and workflow requirements. + +## File-based configuration + + +Load API configurations directly to the `/apps` folder using JSON API specifications. This method is available for open source users and is ideal for testing gateway and API configurations. + +**Use case:** Testing and experimentation in development environments. + +**Learn more:** +* [Create an API in file-based mode](/api-management/gateway-config-managing-classic#create-an-api-in-file-based-mode) + +## Dashboard UI + +Create and configure APIs through the web-based Dashboard interface. Changes take effect immediately, making this method suitable for learning, testing, and proof-of-concept work. + +**Use case:** Manual API management, learning, and proof-of-concept projects. + +**Learn more:** +* [Create an API with the Dashboard](/api-management/gateway-config-managing-classic#create-an-api-with-the-dashboard) + +## Dashboard and Gateway API + +Programmatically create and manage APIs, policies, keys, and developer portals using REST APIs. This method provides flexibility for automation but requires imperative scripting. + +**Use case:** Programmatic API management and basic automation needs. + +**Learn more:** +- [Dashboard API](/tyk-dashboard-api) +- [Gateway API](/tyk-gateway-api) + +## Tyk Sync + +Manage API configurations declaratively using version-controlled files. Tyk Sync enables GitOps workflows by maintaining API configurations as code that can be versioned and deployed through CI/CD pipelines. + +**Use case:** GitOps workflows and teams requiring version-controlled API configurations. + +**Learn more:** +- [Tyk Sync](/api-management/automations/sync) + +## Tyk Operator + +Kubernetes-native API management using Custom Resource Definitions (CRDs). Tyk Operator provides declarative configuration with automatic drift detection and reconciliation in Kubernetes environments. + +**Use case:** Kubernetes-native environments requiring automated API lifecycle management. + +**Learn more:** +- [Tyk Operator](/api-management/automations/operator#what-is-tyk-operator) +- [Using Tyk Operator to enable GitOps](/api-management/automations) diff --git a/api-management/mdcb.mdx b/api-management/mdcb.mdx new file mode 100644 index 0000000000..ae62c43f02 --- /dev/null +++ b/api-management/mdcb.mdx @@ -0,0 +1,1004 @@ +--- +title: "Using Tyk In Distributed Environments" +description: "Learn about Tyk Multi Data Center Bridge (MDCB) and how it enables you to manage multiple API gateways across different data centers" +keywords: "MDCB, Multi Data Center Bridge, Control Plane, Data Plane, Synchroniser" +sidebarTitle: "Manage Distributed Gateways" +--- + +## Overview + +Tyk’s Multi Data Center Bridge (MDCB) is a separately licensed extension to the Tyk control plane that performs management and synchronization of logically or geographically distributed clusters of Tyk API Gateways. We use it ourselves to support our Tyk Cloud offering. + +### Challenges in Distributed Environment + +When your users are spread geographically and want to access your APIs from different parts of the world you can optimize the performance, value and utility of your APIs by deploying API Gateways in data centers local to them. + +Single API gateway + +Having localised gateways offers benefits to you and your users, such as: + +- Reduced latency (roundtrip time) for users by accessing a local data center +- Deployment close to backend services, reducing interconnect costs and latencies +- Increased availability across your estate - if one region goes offline the rest will continue to serve users +- Compliance with data residency and sovereignty regulations + +Distributed API gateways + +This distributed architecture, however, introduces challenges for you in terms of managing the configuration, synchronization and resilience of the Gateways in each data center. + +- How do you configure each of the Tyk API Gateways to ensure that a user can access only their authorized APIs, but from any location? +- How can you ensure that the correct APIs are deployed to the right Gateways - and kept current as they are updated? + +As the complexity of your architecture increases, this maintenance becomes an increasingly difficult and expensive manual task. + +This is where Tyk’s Multi Data Center Bridge (MDCB) comes in. + +### How does Tyk Multi Data Center Bridge help? + +The Tyk MDCB makes it possible to manage federated global deployments easily, from a central Dashboard: you can confidently deploy a multi-data center, geographically isolated set of Tyk Gateway clusters for maximum redundancy, failover, latency optimization, and uptime. + +Combining Tyk Dashboard with MDCB, you are provided with a “single pane of glass” or control plane that allows you to centrally manage multiple Tyk Gateway clusters. This has many advantages over having separate gateways and corresponding dashboard/portals, which would require manual synchronization to roll out any changes (e.g. new APIs) across all the individual gateways. + +By deploying MDCB, API Management with Tyk becomes a service that can be easily offered to multiple teams from a centralised location. + +Distributed API Gateways with MDCB + +### How does MDCB work? + +MDCB acts as a broker between the Tyk Gateway instances that you deploy in data centers around the world. A single Control Plane (Management) Gateway is used as reference: you configure APIs, keys and quotas in one central location; MDCB looks after the propagation of these to the Data Plane Gateways, ensuring the synchronization of changes. + +MDCB is extremely flexible, supporting clusters of Tyk Gateways within or across data centers - so for example two clusters within the same data center could run different configurations of APIs, users etc. + +MDCB keeps your Tyk API Gateways highly available because all the Data Plane Gateways, where your users access your APIs, can be configured and run independently. If the MDCB link back to the Management Gateway goes down, the Data Plane Gateways will continue to service API requests; when the link is back up, MDCB will automatically refresh the Data Planes with any changes they missed. + +Multi Data Center Bridge is down + +What happens if the worst happens and Data Plane Gateways fail while the link to the Control Plane is down? We’ve thought of that: Tyk will automatically configure the new Gateways that spin up using the last known set of API resources in the Data Plane cluster, minimizing the impact on availability of your services. + +### When might you deploy MDCB? + +#### Managing geographically distributed gateways to minimize latency and protect data sovereignty + +Consider Acme Global Bank: they have customers in the USA and the EU. Due to compliance, security and performance requirements they need to deploy their Tyk API Gateways locally in each of those regions. They need to manage the deployment and synchronization of APIs and associated resources (e.g. keys, policies and certificates) between the data centers to ensure global service for their customers. + +Acme Global Bank without MDCB + +Tyk MDCB enables Acme Global Bank to power this architecture by creating a primary data center with all the Tyk Control Plane components and secondary (Data Plane) data centers that act as local caches to run validation and rate limiting operations to optimize latency and performance. + +Acme Global Bank with MDCB + +#### Managing a complex deployment of services with internal and externally facing APIs + +Consider Acme Telecoms: they have a large nationally distributed workforce and complex self-hosted IT systems; are using Tyk API Gateways to deploy internal and external APIs; and have different teams managing and consuming different sets of APIs across multiple sites. They need to ensure data segregation, availability, and access for internal and external users and partners. + +Acme Telecoms without MDCB + +Combining Tyk’s built-in multi-tenancy capability with MDCB enables Acme Telecoms to set up dedicated logical gateways for different user groups and different physical gateways to guarantee data segregation, with a single management layer for operational simplicity. + +Acme Telecoms with MDCB + +### Why Choose MDCB for Your API Infrastructure? + +Beyond the two usage scenarios described here, there are many others where MDCB will provide you with the power and flexibility you need to manage your own particular situation. + +Here are some examples of the benefits that deploying Tyk MDCB can bring: + +#### Flexible architecture + +- You can control geographic distribution of traffic, restricting traffic to data centers/regions of your choice. +- You can put your Tyk API Gateways close to users, but still have a single management layer. +- You have a single, simple, point of access for configuration of your complex API infrastructure and yet deploy multiple Developer Portals, if required, to provide access to different user groups (e.g. Internal and External). +- You can physically [segment teams and environments](/api-management/api-sharding#gateway-sharding) within a single physical data center, giving each team full control of its own API gateway and server resources without the noisy neighbors you might experience in a standard self-managed deployment. +- You can deploy gateways with whichever mix of cloud vendors you wish. +- You can mix and match cloud and on premises data centers. + +#### Improved resiliency, security and uptime + +- Each Data Plane Gateway operates autonomously using a locally stored copy of the API resources it needs. +- The Control Plane (Management) Gateway maintains synchronization of these configurations across your Tyk deployment via the MDCB backbone link. +- If the Management Gateway or MDCB backbone fails, the Data Planes will continue to handle API requests, rejecting only new authorization tokens created on other Gateways. When connectivity is restored, the Data Plane Gateways will hot-reload to fetch any updated configurations (e.g. new authorization tokens) from the Control Plane. +- If a Data Plane Gateway fails, this does not impact the operation of the others: when it comes back online, if it is unable to contact the Control Plane, it will retrieve the “last good” configuration held locally. +- The MDCB backbone runs on a resilient compressed RPC channel that is designed to handle ongoing and unreliable connectivity; all traffic on the backbone is encrypted and so safer to use over the open internet or inter-data center links. +- Improved data security through separation of traffic into completely separate clusters within your network. + +#### Reduced latency + +- Deploying Data Plane Gateways close to your geographically distributed API consumers helps reduce their perceived request latency. +- Deploying Data Plane Gateways close to your backend services will minimize round trip time servicing API requests. +- The Data Plane Gateways cache keys and other configuration locally, so all operations can be geographically localised. +- All traffic to and from one Gateway cluster will have rate limiting, authentication and authorization performed within the data center rather than “calling home” to a central control point; this reduces the API request round trip time. + +#### Improved Infrastructure Management + +- Due to the shared Control Plane, all Data Plane Gateways report into a single Tyk Dashboard. This provides a simple, consistent place to manage your APIM deployment. +- This allows a shared infra team to offer API management and API Gateways as a service, globally, across multiple clouds and Self-Managed regions, from a single pane of glass. + +#### Next Steps + +- [The components of an MDCB deployment](/api-management/mdcb#mdcb-components) +- [Run an MDCB Proof of Concept](/api-management/mdcb#minimizing-latency-with-mdcb) +- [MDCB reference guide](/tyk-multi-data-centre/mdcb-configuration-options) + +## MDCB Components + +### Overview + +Here we will give an overview of the main elements of a Tyk Multi Data Center (distributed) solution, clarifying the terminology used by Tyk. +A Tyk Multi Data Center Bridge deployment + +#### Tyk Gateway +- The workhorse of any deployment, Tyk’s lightweight Open Source API gateway that exposes your APIs for consumption by your users. It is a reverse proxy that secures your APIs, manages session and policies, monitors, caches and manipulates requests/responses when needed before/after it proxies them to and from the upstream. + +#### Tyk Dashboard +- Tyk’s management platform used to control the creation of API configurations, policies and keys in a persistent manner. It provides analytic information on the traffic the Gateways have processed which includes aggregated API usage and detailed information per transaction. + +#### Tyk Multi Data Center Bridge (MDCB) +- The backbone of the distributed Tyk deployment, connecting the distributed Data Plane deployments back to the Control Plane. + +#### Tyk Pump +- Tyk’s open source analytics purger that can be used to export transaction logs from the Tyk deployment to the visualisation tool or other data store of your choice + +#### Tyk Developer Portal +- The access point for your API Consumers where you publish your API catalog(s) and they obtain API keys. + +#### Redis +- An in-memory data store used as a database, cache and message broker. We use it as pub/sub broker for inter-Gateway communication, and as a cache for API configurations, keys, certificates, and temporary store for analytics records. + +#### MongoDB/SQL +- A persistent data store for API configurations, policies, analytics and aggregated analytics, Dashboard organizations, configurations, dashboard users, portal developers and configuration. + + +### Control Plane +The Tyk Control Plane + +The Control Plane must consist of the following elements: +- **Tyk Dashboard** (used to configure and control the whole Tyk installation) +- **Tyk Gateway** (used for creation of keys and certificates, this does not service API requests; it is important to ensure there is no public access to it and it must not be sharded (tagged) as it "belongs" to the whole Tyk installation) +- **Tyk MDCB** +- **Redis** (high availability Redis data store that should be backed up in case of failure; this [document](https://redis.io/docs/management/persistence/) gives recommendation on Redis persistency) +- **MongoDB or SQL** (a persistent data store that should be deployed and set up for redundancy and high availability) + +To improve resilience and availability, multiple instances of each Tyk component should be deployed and load balanced within the Control Plane. + +#### Optional Components +- One or more **Tyk Pumps** can be deployed within the Control Plane to export analytics data (request/response logs) to your [data sink of choice](/api-management/logs/external-data-sinks) for further analytics and visualisation. +- A **Tyk Developer Portal** can be added to enhance the end-user experience when accessing your APIs. + +### Data Plane +The Tyk Data Plane + +The Data Plane deployment must consist of the following elements: +- **Tyk Gateway** (one or more Gateways specifically configured to operate in the Data Plane) +- **Redis** (a single Redis data store shared by all Gateways in the cluster) + +To provide resilience and availability, multiple Gateways should be deployed and load balanced within the cluster. +If you want this Data Plane deployment to be resilient, available, and independent from the Control Plane during a disconnection event, it is advised to make the Redis data store persistent. + +#### Optional Components +- A **Tyk Pump** specifically configured as a [Hybrid Pump](/product-stack/tyk-charts/tyk-data-plane-chart#hybrid-pump) can be deployed with the Data Plane gateways to export analytics data (request/response logs) to your [data sink of choice](/api-management/logs/external-data-sinks) for further analytics and visualisation. + +## Setup MDCB Control Plane + +The [Tyk control plane](/api-management/mdcb#control-plane) contains all the +standard components of a standard Tyk Self-Managed installation with the addition of the Multi Data Center Bridge (MDCB). + +### Installing MDCB Component On Linux +The MDCB component must be able to connect to Redis and MongoDB/PostgreSQL directly from within the Control Plane deployment. It does not require access to the Tyk Gateway(s) or Dashboard application. + +The MDCB component will however, by default, expose an RPC service on port 9091, to which the [Tyk Data Plane](/api-management/mdcb#data-plane) data centers, i.e. the Gateways that serve API traffic, will need connectivity. + +#### Prerequisites +We will assume that your account manager has provided you with a valid MDCB and Dashboard License and the command to enable you to download the MDCB package. +We will assume that the following components are up and running in your Controller DC: + +* MongoDB or SQL (check [supported versions](/planning-for-production/database-settings)) +* Redis (check [supported versions](/tyk-self-managed/install#redis)) +* Tyk Dashboard +* Tyk Gateway / Gateways Cluster +* Working Tyk-Pro [Self-Managed installation](/tyk-self-managed/install) + + + + + When using SQL rather than MongoDB in a production environment, we only support PostgreSQL. + + + +#### Installing using RPM and Debian packages +To download the relevant MDCB package from PackageCloud: + +```curl +curl -s https://packagecloud.io/install/repositories/tyk/tyk-mdcb-stable/script.deb.sh | sudo bash +``` + +```curl +curl -s https://packagecloud.io/install/repositories/tyk/tyk-mdcb-stable/script.rpm.sh | sudo bash +``` + +After the relevant script for your distribution has run, the script will let you know it has finished with the following message: + +`The repository is setup! You can now install packages.` + +You will now be able to install MDCB as follows: + +```curl +sudo apt-get install tyk-sink +``` + +Or + +```curl +sudo yum install tyk-sink +``` + +### Installing in a Kubernetes Cluster with our Helm Chart + +The [Tyk Control Plane](/product-stack/tyk-charts/tyk-control-plane-chart) helm chart is pre-configured to install Tyk control plane for multi data center API management from a single Dashboard with the MDCB component. + +Below is a concise instruction on how to set up an MDCB Control Plane with Redis and PostgreSQL. + +To access the comprehensive installation instructions and configuration options, please see [Tyk Control Plane Helm Chart](/product-stack/tyk-charts/tyk-control-plane-chart). + +#### Prerequisites +- [Kubernetes 1.19+](https://kubernetes.io/docs/setup/) +- [Helm 3+](https://helm.sh/docs/intro/install/) +- MDCB and Dashboard license + +#### Quick Start + +1. **Setup required credentials** + + First, you need to provide Tyk Dashboard and MDCB license, admin email and password, and API keys. We recommend to store them in secrets. + + ```bash + NAMESPACE=tyk-cp + + API_SECRET=changeit + ADMIN_KEY=changeit + ADMIN_EMAIL=admin@default.com + ADMIN_PASSWORD=changeit + DASHBOARD_LICENSE=changeit + MDCB_LICENSE=changeit + SECURITY_SECRET=changeit + OPERATOR_LICENSE=changeit + + kubectl create namespace $NAMESPACE + + kubectl create secret generic my-secrets -n $NAMESPACE \ + --from-literal=APISecret=$API_SECRET \ + --from-literal=AdminSecret=$ADMIN_KEY \ + --from-literal=DashLicense=$DASHBOARD_LICENSE \ + --from-literal=OperatorLicense=$OPERATOR_LICENSE + + kubectl create secret generic mdcb-secrets -n $NAMESPACE \ + --from-literal=MDCBLicense=$MDCB_LICENSE \ + --from-literal=securitySecret=$SECURITY_SECRET + + kubectl create secret generic admin-secrets -n $NAMESPACE \ + --from-literal=adminUserFirstName=Admin \ + --from-literal=adminUserLastName=User \ + --from-literal=adminUserEmail=$ADMIN_EMAIL \ + --from-literal=adminUserPassword=$ADMIN_PASSWORD + ``` + +2. **Install Redis (if you don't already have Redis installed)** + + If you do not already have Redis installed, you may use these charts provided by Bitnami. + + ```bash + helm upgrade tyk-redis oci://registry-1.docker.io/bitnamicharts/redis -n $NAMESPACE --install --version 19.0.2 --set image.repository=bitnamilegacy/redis + ``` + Follow the notes from the installation output to get connection details and password. The DNS name of your Redis as set by Bitnami is `tyk-redis-master.tyk-cp.svc:6379` (Tyk needs the name including the port) + + The Bitnami chart also creates a secret `tyk-redis` which stores the connection password in `redis-password`. We will make use of this secret in installation later. + + + +Ensure that you are installing Redis versions that are supported by Tyk. Please consult the list of [supported versions](/tyk-self-managed/install#redis) that are compatible with Tyk. + + + +3. **Install PostgreSQL (if you don't already have PostgreSQL installed)** + + If you do not already have PostgreSQL installed, you may use these charts provided by Bitnami. + + ```bash + helm upgrade tyk-postgres oci://registry-1.docker.io/bitnamicharts/postgresql --set "auth.database=tyk_analytics" -n $NAMESPACE --install --version 14.2.4 --set image.repository=bitnamilegacy/postgresql + ``` + + Follow the notes from the installation output to get connection details. + + We require the PostgreSQL connection string for Tyk installation. This can be stored in a secret and will be used in installation later. + + ```bash + POSTGRESQLURL=host=tyk-postgres-postgresql.$NAMESPACE.svc\ port=5432\ user=postgres\ password=$(kubectl get secret --namespace $NAMESPACE tyk-postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)\ database=tyk_analytics\ sslmode=disable + + kubectl create secret generic postgres-secrets -n $NAMESPACE --from-literal=postgresUrl="$POSTGRESQLURL" + ``` + + + +Ensure that you are installing PostgreSQL versions that are supported by Tyk. Please consult the list of [supported versions](/tyk-self-managed/install#requirements) that are compatible with Tyk. + + + +4. **Install Tyk Control Plane** + + ```bash + helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ + + helm repo update + + helm upgrade tyk-cp tyk-helm/tyk-control-plane -n $NAMESPACE \ + --install \ + --set global.adminUser.useSecretName=admin-secrets \ + --set global.secrets.useSecretName=my-secrets \ + --set tyk-mdcb.mdcb.useSecretName=mdcb-secrets \ + --set global.redis.addrs="{tyk-redis-master.$NAMESPACE.svc:6379}" \ + --set global.redis.passSecret.name=tyk-redis \ + --set global.redis.passSecret.keyName=redis-password \ + --set global.postgres.connectionStringSecret.name=postgres-secrets \ + --set global.postgres.connectionStringSecret.keyName=postgresUrl + ``` + +5. **Done!** + + Now Tyk Dashboard and Tyk MDCB should be accessible through service `dashboard-svc-tyk-cp-tyk-dashboard` at port `3000` and `mdcb-svc-tyk-cp-tyk-mdcb` at port `9091` respectively. You can login to Dashboard using the admin email and password to start managing APIs. + + You can use the MDCB connection details included in the installation output, to install the [MDCB Data Plane](/api-management/mdcb#setup-mdcb-data-plane). + +### Configuration +If you install MDCB component with package, modify your `/opt/tyk-sink/tyk_sink.conf` file as follows: + +#### Configuration Example +```json +{ + "listen_port": 9091, + "healthcheck_port": 8181, + "server_options": { + "use_ssl": false, + "certificate": { + "cert_file": "", + "key_file": "" + }, + "min_version": 771 + }, + "storage": { + "type": "redis", + "host": "localhost", + "port": 6379, + "username": "", + "password": "", + "enable_cluster": false, + "redis_use_ssl": false, + "redis_ssl_insecure_skip_verify": false + }, + "basic-config-and-security/security": { + "private_certificate_encoding_secret": "" + }, + "hash_keys": true, + "forward_analytics_to_pump": true, + "ignore_tag_prefix_list": [ + + ], + "analytics": { + "mongo_url": "mongodb://localhost/tyk_analytics", + "mongo_use_ssl": false, + "mongo_ssl_insecure_skip_verify": false + }, + "license": "MDCB_LICENSE_KEY" +} +``` + +For the synchronization of [TLS certificates](/api-management/certificates#encryption-of-the-private-key) to Data Planes, the value of [security.private_certificate_encoding_secret](/tyk-multi-data-centre/mdcb-configuration-options#security-private_certificate_encoding_secret) must be identical across all components involved: your Tyk Dashboard, your Control Plane and Data Plane Gateways, and your Tyk MDCB instances. + + +From MDCB 2.0+, you can choose between Mongo or SQL databases to setup your `analytics` storage. In order to setup your PostgreSQL storage, you can use the same configuration from your [Tyk Dashboard main storage](/planning-for-production/database-settings#postgresql). + +For example, to set up a `postgres` storage the `analytics` configurations would be: + +```json +{ +... + ... + "analytics": { + "type": "postgres", + "connection_string": "user=postgres_user password=postgres_password database=dbname host=potgres_host port=postgres_port", + "table_sharding": false + }, +} +``` +This storage will work for fetching your organization data (APIs, Policies, etc) and for analytics. + + + +You should now be able to start the MDCB service, check that it is up and running and ensure that the service starts on system boot: + +```console +sudo systemctl start tyk-sink +``` + + +```console +sudo systemctl enable tyk-sink +``` + +### Health check + +It is possible to perform a health check on the MDCB service. This allows you to determine if the service is running, so is useful when using MDCB with load balancers. + +Health checks are available via the HTTP port. This is defined by `http_port` configuration setting and defaults to `8181`. Do **not** use the standard MDCB listen port (`listen_port`) for MDCB health checks. + +From MDCB v2.7.0, there are 2 health check services available: +1. `/liveness` endpoint returns a `HTTP 200 OK` response when the service is operational. +2. `/readiness` endpoint returns a `HTTP 200 OK` response when MDCB is ready to accept requests. It ensures that dependent components such as Redis and data store are connected, and the gRPC server is ready for connection. + +See [MDCB API](/tyk-mdcb-api) for details of the endpoints. + +In MDCB v2.6.0 or earlier, MDCB only offers one health check endpoint at `/health` via the port defined by the `healthcheck_port` configuration setting. The default port is `8181`. The `/health` endpoint is also available on v2.7.0 or later for backward compatibility. + +To use the health check service, call the `/health` endpoint i.e. `http://my-mdcb-host:8181/health`. This will return a `HTTP 200 OK` response if the service is running. + +Please note that an HTTP 200 OK response from the `/health` endpoint merely indicates that the MDCB service is operational. However, it is important to note that the service may not yet be ready for use if it is unable to establish a connection with its dependent components (such as Redis and Data store) or if they are offline. Upgrade to v2.7.0 and later to have more accurate health checking. + +### Troubleshooting + +#### Check that the MDCB service is running + +```console +sudo systemctl status tyk-sink +``` + +Should Return: + +```console +tyk-sink.service - Multi Data Center Bridge for the Tyk API Gateway + + Loaded: loaded (/usr/lib/systemd/system/tyk-sink.service; enabled; vendor preset: disabled) + + Active: active (running) since Thu 2018-05-03 09:39:37 UTC; 3 days ago + Main PID: 1798 (tyk-sink) + + CGroup: /system.slice/tyk-sink.service + + └─1798 /opt/tyk-sink/tyk-sink -c /opt/tyk-sink/tyk_sink.conf +``` + +#### Check that MDCB is listening on port 9091 + +```console +sudo netstat -tlnp +``` + +Should Return: + +``` +Active Internet connections (only servers) +Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +... +tcp6 0 0 :::9091 :::* LISTEN 1798/tyk-sink +``` + +#### Check the logs for MDCB + +```{.copyWrapper} +> sudo journalctl -u tyk-sink +``` + +Add the `-f` flag to follow the log. The command should return output similar to this: + +```console +-- Logs begin at Thu 2018-05-03 09:30:56 UTC, end at Mon 2018-05-07 08:58:23 UTC. -- +May 06 11:50:37 master tyk-sink[1798]: time="2018-05-06T11:50:37Z" level=info msg="RPC Stats:{\"RPCCalls\":0,\"RPCTime\":0,\"Byte +May 06 11:50:38 master tyk-sink[1798]: time="2018-05-06T11:50:38Z" level=info msg="RPC Stats:{\"RPCCalls\":0,\"RPCTime\":0,\"Byte +... +May 06 11:50:42 master tyk-sink[1798]: time="2018-05-06T11:50:42Z" level=info msg="Ping!" +``` + +#### Check MDCB configurations + +From MDCB v2.7.0, a secured HTTP endpoint `/config` can be enabled that allows you to query configuration of MDCB. + +To enable the secured HTTP endpoint, make sure you have the following in your `/opt/tyk-sink/tyk_sink.conf` config file. + +```json +{ + "security": { + "enable_http_secure_endpoints": true, + "secret": "" + }, + "http_server_options": { + "use_ssl": true, + "certificate": { + "cert_file": ..., + "key_file": ..., + "min_version": ... + } + } +} +``` + +Subsequently, you can issue a request to the `/config` endpoint to return a json representation of your MDCB config: + +```bash +curl -H "x-tyk-authorization: " https://my-mdcb-host:8181/config +``` + +Alternatively, you can issue a request to the `/env` endpoint to return your MDCB config in the form of environment variables settings: + +```bash +curl -H "x-tyk-authorization: " https://my-mdcb-host:8181/env +``` + +### Enabling MDCB on Organization Object on Tyk Dashboard + +Before a Data Plane Gateway can connect to MDCB, it is important to enable the organization that owns all the APIs to be distributed to be allowed to utilize Tyk MDCB. To do this, the organization record needs to be modified with two flags using the [Tyk Dashboard Admin API](/dashboard-admin-api). + +To make things easier, we will first set a few [environment variables](/tyk-oss-gateway/configuration): + +1. `export DASH_ADMIN_SECRET=` + +You can find `` in `tyk_analytics.conf` file under `admin_secret` field or `TYK_DB_ADMINSECRET` environment variable. + +2. `export DASH_URL=` + +This is the URL you use to access the Dashboard (including the port if not using the default port). + +3. `export ORG_ID=` + +You can find your organization id in the Dashboard, under your user account details. + +Org ID + +4. Send a GET request to the Dashboard API to `/admin/organisations/$ORG_ID` to retrieve the organization object. In the example below, we are redirecting the output json to a file `myorg.json` for easy editing. + +```curl +curl $DASH_URL/admin/organisations/$ORG_ID -H "Admin-Auth: $DASH_ADMIN_SECRET" | python -mjson.tool > myorg.json +``` +5. Open `myorg.json` in your favorite text editor and add the following fields as follows. +New fields are between the `...` . + +```json {linenos=table,hl_lines=["5-12"],linenostart=1} +{ + "_id": "55780af69b23c30001000049", + "owner_slug": "portal-test", + ... + "hybrid_enabled": true, + "event_options": { + "key_event": { + "webhook": "https://example.com/webhook", + "email": "user@example.com", + "redis": true + }, + }, + ... + "apis": [ + { + "api_human_name": "HttpBin (again)", + "api_id": "2fdd8512a856434a61f080da67a88851" + } + ] +} +``` + +In the example above it can be seen that the `hybrid_enabled` and `event_options` configuration fields have been added: + +- `hybrid_enabled:` Allows a Data Plane Gateway to login as an Organisation member into MDCB. +- `event_options:` The `event_options` object is optional. By default the update and removal of Redis keys (hashed and unhashed), API Definitions and policies are propagated to various instance zones. The `event_options` object contains a `key_event` object that allows configuration of the following additional features: + + - event notification mechanism for all Redis key (hashed and unhashed) events. Events can be notified via webhook by setting the `webhook` property to the value of the webhook URL. Similarly, events can be notified via email by setting the `email` property to the value of the target email address. + - enable propagation of events for when an OAuth token is revoked from Dashboard by setting the `redis` property to `true`. + + The `event_options` in the example above enables the following functionality: + + - events are propagated when OAuth tokens are revoked from Dashboard since `redis` is `true` + - events associated with Redis keys (hashed and unhashed) and revoking OAuth tokens via Dashboard are sent to webhook `https://example.com/webhook` and email address `user@example.com` + +6. Update your organization with a PUT request to the same endpoint, but this time, passing in your modified `myorg.json` file. + +```curl +curl -X PUT $DASH_URL/admin/organisations/$ORG_ID -H "Admin-Auth: $DASH_ADMIN_SECRET" -d @myorg.json +``` + +This should return: + +```json +{"Status":"OK","Message":"Org updated","Meta":null} +``` + +## Setup MDCB Data Plane + +You may configure an unlimited number of [Tyk Data Planes](/api-management/mdcb#data-plane) containing Gateways for ultimate High Availablity (HA). We recommend that you deploy your Data Plane Gateways as close to your upstream services as possible in order to reduce latency. + +It is a requirement that all the Gateways in a Data Plane data center share the same Redis DB in order to take advantage of Tyk's DRL and quota features. +Your Data Plane can be in the same physical data center as the Control Plane with just a logical network separation. If you have many Tyk Data Planes, they can be deployed in a private-cloud, public-cloud, or even on bare-metal. + +### Installing in a Kubernetes Cluster with our Helm Chart + +The [Tyk Data Plane](/product-stack/tyk-charts/tyk-data-plane-chart) helm chart is pre-configured to install Tyk Gateway and Tyk Pump that connects to MDCB or Tyk Cloud, our SaaS MDCB Control Plane. After setting up Tyk Control Plane with Helm Chart, obtain the required connection details from installation output and configure data plane chart as below. For Tyk Cloud users, following [Tyk Cloud instructions](/tyk-cloud/environments-deployments/hybrid-gateways) to deploy your hybrid gateways. + +#### Prerequisites + +* [Kubernetes 1.19+](https://kubernetes.io/docs/setup/) +* [Helm 3+](https://helm.sh/docs/intro/install/) +* Connection details to remote control plane from the tyk-control-plane installation output. + +The following quick start guide explains how to use the [Tyk Data Plane Helm chart](/product-stack/tyk-charts/tyk-data-plane-chart) to configure Tyk Gateway that includes: +- Redis for key storage +- Tyk Pump to send analytics to Tyk Control Plane and Prometheus + +At the end of this quickstart Tyk Gateway should be accessible through service `gateway-svc-tyk-dp-tyk-gateway` at port `8080`. Pump is also configured with Hybrid Pump which sends aggregated analytics to MDCB, and Prometheus Pump which expose metrics locally at `:9090/metrics`. + +1. **Set connection details** + + Set the below environment variables and replace values with connection details to your MDCB control plane. See [Tyk Data Plane](/product-stack/tyk-charts/tyk-data-plane-chart#obtain-remote-control-plane-connection-details-from-tyk-control-plane-chart) documentation on how to get the connection details. + + ```bash + USER_API_KEY=9d20907430e440655f15b851e4112345 + ORG_ID=64cadf60173be90001712345 + MDCB_CONNECTIONSTRING=mdcb-svc-tyk-cp-tyk-mdcb.tyk-cp.svc:9091 + GROUP_ID=your-group-id + MDCB_USESSL=false + ``` + +2. **Then use Helm to install Redis and Tyk** + + ```bash + NAMESPACE=tyk-dp + APISecret=foo + + helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ + helm repo update + + helm upgrade tyk-redis oci://registry-1.docker.io/bitnamicharts/redis -n $NAMESPACE --create-namespace --install --set image.repository=bitnamilegacy/redis + + helm upgrade tyk-dp tyk-helm/tyk-data-plane -n $NAMESPACE --create-namespace \ + --install \ + --set global.remoteControlPlane.userApiKey=$USER_API_KEY \ + --set global.remoteControlPlane.orgId=$ORG_ID \ + --set global.remoteControlPlane.connectionString=$MDCB_CONNECTIONSTRING \ + --set global.remoteControlPlane.groupID=$GROUP_ID \ + --set global.remoteControlPlane.useSSL=$MDCB_USESSL \ + --set global.secrets.APISecret="$APISecret" \ + --set global.redis.addrs="{tyk-redis-master.$NAMESPACE.svc.cluster.local:6379}" \ + --set global.redis.passSecret.name=tyk-redis \ + --set global.redis.passSecret.keyName=redis-password + ``` + +3. **Done!** + + Now Tyk Gateway should be accessible through service `gateway-svc-tyk-dp-tyk-gateway` at port `8080`. Pump is also configured with Hybrid Pump which sends aggregated analytics to MDCB, and Prometheus Pump which expose metrics locally at `:9090/metrics`. + + For the complete installation guide and configuration options, please see [Tyk Data Plane Chart](/product-stack/tyk-charts/tyk-data-plane-chart). + +### Configuring an existing Tyk Gateway +If you have Redis and a working Tyk Gateway deployed, follow below steps to configure your gateways to work in RPC mode. + + + +If you have deployed Gateway with `tyk-data-plane` Chart, you don't need to go through following steps to configure Tyk Gateway. The necessary configurations has been set in `tyk-data-plane` chart templates. + + + +#### Prerequisites +- Redis +- A working headless/open source Tyk Gateway deployed + +#### Data Plane Gateway Configuration + +Modify the Tyk Gateway configuration (`tyk.conf`) as follows: +`"use_db_app_configs": false,` + +Next, we need to ensure that the policy loader and analytics pump use the RPC driver: + +```{.json} +"policies": { + "policy_source": "rpc", + "policy_record_name": "tyk_policies" +}, +"analytics_config": { + "type": "rpc", + ... // remains the same +}, +``` + +Lastly, we add the sections that enforce the Data Plane mechanism: + +```{.json} +"slave_options": { + "use_rpc": true, + "rpc_key": "{ORGID}", + "api_key": "{APIKEY}", + "connection_string": "{MDCB_HOSTNAME:9091}", + "enable_rpc_cache": true, + "bind_to_slugs": false, + "group_id": "{ny}", + "use_ssl": false, + "ssl_insecure_skip_verify": true +}, +"auth_override": { + "force_auth_provider": true, + "auth_provider": { + "name": "", + "storage_engine": "rpc", + "meta": {} + } +} +``` + + +if you set `analytics_config.type` to `rpc` - make sure you don't have your Tyk Pump configured to send analytics via the `hybrid` Pump type. + + + + +As an optional configuration you can use `key_space_sync_interval` to set the period's length in which the gateway will check for changes in the key space, if this value is not set then by default it will be 10 seconds. + + +The most important elements here are: + +| Field | Description | +| :--------------- | :---------------- | +|`api_key` |This the API key of a user used to authenticate and authorize the Gateway's access through MDCB. The user should be a standard Dashboard user with minimal privileges so as to reduce risk if compromised. The suggested security settings are `read` for `Real-time notifications` and the remaining options set to `deny`.| +|`group_id` |This is the "zone" that this instance inhabits, e.g. the cluster/data center the gateway lives in. The group ID must be the same across all the gateways of a data center/cluster which are also sharing the same Redis instance. This id should also be unique per cluster (otherwise another gateway's cluster can pick up your keyspace events and your cluster will get zero updates). +|`connection_string` |The MDCB instance or load balancer.| +|`bind_to_slugs` | For all Tyk installations this should be set to false.| + +Once this is complete, you can restart the Tyk Gateway in the Data Plane, and it will connect to the MDCB instance, load its API definitions, and is ready to proxy traffic. + +## Minimizing latency with MDCB + +### Overview + +As described [previously](/api-management/mdcb#managing-geographically-distributed-gateways-to-minimize-latency-and-protect-data-sovereignty), Acme Global Bank has operations and customers in both the EU and USA. + +To decrease the latency in response from their systems and to ensure that data remains in the same legal jurisdiction as the customers (data residency), they have deployed backend (or, from the perspective of the API gateway, “upstream”) services in two data centers: one in the US, the other in the EU. + +Without a dedicated solution for this multi-region use case, Acme Global Bank would deploy a Tyk Gateway cluster in each data center and then have the operational inconvenience of maintaining two separate instances of Tyk Dashboard to configure, secure and publish the APIs. + +By using Tyk's Multi-Data Center Bridge (MDCB), however, they are able to centralise the management of their API Gateways and gain resilience against failure of different elements of the deployment - data or control plane - improving the availability of their public APIs. + +In this example we will show you how to create the Acme Global Bank deployment using our example Helm charts. + +MDCB Proof of Concept - Acme Global Bank + +**Step-by-step instructions to deploy the Acme Global Bank scenario with Kubernetes in a public cloud (here we’ve used Google Cloud Platform):** + +### Pre-requisites and configuration + +1. What you need to install/set-up + - Tyk Pro license (Dashboard and MDCB keys - obtained from Tyk) + - Access to a cloud account of your choice, e.g. GCP + - You need to grab this Tyk Demo repository: [GitHub - TykTechnologies/tyk-k8s-demo](https://github.com/TykTechnologies/tyk-k8s-demo) + - You need to install `helm`, `jq`, `kubectl` and `watch` + +2. To configure GCP + - Create a GCP cluster + - Install the Google Cloud SDK + - install `gcloud` + - `./google-cloud-sdk/install.sh` + - Configure the Google Cloud SDK to access your cluster + - `gcloud auth login` + - `gcloud components install gke-gcloud-auth-plugin` + - `gcloud container clusters get-credentials <> —zone <>—project <>` + - Verify that everything is connected using `kubectl` + - `kubectl get nodes` + +3. You need to configure the Tyk build + - Create a `.env` file within tyk-k8s-demo based on the provided `.env.example` file + - Add the Tyk license keys to your `.env`: + - `LICENSE=` + - `MDCB_LICENSE=` + +### Deploy Tyk Stack to create the Control and Data Planes + +1. Create the Tyk Control Plane + - `./up.sh -r redis-cluster -e load-balancer tyk-cp` + +*Deploying the Tyk Control Plane* +Tyk Control Plane Deployed + +2. Create two logically-separate Tyk Data Planes to represent Acme Global Bank’s US and EU operations using the command provided in the output from the `./up.sh` script: + - `TYK_WORKER_CONNECTIONSTRING= TYK_WORKER_ORGID= TYK_WORKER_AUTHTOKEN= TYK_WORKER_USESSL=false ./up.sh --namespace tyk-worker` + +Note that you need to run the same command twice, once setting `` to `tyk-worker-us`, the other to `tyk-worker-eu` (or namespaces of your choice) + +*Deploying the `tyk-worker-us` namespace (Data Plane #1)* +Deploying the tyk-worker-us namespace + +*Deploying the `tyk-worker-eu` namespace (Data Plane #2)* +Deploying the tyk-worker-eu namespace + +3. Verify and observe the Tyk Control and Data Planes + - Use `curl` to verify that the gateways are alive by calling their `/hello` endpoints + +observe Tyk K8s namespace console output + + - You can use `watch` to observe each of the Kubernetes namespaces + +*`tyk-cp` (Control Plane)* +Control Plane + +*`tyk-worker-us` (Data Plane #1)* +Data Plane #1 + +*`tyk-worker-eu` (Data Plane #2)* +Data Plane #2 + +### Testing the deployment to prove the concept +As you know, the Tyk Multi Data Center Bridge provides a link from the Control Plane to the Data Plane Gateways, so that we can control all the remote gateways from a single dashboard. + +1. Access Tyk Dashboard + - You can log into the dashboard at the external IP address reported in the watch window for the Control Plane - in this example it was at `34.136.51.227:3000`, so just enter this in your browser + - The user name and password are provided in the `./up.sh` output: + - username: `default@example.com` + - password: `topsecretpassword` (or whatever you’ve configured in the `.env` file) + +Tyk Dashboard login + +2. Create an API in the dashboard, but don’t secure it (set it to `Open - keyless`); for simplicity we suggest a simple pass-through proxy to `httbin.org`. +3. MDCB will propagate this API through to the Data Plane Gateways - so try hitting that endpoint on the two Data Plane gateways (their addresses are given in the watch windows: for example `34.173.240.149:8081` for my `tyk-worker-us` gateway above). +4. Now secure the API from the dashboard using the Authentication Token option. You’ll need to set a policy for the API and create a key. +5. If you try to hit the API again from the Data Plane Gateways, you’ll find that the request is now rejected because MDCB has propagated out the change in authentication rules. Go ahead and add the Authentication key to the request header… and now you reach `httpbin.org` again. You can see in the Dashboard’s API Usage Data section that there will have been success and error requests to the API. +6. OK, so that’s pretty basic stuff, let’s show what MDCB is actually doing for you… reset the API authentication to be `Open - keyless` and confirm that you can hit the endpoint without the Authentication key from both Data Planes. +7. Next we’re going to experience an MDCB outage - by deleting its deployment in Kubernetes: +
`kubectl delete deployment.apps/mdcb-tyk-cp-tyk-pro -n tyk` +8. Now there's no connection from the data placne to the control plane, but try hitting the API endpoint on either Data Plane Gateway and you’ll see that they continue serving your users' requests regardless of their isolation from the Control Plane. +9. Back on the Tyk Dashboard make some changes - for example, re-enable Authentication on your API, add a second API. Verify that these changes **do not** propagate through to the Data Plane Gateways. +10. Now we’ll bring MDCB back online with this command: +
`./up.sh -r redis-cluster -e load-balancer tyk-cp` +11. Now try hitting the original API endpoint from the Data Planes - you’ll find that you need the Authorization key again because MDCB has updated the Data Planes with the new config from the Control Plane. +12. Now try hitting the new API endpoint - this will also have automatically been propagated out to the Data Planes when MDCB came back up and so is now available for your users to consume. + +Pretty cool, huh? + +There’s a lot more that you could do - for example by deploying real APIs (after all, this is a real Tyk deployment) and configuring different Organisation Ids for each Data Plane to control which APIs propagate to which Gateways (allowing you to ensure data localisation, as required by the Acme Global Bank). + +### Closing everything down +We’ve provided a simple script to tear down the demo as follows: +1. `./down.sh -n tyk-worker-us` +2. `./down.sh -n tyk-worker-eu` +3. `./down.sh` + +**Don’t forget to tear down your clusters in GCP if you no longer need them!** + +## MDCB Synchroniser + +### Overview + +Data Plane Gateways need resources such as API keys, certificates, and OAuth clients to process API requests. To ensure high availability and resilience, these resources need to be synchronized from the Control Plane to the Data Planes. + +The MDCB Synchroniser is a feature that proactively pushes these resources to the Data Planes when they start up. This improves resilience if the MDCB link or the Control Plane is unavailable, as the Data Planes can continue to operate independently using the resources stored locally in their Redis instances. It also offers a performance improvement, as Gateways do not have to retrieve resources on demand when an API is first called. + + + **A note on spelling** + + Throughout this documentation, we use specific spelling conventions to help distinguish between product features and general concepts: + - Synchroniser (with an ‘s’) refers specifically to a core functionality of Tyk MDCB + - synchronize (with a ‘z’) and its derivatives refer to the general concept of synchronization + + This British/American English distinction helps clarify when we’re discussing the Tyk MDCB feature versus general synchronization concepts. + + +### How Resources are Synchronized + +There are two models for synchronizing resources from the Control Plane to the Data Planes: on-demand retrieval and proactive synchronization using the Synchroniser. + +#### Without the Synchroniser (On-demand) + +If the Synchroniser is not in use, the Data Plane Gateways pull resources on demand. When a Gateway requires a resource (for example, a TLS certificate to serve a request), it first checks its local Redis cache. If the resource doesn't exist locally, it requests it from the Control Plane via the MDCB. Once retrieved, the resource is cached locally in Redis for future use. + +This model introduces a potential single point of failure. If the MDCB or Control Plane is down, a Data Plane Gateway cannot retrieve a resource it has not previously cached, which could impact API availability. + +Without Synchroniser + +#### With the Synchroniser (Proactive) + +When the Synchroniser is enabled, API keys, certificates, and OAuth clients are synchronized and stored in the Data Plane's Redis in advance. When one of these resources is created, modified, or deleted on the Control Plane, a signal is emitted, and the Data Planes update their local stores accordingly. This ensures that all required resources are already in place when the Gateway needs to handle traffic. + + + **Note** + + By default, the Synchroniser will synchronize all certificates to every Data Plane. In Tyk 5.12.0, we added the option to configure a more granular, selective synchronization for TLS certificates. This is particularly useful in environments with a large number of certificates where different Data Planes serve different domains. + + This selective approach reduces both the memory footprint of each Data Plane's Redis instance and unnecessary network traffic between the Control and Data Planes. + + + +Considerations: + +- **Redis Storage**: Proactively synchronizing all resources will increase the storage requirements for each Data Plane's local Redis instance. Using the selective certificate sync feature from Tyk 5.12.0 can help mitigate this for certificates. +- **Data Residency**: The synchronization of keys and OAuth clients is an all-or-nothing process. All keys and OAuth clients will be propagated to all Data Planes. If you have strict data residency requirements, be aware that there is no mechanism to restrict these resources to specific Gateway groups. + +With Synchroniser + +### Configuring the Synchroniser for Tyk Self Managed + +The Synchroniser is disabled by default. To enable it, please configure both the Data Plane Gateways and MDCB accordingly. + +#### Data Plane configuration + +The Synchroniser behaviour is configured in [`slave_options`](/tyk-oss-gateway/configuration#slave_options) object in the Data Plane Gateway's `tyk.conf` file or equivalent environment variables, in addition to the [standard Data Plane Gateway settings](/api-management/mdcb#data-plane-gateway-configuration): + +```json +{ + "slave_options": { + "synchroniser_enabled": true, + "sync_used_certs_only": true + } +} +``` + +| Property | Description | +| :--------------------- | :--------------------------------------- | +| `synchroniser_enabled` | Set to `true` to use the Synchroniser | +| `sync_used_certs_only` | Synchronize only the TLS certificates used by APIs loaded to the Gateway | + + + *Note* + + The `sync_used_certs_only` option was added in Tyk 5.12.0 to prevent the Data Plane from loading irrelevant TLS certificates (which could generate noise in the Gateway application logs if expired). With this option set to `true` only certificates required for APIs loaded by the Gateway will be synchronised to the Data Plane. + + In Tyk 5.12.0, there is a small limitation. When this option is enabled, certificates registered for APIs secured with [Certificate Authentication](/api-management/authentication/certificate-auth) are retrieved on demand rather than during the initial sync. + + +#### Control Plane configuration + +The Synchroniser behaviour is configured in the [`sync_worker_config`](/tyk-multi-data-centre/mdcb-configuration-options#sync_worker_config) object in the MDCB's `tyk_sink.conf` file or equivalent environment variables, in addition to the [standard MDCB settings](/api-management/mdcb#configuration): + +```json +{ + "sync_worker_config": { + "enabled": true, + "hash_keys": true, + "max_batch_size": 1000, + "time_between_batches": 0, + "max_workers": 10000, + "warmup_time": 2, + "group_key_ttl": 180 + } +} +``` + +| Property | Description | +| :--------------- | :--------------------------------------- | +| `enabled` | Set to `true` to use the Synchroniser | +| `hash_keys` | Set to `true` if [key hashing](/api-management/access-control/sessions-and-keys/key-hashing) is also enabled in your Tyk Dashboard and Gateway configurations. This ensures that the Synchroniser correctly handles hashed API keys. | +| `max_batch_size` | The maximum number of keys that the Synchroniser will fetch from Redis in a single batch. The default value is 1000. | +| `time_between_batches` | The cooldown period in seconds between fetching batches of keys. The default is 0, which means there is no delay. | +| `max_workers` | The maximum number of Gateway groups (clusters) that can be synchronized concurrently. The default value is 10000. | +| `warmup_time` | The time in seconds that MDCB will wait before starting the synchronization process. This allows the worker nodes to load APIs and policies from their local Redis before synchronising other resources. The default value is 2 seconds. | +| `group_key_ttl` | The Time To Live (TTL) in seconds for the group synchronization key in Redis. This key (`syncworker-{groupID}-{orgID}`) prevents multiple Gateways in the same group from triggering a sync simultaneously. The default value is 180 seconds. | + + +### Configuring the Synchroniser for Tyk Cloud + +Please [submit a support ticket](https://support.tyk.io/hc/en-gb) to us if you want to enable Synchroniser for your Tyk Cloud deployment. + +### Frequently Asked Questions + + + + + +You could check the MDCB log message to know when synchronization started and finished: + +``` +Starting oauth clients sync worker for orgID... +Starting keys sync worker for orgID... +Starting keys sync worker for orgID... + +Sync APIKeys worker for orgID:... +Sync Certs worker for orgID:... +Sync oauth worker for orgID:... +``` + + + + + +Synchronization will be triggered when the Time To Live (TTL) of a Data Plane Gateway expires. The default expiry duration is 3 minutes. The Time To Live (TTL) value can be set in the MDCB config using [`sync_worker_config.group_key_ttl`](/tyk-multi-data-centre/mdcb-configuration-options#sync_worker_config-group_key_ttl). + + + + diff --git a/api-management/metrics/custom-metrics.mdx b/api-management/metrics/custom-metrics.mdx new file mode 100644 index 0000000000..65e41a6f70 --- /dev/null +++ b/api-management/metrics/custom-metrics.mdx @@ -0,0 +1,429 @@ +--- +title: "Custom Metrics in Tyk Gateway" +description: "Define custom metric instruments in Tyk Gateway with your own dimensions sourced from request headers, JWT claims, session data, endpoint paths, and more." +keywords: "Custom Metrics, OpenTelemetry, OTLP, Dimensions, Labels, Multi-tenant, Counter, Histogram, Cardinality, config_data, endpoint, listen_path" +sidebarTitle: "Custom Metrics" +--- + +## Availability + +| Component | Version | Edition | +| :-------- | :------ | :------- | +| Tyk Gateway | Available since [v5.13.0](/developer-support/release-notes/gateway#5-13-0-release-notes) | Community & Enterprise | + +## Custom Metrics + +By default, Tyk Gateway exports [standard RED metrics](/api-management/metrics/default-metrics) with a fixed set of dimensions. Custom metrics let you define additional instruments with dimensions drawn from request headers, session data, JWT claims, and other context, enabling use cases such as: + +- **Multi-tenant billing:** Count requests per customer ID from a header or JWT claim +- **Tier-based SLOs:** Track latency separately for premium vs. standard API tiers +- **Business KPIs:** Measure domain-specific signals (e.g., successful transactions per payment provider) + +## The api_metrics Array + +Custom metrics are defined in the `api_metrics` array inside `opentelemetry.metrics`: + +```json +{ + "opentelemetry": { + "metrics": { + "enabled": true, + "api_metrics": [ + { ... } + ] + } + } +} +``` + +The behavior of this field depends on its value: + +| Value | Behavior | +|-------|----------| +| Field omitted or `null` | [Default RED metrics](/api-management/metrics/default-metrics) are exported automatically. | +| Empty array `[]` | API-level metrics are disabled entirely; no request metrics are exported. | +| Populated array | Only the instruments you define are exported. Default RED metrics are not exported unless you explicitly define them. | + + +**Preserving default RED metrics** + +When you populate `api_metrics`, the built-in RED instruments are no longer exported automatically. Use the block below as a starting point, it re-creates all four default instruments, then append your custom instruments after them. + +```json expandable +{ + "opentelemetry": { + "metrics": { + "enabled": true, + "api_metrics": [ + { + "name": "http.server.request.duration", + "type": "histogram", + "description": "End-to-end request latency", + "histogram_source": "total", + "dimensions": [ + { "source": "metadata", "key": "method", "label": "http.request.method" }, + { "source": "metadata", "key": "response_code", "label": "http.response.status_code" }, + { "source": "metadata", "key": "api_id", "label": "tyk.api.id" }, + { "source": "metadata", "key": "response_flag", "label": "tyk.response_flag" } + ] + }, + { + "name": "tyk.gateway.request.duration", + "type": "histogram", + "description": "Gateway processing time", + "histogram_source": "gateway", + "dimensions": [ + { "source": "metadata", "key": "method", "label": "http.request.method" }, + { "source": "metadata", "key": "api_id", "label": "tyk.api.id" }, + { "source": "metadata", "key": "response_flag", "label": "tyk.response_flag" } + ] + }, + { + "name": "tyk.upstream.request.duration", + "type": "histogram", + "description": "Upstream response time", + "histogram_source": "upstream", + "dimensions": [ + { "source": "metadata", "key": "method", "label": "http.request.method" }, + { "source": "metadata", "key": "api_id", "label": "tyk.api.id" }, + { "source": "metadata", "key": "response_flag", "label": "tyk.response_flag" } + ] + }, + { + "name": "tyk.api.requests.total", + "type": "counter", + "description": "Request count with identity dimensions", + "dimensions": [ + { "source": "metadata", "key": "method", "label": "http.request.method" }, + { "source": "metadata", "key": "response_code", "label": "http.response.status_code" }, + { "source": "metadata", "key": "api_id", "label": "tyk.api.id" } + ] + } + ] + } + } +} +``` + + +## Instrument Types + +Each entry in `api_metrics` defines one instrument: + +| Type | Description | +|------|-------------| +| `counter` | A monotonically increasing count. Use for request counts, error counts, etc. | +| `histogram` | A distribution of values. Use for latency measurements. | + +Histograms require a `histogram_source` field that selects which latency to measure: + +| `histogram_source` | Measures | +|--------------------|----------| +| `total` | End-to-end request latency (client to Gateway to upstream) | +| `gateway` | Gateway processing time only (excludes upstream response time) | +| `upstream` | Upstream service response time only | + +## Dimension Sources + +Dimensions let you slice your metrics by any signal available at request time. Tyk can source dimension values from six places; the sections below describe common use cases and which source and key to use for each. + +| Source | What It Provides | Example Keys | +|--------|-----------------|--------------| +| `metadata` | Request metadata always in scope | `method`, `response_code`, `api_id`, `api_name`, `org_id`, `response_flag`, `ip_address`, `api_version`, `host`, `scheme`, `listen_path`, `endpoint`. MCP APIs also expose `mcp_method`, `mcp_primitive_type`, `mcp_primitive_name`, `mcp_error_code`, see [MCP dimensions](#mcp-dimensions). | +| `session` | Authenticated session fields | `api_key`, `oauth_id`, `alias`, `portal_app`, `portal_org` | +| `header` | Any HTTP request header | `X-Customer-ID`, `X-Tenant-ID`, `Authorization` | +| `context` | Tyk [context variables](/api-management/traffic-transformation/request-context-variables) | `jwt_claims_tier`, `request_id`, `path_parts` | +| `response_header` | Any upstream response header | `X-Cache-Status`, `X-Backend-Version` | +| `config_data` | API definition metadata from the `config_data` map | Any key set on the API definition | + +### Response status + +Two `metadata` keys capture response status, and they answer different questions: + +| Key | Source | What it captures | +|-----|--------|-----------------| +| `response_code` | `metadata` | The numeric HTTP status code returned to the client (`200`, `429`, `502`, etc.). Always populated. Bounded cardinality (~100 values). | +| `response_flag` | `metadata` | A 3-letter error classification code set by the gateway, e.g. `URS` (upstream returned 5XX), `AKI` (API key invalid), `CBO` (circuit breaker open). Falls back to the HTTP status code string (e.g. `"200"`) when no error classification applies. See the [full list of response flags](/api-management/logs/access-logs#error-classification). | + +Use `response_code` to break down traffic by HTTP status. Use `response_flag` to understand Gateway-level error causes. + +The `status_codes` filter in the `filters` block decides *whether* a request is recorded at all. The `response_code` dimension attaches the actual code as a label on the recorded data points. A common pattern is to combine both: filter to error responses, then break them down by exact code: + +```json +{ + "name": "tyk.api.errors.by_status", + "type": "counter", + "dimensions": [ + { "source": "metadata", "key": "response_code", "label": "http_status_code" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ], + "filters": { "status_codes": ["4xx", "5xx"] } +} +``` + +### Request path + +Four options are available, at different cardinality levels: + +| | Source | Key | Cardinality | Notes | +|---|--------|-----|-------------|-------| +| API listen path | `metadata` | `listen_path` | Bounded (one value per API) | The configured listen path (e.g. `/api/v1/`). Safe to use in any metric. | +| Endpoint template | `metadata` | `endpoint` | Bounded (one per track endpoint) | The matched path template (e.g. `/user/{id}`). Requires `track_endpoints` configured on the API. Empty string for unmatched requests. | +| Raw request path | `context` | `path` | ⚠️ Potentially unbounded | The URL path as requested by the client (e.g. `/api/v1/users/12345`). Avoid unless paths have known low cardinality. | +| Path segments | `context` | `path_parts` | ⚠️ Potentially unbounded | The path split by `/` into a list of segments (e.g. `/api/v1/users/123` → `["api", "v1", "users", "123"]`). Access a specific segment with `{{ index ._tyk_context.path_parts N }}`. Carries the same cardinality risk as `path` if any segment varies per request. | + +Prefer `listen_path` or `endpoint` (with `track_endpoints`) over raw `path` or `path_parts` in most cases. + +### Client and auth identity + +Several sources expose client identity, with different cardinality trade-offs: + +- **Session fields** (`session` source): `alias` (the human-readable key alias), `portal_app` (Developer Portal app ID), and `portal_org` (Developer Portal org ID) are typically bounded and safe to use. `api_key` and `oauth_id` are ⚠️ high cardinality (one value per key/client); only use these with [cardinality control](/api-management/logs-metrics#cardinality-control) enabled. +- **JWT claims** (`context` source): any claim is available as `jwt_claims_` (e.g. `jwt_claims_tier`, `jwt_claims_tenant_id`). See [Request context variables](#request-context-variables) for setup requirements and cardinality notes. +- **Request headers** (`header` source): use when client identity is passed as a header (e.g. `X-Tenant-ID`). Cardinality depends on what the header contains. + +### Custom API metadata + +If you want to attach static, API-level metadata (such as team ownership, service tier, cost centre, or criticality) to your metrics without touching request headers or tokens, use the `config_data` source. + +The [config_data](/api-management/gateway-config-tyk-oas#pluginconfigdata) field in the API definition is a free-form key-value map you can populate per API: + +```json +{ + "config_data": { + "environment": "staging", + "team": "platform", + "criticality": "high" + } +} +``` + +A dimension like `{"source": "config_data", "key": "team"}` will carry `team="platform"` on every metric recorded for that API; no request modification required. + +**Fallback behaviour:** If `config_data_disabled` is `true` on the API, or the key is absent, the dimension falls back to the `default` value on the dimension definition. If no default is set, an empty string is used. All values are treated as strings. + +### Request context variables + +The `context` source reads from Tyk's [request context variables](/api-management/traffic-transformation/request-context-variables), a set of values extracted from the incoming request and enriched during middleware processing. The `context` source covers two kinds of variables: those populated automatically by Tyk, and those written by custom plugins. + + +Context variables must be enabled on the API definition (`enable_context_vars: true`) for the `context` source to work. Without this setting, all `context` dimensions will be empty. + + +#### Default context variables (no plugin required) + +When context variables are enabled, Tyk automatically populates the following variables for every request: + +| Key | Description | Cardinality | +|-----|-------------|-------------| +| `request_id` | A generated correlation ID for the request. | ⚠️ Unbounded, unique per request. Not suitable as a metric dimension. | +| `path` | The raw request path (e.g. `/api/v1/users/123`). | ⚠️ Potentially unbounded. See [Request path](#request-path). | +| `path_parts` | The request path split by `/` into a list of segments. E.g. `/api/v1/users/123` → `["api", "v1", "users", "123"]`. Access individual segments with `{{ index ._tyk_context.path_parts N }}`. | ⚠️ Potentially unbounded. | +| `remote_addr` | The connecting client IP address. | ⚠️ High cardinality. | +| `jwt_claims_` | Individual JWT claims, e.g. `jwt_claims_tier`, `jwt_claims_tenant_id`. Available when JWT auth is in use. | Bounded if the claim takes a fixed set of values (e.g. subscription tier). Safe to use. | +| `cookies_` | Cookie values by name (hyphens replaced with underscores). | Depends on cookie values. | +| `headers_` | Request header values by name (capitalised, hyphens replaced with underscores, e.g. `headers_User_Agent`). | Depends on header values. | +| `token` | The raw inbound bearer token. | ⚠️ Extremely high cardinality. Do not use as a dimension. | + +Usually the most useful defaults for metrics are **JWT claims**: they let you segment traffic by tenant, subscription tier, or any other bounded claim without requiring a plugin: + +```json +{ "source": "context", "key": "jwt_claims_tier", "label": "tier", "default": "standard" } +``` + +#### Custom context variables (via plugin) + +Go plugins and [JQ request transforms](/api-management/traffic-transformation/jq-transforms) can write arbitrary variables into the request context, which are then accessible as `context` source dimensions. This is useful for computed signals, such as enriched tenant IDs, feature flags, routing decisions, or values decoded from opaque tokens. + +**Go plugin:** + +```go +import "github.com/TykTechnologies/tyk/ctx" + +func MyPlugin(w http.ResponseWriter, r *http.Request) { + if ctxData, ok := r.Context().Value(ctx.ContextData).(map[string]interface{}); ok { + ctxData["my_custom_var"] = "value" + } +} +``` + +**JQ transform**: return a `tyk_context` object alongside the transformed body: + +```json +{ + "body": "", + "tyk_context": { "my_custom_var": "value" } +} +``` + +The variable is then available as a dimension: + +```json +{ "source": "context", "key": "my_custom_var", "label": "my_variable" } +``` + + +Python, gRPC (coprocess), and JavaScript (JSVM) plugins cannot write to request context variables. To pass data from these plugins into metrics dimensions, use session metadata (`session` source, via `session.meta_data`) or inject a custom HTTP request header (`header` source) instead. + + +### MCP dimensions + +When a request is handled by an MCP API, Tyk populates four additional `metadata` keys derived from the JSON-RPC payload. These are available as dimensions in any custom metric instrument. + +| Key | Description | Example Values | Cardinality | +|-----|-------------|----------------|-------------| +| `mcp_method` | JSON-RPC method invoked | `tools/call`, `initialize`, `resources/read`, `prompts/get` | Bounded | +| `mcp_primitive_type` | MCP primitive category | `tool`, `resource`, `prompt` | Bounded (3 values) | +| `mcp_primitive_name` | Name of the specific tool, resource, or prompt | `get_current_weather`, `search_docs` | Bounded per API | +| `mcp_error_code` | JSON-RPC error code on failure; empty string on success | `-32001`, `-32002`, `` | Bounded | + + +These keys are only populated for MCP APIs. For non-MCP requests all four keys resolve to an empty string; use the `default` field on the dimension definition to set a fallback value. There is no performance overhead on non-MCP metric configurations. + + +**Example: count tool calls by tool name:** + +```json +{ + "name": "tyk.mcp.tool_calls.total", + "type": "counter", + "description": "MCP tool invocations by tool name and API", + "dimensions": [ + { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" }, + { "source": "metadata", "key": "mcp_primitive_type", "label": "primitive_type", "default": "" }, + { "source": "metadata", "key": "api_id", "label": "api_id" } + ], + "filters": { "methods": ["POST"] } +} +``` + +For a complete set of MCP monitoring use cases, see [MCP Observability](/ai-management/mcp-gateway/mcp-metrics). + +### Other request metadata + +Remaining `metadata` keys available for general request context: + +| Key | Description | Cardinality | +|-----|-------------|-------------| +| `method` | HTTP method (`GET`, `POST`, etc.) | Bounded | +| `api_id` | Tyk API ID | Bounded | +| `api_name` | API display name | Bounded | +| `org_id` | Organisation ID | Bounded | +| `api_version` | API version name | Bounded | +| `host` | Request host | Typically bounded | +| `scheme` | URL scheme (`http`, `https`) | Bounded | +| `ip_address` | Client IP address | ⚠️ High cardinality | + +Each dimension definition has four fields: + +| Field | Required | Description | +|-------|----------|-------------| +| `source` | Yes | One of the source names above. | +| `key` | Yes | The specific field or header name to read from. | +| `label` | Yes | The dimension name as it appears in your metrics backend. | +| `default` | No | Fallback value when the key is not present on the request. | + +## Filters + +Each instrument can restrict which requests it records using the `filters` block. All filter conditions use AND logic (a request must match all specified filters): + +| Filter Field | Type | Description | +|--------------|------|-------------| +| `api_ids` | string array | Only record requests to these API IDs. | +| `methods` | string array | Only record requests with these HTTP methods (e.g., `["GET", "POST"]`). | +| `status_codes` | string array | Only record responses with these status codes. Supports exact values (`"200"`) and class patterns (`"2xx"`, `"4xx"`, `"5xx"`). | + +## Example Configuration + +The following example defines two custom instruments: + +1. A counter that tracks requests by customer ID and subscription tier +2. A histogram that tracks latency for premium-tier requests only + +```json expandable +{ + "opentelemetry": { + "metrics": { + "enabled": true, + "api_metrics": [ + { + "name": "tyk.requests.by_customer", + "type": "counter", + "description": "Request count by customer ID and subscription tier", + "dimensions": [ + { + "source": "header", + "key": "X-Customer-ID", + "label": "customer_id", + "default": "unknown" + }, + { + "source": "context", + "key": "jwt_claims_tier", + "label": "tier", + "default": "standard" + }, + { + "source": "metadata", + "key": "api_id", + "label": "api_id" + } + ], + "filters": { + "api_ids": ["payments-api", "orders-api"] + } + }, + { + "name": "tyk.latency.premium_tier", + "type": "histogram", + "description": "End-to-end latency for premium tier requests", + "histogram_source": "total", + "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + "dimensions": [ + { + "source": "context", + "key": "jwt_claims_tier", + "label": "tier" + }, + { + "source": "metadata", + "key": "api_id", + "label": "api_id" + } + ], + "filters": { + "methods": ["POST", "PUT", "PATCH"], + "status_codes": ["2xx"] + } + } + ] + } + } +} +``` + +## Full Field Reference + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Metric instrument name. Use lowercase with dots or underscores (e.g., `tyk.requests.by_customer`). | +| `type` | string | Yes | `"counter"` or `"histogram"`. | +| `description` | string | No | Human-readable description included in metric metadata. | +| `dimensions` | array | Yes | List of dimension definitions. See [Dimension Sources](#dimension-sources). | +| `filters` | object | No | Restricts which requests are recorded. See [Filters](#filters). | +| `histogram_source` | string | Histograms only | `"total"`, `"gateway"`, or `"upstream"`. | +| `histogram_buckets` | float array | No | Custom histogram bucket boundaries in seconds. If omitted, OTel default buckets are used. | + +## Cardinality Considerations + +Custom dimensions can significantly increase cardinality. Before adding a dimension, estimate the number of unique values it can take: + +- `api_id`: bounded (tens or hundreds of APIs) +- `method`: bounded (fewer than 10 values) +- `customer_id` from a header: potentially unbounded if unique per user + +The `cardinality_limit` setting (default: 2,000 unique combinations per instrument) protects against unbounded cardinality. When the limit is reached, new combinations are tracked in an overflow bucket. See [Cardinality Control](/api-management/logs-metrics#cardinality-control) for details. + + +On Tyk Cloud, custom metric configuration is not currently self-service. Contact Tyk support to request custom dimensions for your Cloud deployment. + diff --git a/api-management/metrics/default-metrics.mdx b/api-management/metrics/default-metrics.mdx new file mode 100644 index 0000000000..06bd14fcbc --- /dev/null +++ b/api-management/metrics/default-metrics.mdx @@ -0,0 +1,181 @@ +--- +title: "Default Tyk Gateway Metrics Reference" +description: "Complete reference of all metrics exported by Tyk Gateway when OpenTelemetry is enabled: request metrics, Go runtime metrics, and configuration state metrics." +keywords: "OpenTelemetry, Metrics, RED Metrics, Request Duration, Go Runtime, Prometheus, Grafana, API Observability" +sidebarTitle: "Default Metrics" +--- + +## Availability + +| Component | Version | Edition | +| :-------- | :------ | :------- | +| Tyk Gateway | Available since [v5.13.0](/developer-support/release-notes/gateway#5-13-0-release-notes) | Community & Enterprise | + +## Default Gateway Metrics + +When `opentelemetry.metrics.enabled` is set to `true`, Tyk Gateway automatically exports three groups of metrics, no additional configuration required: + +1. [Request metrics](#request-metrics): Rate, Errors, and Duration (RED) for every API request +2. [Go runtime metrics](#go-runtime-metrics): Memory, goroutines, and GC health +3. [Configuration state metrics](#configuration-state-metrics): API/policy load counts and reload events + +All metrics are exported as OpenTelemetry-semantic-convention-compliant instruments and include [resource attributes](#resource-attributes) that identify the gateway instance. + +### Request Metrics + +These four instruments cover the standard RED signals for every API proxied by the Gateway. + +| Metric Name | Type | Unit | Description | +|-------------|------|------|-------------| +| `http.server.request.duration` | Histogram | seconds | End-to-end request latency (client to Gateway to upstream and back). | +| `tyk.gateway.request.duration` | Histogram | seconds | Time spent inside the gateway only (middleware processing, routing). Excludes upstream response time. | +| `tyk.upstream.request.duration` | Histogram | seconds | Time spent waiting for the upstream service to respond. | +| `tyk.api.requests.total` | Counter | requests | Total number of requests processed. | + +The three-way latency breakdown is particularly useful for distinguishing gateway processing overhead from upstream service slowness. + +#### Default Dimensions + +Default dimensions vary by instrument: + +| Instrument | `http.request.method` | `http.response.status_code` | `tyk.api.id` | `tyk.response_flag` | +|-----------|:---:|:---:|:---:|:---:| +| `http.server.request.duration` | Yes | Yes | Yes | Yes | +| `tyk.gateway.request.duration` | Yes | - | Yes | Yes | +| `tyk.upstream.request.duration` | Yes | - | Yes | Yes | +| `tyk.api.requests.total` | Yes | Yes | Yes | - | + +| Dimension | Description | +|-----------|-------------| +| `http.request.method` | HTTP request method (`GET`, `POST`, etc.). | +| `tyk.api.id` | The Tyk API ID. | +| `http.response.status_code` | HTTP response status code (`200`, `404`, `500`, etc.). | +| `tyk.response_flag` | Tyk 3-letter error classification code (e.g., `AKI`, `URS`, `CBO`), or the HTTP status code string (e.g., `"200"`) for successful requests or when error classification is absent. See the [full list of response flags](/api-management/logs/access-logs#error-classification). | + +You can add more dimensions to any instrument using [custom metrics](/api-management/metrics/custom-metrics). + +### Go Runtime Metrics + +These metrics are exported automatically and reflect the health of the Go runtime inside the Gateway process. They can be disabled by setting `runtime_metrics: false` in the metrics config. + +| Metric Name | Type | Unit | Description | +|-------------|------|------|-------------| +| `go.memory.used` | Gauge | bytes | Current heap memory in use. | +| `go.memory.limit` | Gauge | bytes | Soft memory limit set via `GOMEMLIMIT`. | +| `go.memory.allocated` | Gauge | bytes | Total bytes allocated to the heap. | +| `go.memory.allocations` | Counter | bytes | Cumulative bytes allocated since startup. | +| `go.memory.gc.goal` | Gauge | bytes | Target heap size for the next GC cycle. | +| `go.goroutine.count` | Gauge | goroutines | Number of active goroutines. Useful for detecting goroutine leaks. | +| `go.processor.limit` | Gauge | threads | Number of OS threads available (`GOMAXPROCS`). | +| `go.config.gogc` | Gauge | percent | GC target percentage (`GOGC` env var). | + +### Configuration State Metrics + +These metrics reflect the operational state of the Gateway's loaded configuration. They are useful for detecting silent configuration failures, for example, alerting when the number of loaded APIs drops unexpectedly. + +| Metric Name | Type | Unit | Description | +|-------------|------|------|-------------| +| `tyk.gateway.apis.loaded` | Gauge | APIs | Number of API definitions currently loaded and active. | +| `tyk.gateway.policies.loaded` | Gauge | policies | Number of policies currently loaded and active. | +| `tyk.gateway.config.reload.total` | Counter | reloads | Cumulative number of configuration reload events since startup. | +| `tyk.gateway.config.reload.duration` | Histogram | seconds | Time taken to complete each configuration reload cycle. | + +## Resource Attributes + +Every metric exported from a Gateway instance carries resource attributes, metadata that identifies the source. These are set once at startup and attached to all metrics. + +### Tyk-Specific Resource Attributes + +| Attribute | Always Present | Example | Description | +|-----------|---------------|---------|-------------| +| `tyk.gw.id` | Yes | `gw-prod-eu-west-1-a` | Unique Gateway ID. | +| `tyk.gw.dataplane` | Yes | `true` | Whether the Gateway is running in a distributed Data Plane | +| `tyk.gw.group.id` | No | `prod-eu-west-1` | Present only for Data Plane Gateways, identifies the group. | +| `tyk.gw.tags` | No | `["region:eu", "env:prod"]` | Present only when the Gateway has [segment tags](/api-management/api-sharding#what-is-api-sharding-) configured. | + +### Standard OTel Resource Attributes + +| Category | Attributes | +|----------|-----------| +| Service | `service.name`, `service.version`, `service.instance.id` | +| Host | `host.name`, `host.arch`, `host.ip` | +| Process | `process.pid` | + +### Using Resource Attributes in Prometheus + +When using an OTel Collector to export to Prometheus, enable `resource_to_telemetry_conversion` to expose resource attributes as metric labels: + +```yaml +exporters: + prometheus: + endpoint: "0.0.0.0:8889" + resource_to_telemetry_conversion: + enabled: true +``` + +Alternatively, add a transform processor to metrics pipeline for OTLP exporters: + +```yaml +processors: + transform/tyk_gw_resource_attrs: + error_mode: ignore + metric_statements: + - context: datapoint + statements: + - set(attributes["tyk_gw_group_id"], resource.attributes["tyk.gw.group.id"]) where resource.attributes["tyk.gw.group.id"] != nil + - set(attributes["tyk_gw_id"], resource.attributes["tyk.gw.id"]) where resource.attributes["tyk.gw.id"] != nil + - set(attributes["tyk_gw_tags"], resource.attributes["tyk.gw.tags"]) where resource.attributes["tyk.gw.tags"] != nil + - set(attributes["tyk_gw_dataplane"], resource.attributes["tyk.gw.dataplane"]) where resource.attributes["tyk.gw.dataplane"] != nil +``` + +This allows PromQL queries like: + +```promql +# All metrics from a specific gateway node +{tyk_gw_id="gw-prod-eu-west-1-a"} + +# Compare request rates across gateways in an edge group +sum by (tyk_gw_id) ( + rate(tyk_api_requests_total{tyk_gw_group_id="edge-eu-west"}[5m]) +) +``` + +Without this setting, resource attributes are available via the `target_info` metric and can be joined in PromQL. + +## Exemplars + +Exemplars are sample observations attached to histogram metric data points that carry the active trace context (specifically the `trace_id` and `span_id` of the request that produced the measurement). They create a direct, clickable link from an aggregated metric to the specific trace that caused an anomaly. + +### How they work in Tyk + +When OpenTelemetry tracing is enabled and a histogram metric (such as `http.server.request.duration`) is recorded during an active sampled request, the OTel Go SDK automatically attaches the current trace and span IDs to the observation. Exemplars are only attached for sampled requests. + +### Infrastructure requirements + +Exemplar storage and rendering must be enabled in your observability stack; nothing additional is needed on the Tyk side. + +For example, in the following standard OTel pipeline: + +``` +Tyk Gateway + └─ OTLP/gRPC ──► OTel Collector ──► Prometheus ──► Grafana + (exemplar storage) (exemplar markers) +``` + +| Component | Requirement | +|-----------|-------------| +| OTel Collector | Any version supporting OTLP metrics with exemplar passthrough | +| Prometheus | v2.26+. Start with `--enable-feature=exemplar-storage` | +| Grafana | v7.4+. Enable "Exemplars" toggle on the Prometheus data source | + +### Querying exemplars in Prometheus + +Exemplars are accessible via the Prometheus exemplar API: + +``` +GET /api/v1/query_exemplars?query=http_server_request_duration_seconds&start=&end= +``` + +### Grafana workflow + +When exemplar rendering is enabled, Grafana displays scatter plot markers on histogram panels. Clicking a marker navigates directly to the linked trace in your configured tracing backend (Jaeger, Grafana Tempo, etc.), with no manual trace ID lookup required. \ No newline at end of file diff --git a/api-management/metrics/metrics-pumps.mdx b/api-management/metrics/metrics-pumps.mdx new file mode 100644 index 0000000000..404c9a34d3 --- /dev/null +++ b/api-management/metrics/metrics-pumps.mdx @@ -0,0 +1,239 @@ +--- +title: "Metrics Pumps (Legacy)" +description: "Configure Tyk Pump to expose Prometheus, StatsD, or DogStatsD metrics derived from traffic logs." +keywords: "Tyk Pump, Prometheus, StatsD, DogStatsD, Datadog, Grafana, Metrics, Legacy" +sidebarTitle: "Metrics Pumps (Legacy)" +--- + + +For new deployments, we recommend [OpenTelemetry Metrics](/api-management/logs-metrics#opentelemetry-metrics) instead: Tyk Gateway pushes Rate, Errors, and Duration (RED) metrics via an OpenTelemetry Collector. This page remains relevant for existing deployments already using Tyk Pump for metrics. + + +## Introduction + +[Tyk Pump](/api-management/tyk-pump) can independently expose metrics, derived from the traffic logs it reads out of Redis, the same traffic logs used for [Dashboard Analytics](/api-management/dashboard-analytics). These metrics are computed by Tyk Pump after the fact from traffic log records, so it can only produce traffic-derived counters and histograms (status codes, latency, and whatever traffic log fields you tag). It has no visibility into Gateway's own runtime health or configuration state, unlike [OpenTelemetry metrics](/api-management/metrics/default-metrics). + +Three pump types expose metrics this way, all deriving from the same traffic log fields: + +| Pump Type | Format | +| :-- | :-- | +| [Prometheus](#prometheus) | Exposes an HTTP endpoint for Prometheus to scrape | +| [StatsD](#statsd) | Sends per-request metrics to a StatsD server | +| [DogStatsD](#dogstatsd-datadog) | Sends per-request metrics in the DogStatsD format, for example to Datadog | + + +Not to be confused with [Tyk Gateway's native StatsD instrumentation](/api-management/logs-metrics#statsd-instrumentation): that's a completely different, built-in mechanism exporting internal system metrics (request rate, goroutine health, and so on), not traffic-log-derived request metrics. The [StatsD pump](#statsd) on this page is unrelated to that feature. + + +## Prometheus + +Tyk Pump can expose Prometheus metrics derived from the traffic logs it reads out of Redis, useful for tracking how often your APIs are called, how they perform, and what status codes they return. See the [demo project on GitHub](https://github.com/TykTechnologies/demo-slo-prometheus-grafana) for a working example. + +Unlike the other pumps on this page, Tyk Pump doesn't send these metrics anywhere itself: it exposes them on a plain HTTP endpoint (`/metrics` by default), and Prometheus scrapes that endpoint on its own schedule. + + +This endpoint has no built-in authentication or TLS: anyone who can reach it can read all your metrics. That's more than a data-exposure risk: request rates, status codes, and latency broken down per API give an attacker a detailed map of your system's architecture, traffic patterns, and likely weak points, valuable reconnaissance for an attack. Restrict access at the network level, for example with firewall rules or security groups, rather than relying on Tyk Pump to secure it. + + +### Base Metrics + +By default, the `prometheus` pump exposes these metrics: + +| Metric | Type | Labels | Description | +| :-- | :-- | :-- | :-- | +| `tyk_http_status` | Counter | `code`, `api` | HTTP status codes per API. | +| `tyk_http_status_per_path` | Counter | `code`, `api`, `path`, `method` | HTTP status codes per API path and method. | +| `tyk_http_status_per_key` | Counter | `code`, `key` | HTTP status codes per access key. | +| `tyk_http_status_per_oauth_client` | Counter | `code`, `client_id` | HTTP status codes per OAuth client. | +| `tyk_latency` | Histogram | `type`, `api` | Latency added by Tyk, per API. `type` distinguishes total, upstream, and gateway latency. | + +You can add further custom metrics of your own; see [Custom Metrics](#custom-metrics) below. + +### Configuring the Pump + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `prometheus` pump accepts the following `meta` fields. Add them to `pump.conf`: + +```json +{ + "pumps": { + "prometheus": { + "type": "prometheus", + "meta": { + "listen_address": ":9090", + "path": "/metrics", + "custom_metrics": [], + "disabled_metrics": [] + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `listen_address` | - | Bind address for the metrics endpoint, in Go's `host:port` format. Use `:9090` to listen on all interfaces; add a host, for example `127.0.0.1:9090`, only if you want to restrict it to one interface. | +| `path` | `/metrics` | HTTP path Prometheus scrapes. | +| `custom_metrics` | (none) | Additional counters and histograms, on top of the [base metrics](#base-metrics) above. See [Custom Metrics](#custom-metrics) below. | +| `disabled_metrics` | (none) | Base metric families to remove. | + +Tyk Pump will listen on the port configured in `listen_address`. The Prometheus scraper must be configured to access that port on the Tyk Pump service. You must ensure that the port is exposed for the scraper to find. + +Restart Tyk Pump, then verify metrics are exposed by visiting `http://:9090/metrics` from a machine that can reach it. Then add a scrape target to your [Prometheus configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) pointing at that same host and port, and connect a [Grafana data source](https://grafana.com/docs/grafana/latest/datasources/add-a-data-source/) to your Prometheus server. See the [Tyk Pump GitHub repository](https://github.com/TykTechnologies/tyk-pump#prometheus) for example dashboard queries, including request rate, error rate, and percentile latency, per API or across all APIs. + +### Custom Metrics + +The base metrics above cover a fixed set of dimensions. You can define your own counters and histograms instead, built from any traffic log fields you choose as labels, for example, you could break down requests by a custom aggregation tag or API alias that the base metrics don't expose. + +Add custom metrics to your Prometheus pump using the `custom_metrics` object in the pump `meta`: + +```json +"custom_metrics": [ + { + "name": "tyk_http_requests_total", + "description": "Total of API requests", + "metric_type": "counter", + "labels": ["response_code", "api_name", "method", "api_key", "alias", "path"] + }, + { + "name": "tyk_http_latency", + "description": "Latency of API requests", + "metric_type": "histogram", + "labels": ["type", "response_code", "api_name", "method", "api_key", "alias", "path"], + "buckets": [1, 5, 10, 50, 100, 500, 1000, 5000, 10000] + } +], +``` + +Each entry accepts: + +| Field | Default | Description | +| :-- | :-- | :-- | +| `name` | - | Metric name. | +| `description` | - | Metric description. | +| `metric_type` | - | `counter` or `histogram`. Histograms always observe the `request_time` field. | +| `labels` | - | Traffic log fields to expose as metric labels. Available values: `host`, `method`, `path`, `response_code`, `api_key`, `time_stamp`, `api_version`, `api_name`, `api_id`, `org_id`, `oauth_id`, `request_time`, `ip_address`, `alias`, `mcp_method`, `mcp_primitive_type`, `mcp_primitive_name`, `mcp_error_code`. | +| `buckets` | `[1, 2, 5, 7, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 1000, 2000, 5000, 10000, 30000, 60000]` | Histogram bucket boundaries, in milliseconds. Histograms only. | +| `mcp_only` | `false` | Restrict this metric to [MCP Gateway](/ai-management/mcp-gateway/overview) traffic. When `false`, the metric processes all traffic, and the `mcp_*` labels resolve to an empty string for non-MCP requests. | + + +`mcp_method`, `mcp_primitive_type`, `mcp_primitive_name`, and `mcp_error_code` are only populated for MCP Gateway traffic. Use `mcp_only` to keep a metric exclusively for MCP records, for example to count MCP tool and prompt invocations separately from your REST API traffic: + +```json +{ + "name": "tyk_mcp_requests_total", + "description": "Total MCP requests per method and primitive", + "metric_type": "counter", + "labels": ["api_id", "mcp_method", "mcp_primitive_type", "mcp_primitive_name"], + "mcp_only": true +} +``` + + +### Docker + +If `listen_address` is set to `:9090`, make sure to publish port 9090 in addition to the health check port. + +For example, in Docker Compose: + +```yaml +tyk-pump: + image: tykio/tyk-pump-docker-pub:${PUMP_VERSION} + ports: + - 8083:8083 + - 9090:9090 +``` + +### Kubernetes + +Tyk Pump's Prometheus endpoint can be scraped on Kubernetes using either of the standard Prometheus discovery mechanisms: + +- **Prometheus Operator**: if enabled on your cluster (for example via the [kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack) chart), it looks for `PodMonitor` or `ServiceMonitor` resources and scrapes the specified port automatically. Enable this with `tyk-pump.pump.prometheusPump.prometheusOperator.enabled=true` on the [Tyk OSS Helm chart](/product-stack/tyk-charts/tyk-oss-chart). +- **Pod annotations**: if your Prometheus deployment scrapes pods by annotation instead, for example using the [prometheus-community/prometheus](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus#scraping-pod-metrics-via-annotations) chart, set matching `prometheus.io/scrape`, `prometheus.io/path`, and `prometheus.io/port` annotations via `tyk-pump.pump.podAnnotations`. + +Either way, Tyk Pump must also be exposed as a Kubernetes service so Prometheus can reach `/metrics`: set `tyk-pump.pump.service.enabled: true`. + +Tyk Pump exposing Prometheus metrics on Kubernetes + +## StatsD + +[StatsD](https://github.com/etsy/statsd) is a network daemon that listens for statistics sent over UDP or TCP and forwards aggregates to pluggable backend services. The `statsd` pump sends per-request metrics to a StatsD server this way, derived from the same traffic logs as the other pumps on this page. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `statsd` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "statsd": { + "type": "statsd", + "meta": { + "address": "localhost:8125", + "fields": ["request_time"], + "tags": ["path", "response_code", "api_key", "api_version", "api_name", "api_id", "raw_request", "ip_address", "org_id", "oauth_id"], + "separated_method": false + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `address` | - | StatsD server host and port. | +| `fields` | - | Traffic log fields sent as timing values. Accepts `request_time`, `latency_total`, `latency_upstream`, and `latency_gateway`. | +| `tags` | - | Traffic log fields sent as tags. | +| `separated_method` | `false` | By default, the method and path are combined into a single path field. Set to `true` to record the method in its own field instead. | + +## DogStatsD (Datadog) + +Tyk Pump can send API traffic analytics to [Datadog](https://www.datadoghq.com/), which you can use to build [dashboards](https://docs.datadoghq.com/integrations/tyk/#dashboards) from your API traffic. + +**Prerequisites:** + +- A working Datadog agent. See the [Datadog Tyk integration documentation](https://docs.datadoghq.com/integrations/tyk/). +- A [Tyk Self-Managed](/tyk-self-managed/install) or [Tyk Open Source](/apim/open-source/installation) installation with [Tyk Pump](/api-management/tyk-pump) configured. + +**How it works:** when running the Datadog Agent, the `dogstatsd` pump sends the [request_time](https://docs.datadoghq.com/integrations/tyk/#data-collected) metric from Tyk Pump in real time, per request, so you can aggregate by API, version, response code, method, and other parameters. + +In addition to the [common pump settings](/api-management/tyk-pump#common-pump-settings), the `dogstatsd` pump accepts the following `meta` fields: + +```json +{ + "pumps": { + "dogstatsd": { + "type": "dogstatsd", + "meta": { + "address": "dd-agent:8126", + "namespace": "tyk", + "async_uds": true, + "async_uds_write_timeout_seconds": 2, + "buffered": true, + "buffered_max_messages": 32, + "sample_rate": 0.9999999999, + "tags": ["method", "response_code", "api_version", "api_name", "api_id", "org_id", "tracked", "path", "oauth_id"] + }, + ... + } + } +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `address` | - | Datadog agent host and port. | +| `namespace` | - | Prefix applied to metrics sent to Datadog. | +| `async_uds` | `false` | Enable async [UDS over UDP](https://github.com/Datadog/datadog-go#unix-domain-sockets-client). | +| `async_uds_write_timeout_seconds` | - | Write timeout in seconds if `async_uds: true`. | +| `buffered` | `false` | Enable buffering of messages. | +| `buffered_max_messages` | `16` | Maximum messages per datagram if `buffered: true`. | +| `sample_rate` | `1` | Fraction of requests to sample; `1` is 100%, `0.5` samples 50%. | +| `tags` | `path`, `method`, `response_code`, `api_version`, `api_name`, `api_id`, `org_id`, `tracked`, `oauth_id` | Traffic log fields sent as tags. | + + +Including `path` as a tag can generate significant cardinality, since it's unbounded. + + +Tyk maintains a default Datadog dashboard canvas to give you an easier starting point. In the Datadog portal, under [Dashboards → Lists](https://app.datadoghq.com/dashboard/lists), it's named **Tyk Analytics Canvas**. To use it, ensure your Datadog agent deployment has the tag `env:tyk-demo-env` and that `dogstatsd.meta.namespace` is set to `pump`. You can also import it from the [Datadog integrations-extras repository](https://github.com/DataDog/integrations-extras/blob/master/tyk/assets/dashboards/tyk_analytics_canvas.json) and adjust those values to match your own setup. + +Sample Datadog dashboard diff --git a/api-management/migrate-from-tyk-classic.mdx b/api-management/migrate-from-tyk-classic.mdx new file mode 100644 index 0000000000..e992774529 --- /dev/null +++ b/api-management/migrate-from-tyk-classic.mdx @@ -0,0 +1,197 @@ +--- +title: "Migrating from Tyk Classic APIs" +description: "API Migration: Converting Tyk Classic APIs to Tyk OAS Format" +keywords: "Tyk OAS API, Tyk Classic API, Migrate, Convert, Migration, Tyk Classic, Tyk OAS, API definition" +sidebarTitle: "Migrate from Tyk Classic APIs" +--- + +## Overview + +From Tyk 5.8.0, you can convert your existing [Tyk Classic APIs](/api-management/gateway-config-tyk-classic) to the recommended [Tyk OAS API](/api-management/gateway-config-tyk-oas) format. + +The API Migration feature provides a powerful way to convert your existing Tyk Classic API definitions to the newer Tyk OAS format with various options to ensure a smooth transition. We've built support into the Tyk Dashboard's [API Designer](/api-management/migrate-from-tyk-classic#using-the-api-designer) to convert individual Tyk Classic APIs one by one. The Tyk Dashboard API [migrate endpoint](/api-management/migrate-from-tyk-classic#using-the-migration-api) allows you to migrate individual APIs or perform bulk migrations. + +### Features of Tyk Dashboard's Migration Feature + +- **Flexibility**: Migrate APIs using the API Designer or using the Tyk Dashboard API +- **Bulk Migration**: Convert multiple APIs in a single operation via the Dashboard API +- **Risk Mitigation**: Test migrations before applying changes +- **Phased Implementation**: Stage and test migrations before final deployment +- **Detailed Reporting**: Get comprehensive success, failure, and skip information + +## Migration Modes + +Tyk Dashboard supports four different migration modes to suit your needs: + +1. **Dry Run Mode**: Simulates the conversion process without making changes. This mode returns the converted API definitions in Tyk OAS format for your review, allowing you to verify the conversion before committing to it. + +2. **Stage Mode**: Perform a phased conversion by creating staged copies of your APIs in Tyk OAS format alongside the existing Tyk Classic APIs. You can then thoroughly test that the migrated APIs will behave as expected without affecting production traffic. When you are happy, you can **promote** the staged APIs. + + The **staged** API will be the same as the original Tyk Classic, with the following modifications to allow it to coexist: + + - The API ID will have the `staging-` prefix added + - `[staged] ` prefix will be added to the API name + - The listen path will be modified by the addition of the `/tyk-staging` prefix + - A reference will be added to link the staged API to the original Tyk Classic API's ID + +3. **Promote Mode**: Promote previously staged APIs, replacing their Tyk Classic counterparts. This process removes the staging prefixes from ID, name, and listen path. It then replaces the original Tyk Classic API with the Tyk OAS version - the Tyk OAS API will inherit both the API ID and database ID of the original API. + +4. **Direct Mode**: Directly migrates APIs from Tyk Classic to Tyk OAS format without staging; typically this will be used if testing was performed on a **dry run** copy of the original. This mode will also replace the original Tyk Classic API with the Tyk OAS version - the Tyk OAS API will inherit both the API ID and database ID of the original API. + + + + + Note that both Promote and Direct operations are destructive. The converted API will replace the original in the API database, inheriting both API Id and database ID. + + + +## Using the API Designer + +The API Designer provides a simple interface for migrating your Tyk Classic APIs to Tyk OAS, offering both **staged** and **direct** conversions. + +1. **Back Up Your APIs:** + + First ensure that you have taken a backup of your API, in case of accidental deletion - the direct and promote operations are destructive and will permanently delete the original Tyk Classic API definition from your database. You can export the API definition using the **Actions** menu or, if you want to backup all APIs you can do so via the Tyk Dashboard API by following [these instructions](/developer-support/upgrading#backup-apis-and-policies). + +2. **Start API Migration:** + + Find the API that you want to convert in the list of **Created APIs**. You can use the **Select API type** filter to show just Tyk Classic APIs. + + Applying a filter to see just the Tyk Classic APIs + + From the **Actions** menu select **Convert to Tyk OAS**. You can also find this option in the **Actions** menu within the Tyk Classic API Designer. + + Convert an API to Tyk OAS + + This will open the **Convert to Tyk OAS** mode selector. Choose whether to **stage** the conversion or perform a **direct** migration, then select **Convert API**. + + Choosing the migration path: staged or direct + +5. **Staging the API:** + + If you selected **stage** you'll be taken to the API Designer for the newly created staged Tyk OAS API. Note the prefixes that have been applied to the API name, API ID and API base path. + + The staged Tyk OAS API with prefixes + + For the staged API, you can now validate the configuration, make any required modifications and test that behavior is as expected. + +6. **Promote the Stage API:** + + When you are ready, you can promote the staged API to complete migration by selecting **Promote staged API** from the API Designer's **Actions** menu. Note that the **promote** option is not available from the API list, only within the API Designer for the specific API. This is to protect against accidentally promoting the wrong API. + + Promoting a staged API +
+ + Confirm the promotion +
+ + + +There is a known issue in Tyk 5.8.0 that the cancel button does not work in the promotion confirmation window, so once you have selected Promote staged API you will have to hit back in your browser if you do not want to complete the migration. This will be addressed in the next patch. + + + + +7. **Final Stage:** + + Following promotion, or if you selected **direct** conversion, you'll be taken to the API Designer for the newly created Tyk OAS API. Note that this has inherited the API ID from your Tyk Classic API and has replaced the Tyk Classic in your API database. + + Migration complete! + +## Using the Migration API + +You can use the Tyk Dashboard API to convert individual APIs, multiple API, or all APIs stored in your API storage database via the dedicated [/migrate](/api-management/migrate-from-tyk-classic#tyk-dashboard-api-migrate-endpoint) endpoint. + +### Tyk Dashboard API Migrate Endpoint + +`POST /api/apis/migrate` + +The payload for this request is: + +```json +{ + "mode": "dryRun", // Required: Migration mode (dryRun, stage, promote, direct) + "apiIDs": ["api123", "api456"], // List of API IDs to migrate (cannot be used with 'all') + "all": false, // Migrate all APIs (cannot be used with 'apiIDs') + "abortOnFailure": false, // Stop migration process on first failure + "overrideStaged": false // When mode is 'stage', overwrite existing staged APIs +} +``` + +- Indicate the migration [mode] using the following options: + + - `dryRun` + - `stage` + - `promote` + - `direct` + +- You can convert specific APIs by providing their API IDs in the `apiIDs` array or convert all your Tyk Classic APIs in one go using the `all` option. + +- Set `abortOnFailure` to `true` if you want Tyk to stop processing if it encounters a failure while operating on a batch of APIs. + +- You can only have one **staged** version of a Tyk Classic API, so if you have already started the migration of an API and try again - for example after making changes to the original Tyk Classic API, the operation will fail. Use **overrideStaged** to delete the existing staged API and create a new one. + + +#### Example: Dry Run Migration + +``` +curl -X POST \ + https://your-tyk-dashboard/api/apis/migrate \ + -H "Authorization: your-dashboard-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "mode": "dryRun", + "apiIDs": ["api123", "api456"], + "abortOnFailure": false + }' +``` + +#### Example: Migrate All APIs + +``` +curl -X POST \ + https://your-tyk-dashboard/api/apis/migrate \ + -H "Authorization: your-dashboard-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "mode": "direct", + "all": true, + "abortOnFailure": false + }' +``` + +## Known Limitations of Migration + +There are some differences between the way Tyk Gateway works with Tyk Classic and Tyk OAS APIs, so you are advised to take the following into account and, if your API uses any of these features, to stage (or dry run) your migrations and ensure you perform appropriate testing on the interim versions. + +1. **Handling of Regular Expression Path Parameters** + + When migrating APIs with regex-based path parameters, be aware that: + + - In Tyk Classic, the Gateway only routes requests matching the regex pattern + - In Tyk OAS, the Gateway routes all requests matching the pattern structure by default + + Recommended action: Enable the [Validate Request](/api-management/traffic-transformation/request-validation#request-validation-using-tyk-oas) middleware in your migrated Tyk OAS API to maintain the same behavior. + +2. **Location of Mock Response Middleware** + + The position of mock response middleware in the request processing chain [differs between Tyk Classic and Tyk OAS](/api-management/traffic-transformation/mock-response#how-it-works): + + - In Tyk Classic, it appears at the start of the request processing chain (before authentication) + - In Tyk OAS, it appears at the end of the request processing chain + + During migration, the system automatically adds the [ignore authentication](/api-management/traffic-transformation/ignore-authentication#ignore-authentication-overview) middleware to endpoints with mock responses to maintain similar behavior. Note, however, that any other middleware configured for that endpoint or at the API level will be applied for the Tyk OAS API (which was not the case for the Tyk Classic API). + +3. **Enhanced Request Validation** + + Tyk OAS uses the more advanced [Validate Request](/api-management/traffic-transformation/request-validation#request-validation-using-tyk-oas) middleware, whereas Tyk Classic is limited to the [Validate JSON](/api-management/traffic-transformation/request-validation#request-validation-using-classic) middleware. The migration will configure Validate Request to check the request body (as performed by Validate JSON). + +## Recommended Migration Strategy + +For a safe migration approach, we recommend following these steps: +1. **Back Up Your APIs**: Perform a full [back-up](/developer-support/upgrading#backup-apis-and-policies) of your API Definitions - remember that the final migration is destructive, as the Tyk OAS APIs will inherit the database and API IDs of the originals +2. **Start with Dry Run**: Use the `dryRun` mode to validate the migration before making changes +3. **Stage Critical APIs**: For important APIs, use the `stage` mode to create test versions +4. **Test Thoroughly**: Verify all functionality in the staged APIs +5. **Promote Gradually**: Once testing is complete, use the `promote` mode to complete the migration of original APIs in batches +6. **Monitor Performance**: After migration, closely monitor the performance of migrated APIs diff --git a/api-management/multiple-environments.mdx b/api-management/multiple-environments.mdx new file mode 100644 index 0000000000..35a8d2abb8 --- /dev/null +++ b/api-management/multiple-environments.mdx @@ -0,0 +1,230 @@ +--- +title: "Promote APIs, Keys and Policies Between Environments" +description: "Learn how to promote APIs, keys and policies between Tyk environments, such as Development, Staging and Production" +keywords: "Multiple Environments, Environment Promotion, Move APIs, Move Keys, Move Policies, Tyk Sync, Export, Import" +sidebarTitle: "Promote Between Environments" +--- + +## Introduction + +It is possible with the Multi-Cloud and the Self-Managed version of Tyk to manage multiple environments across data centers. This can be very useful if you have QA, UAT and Production environments that are physically or geographically separate and you want to move API configurations between environments seamlessly. + +This page explains how to promote APIs, keys and policies from one environment to another. If you instead want to segment a single Tyk cluster into zones, so that specific Gateways selectively load specific APIs, see [Gateway and API Sharding](/api-management/api-sharding). + +## Move APIs Between Environments + +It is possible to move APIs between Tyk environments in the following ways: + +### In Shared Dashboard Environments + +If the environments are both Self-Managed installations and are sharing a Tyk Dashboard (and optionally an MDCB instance) then you can use API and Gateway tagging to transparently and effortlessly move an API from one environment to another. + +See [API Tagging](/api-management/api-sharding#api-tagging-with-on-premises) for more details. + +#### API Sharding + +You can also use [API Sharding](/api-management/api-sharding#what-is-api-sharding-) to move APIs in a Shards (and or MDCB) Tyk Self-Managed installation. + +### In Separate Dashboard Environments + +If the API dashboards are separate and you wish to migrate API Definitions between two completely segregated environments (e.g. migrating to new hardware or a new DC), then you can use the Export functionality of the Dashboard to download the API definition as JSON and import it into your new installation. + +#### Steps for Configuration: + +1. **Select Your API** + + From the **API Designer**, select your API: + + API designer + +2. **Export the API** + + Click **EXPORT**: + + Export button location + +3. **Save the API** + + Save and rename the JSON file: + +4. **Import into your New Environment** + + In your new environment, click **IMPORT API**: + + Select import + +5. **Generate the new API** + + Select the **From Tyk Definition** tab and paste the contents of the JSON file into the code editor and click **GENERATE API**: + + Generate API + + This will now import the API Definition into your new environment, if you have kept the API ID in the JSON document as is, the ID will remain the same. + + + + + The ID you use in with any Dashboard API integrations will change as the documents physical ID will have changed with the import. + + + +### Use Tyk-Sync + +You can also use our new Tyk-Sync tool which allows you to sync your APIs (and Policies) with a Version Control System (VCS). You can then move your APIs between environments. See [Tyk-Sync](/api-management/automations/sync) for more details. + +## Move Keys Between Environments + +Tyk currently does not have a facility to export a group of keys from one environment and reissue them in another and still be able to manage those keys from within the Dashboard. + +However, it is possible to temporarily allow access to existing keys in a new environment, but it should be noted that these keys should eventually be expired and re-generated within the new environment. + +### Moving Keys Between Environments / Creating Custom Keys + +In order to use a legacy key in a new environment, simply extract the key from the old environment using the Tyk REST APIs and then create them in the new environment using the custom key creation API. + +To create a key with a custom identifier, ie Token, simply use the [Gateway (OSS)](/tyk-gateway-api) or [Dashboard (Pro)](https://tyk.io/docs/api-reference/keys/create-custom-key) REST APIs to import a custom key. + +## Move Policies Between Environments + +Moving policies between two (Dashboard) environments is not as easy as moving API definitions and requires working with the Dashboard API to first retrieve the policies, and then modifying the document to reinsert them in your new environment: + +### Preparation + +First you must set up your new environment to respect explicit policy IDs. To do so, edit the `tyk.conf` and `tyk_analytics.conf` files in your new environment and set the `policies. allow_explicit_policy_id` setting to `true` (the setting is just `allow_explicit_policy_id` at the root level of the Dashboard configuration). In order to retain your `api_id` when moving between environments then set `enable_duplicate_slugs` to `true` in your target `tyk_analytics.conf`. + +### Steps for Configuration + +1. **Get your Policy** + + ```{.copyWrapper} + curl -X GET -H "authorization: {YOUR TOKEN}" \ + -s \ + -H "Content-Type: application/json" \ + https://admin.cloud.tyk.io/api/portal/policies/{POLICY-ID} | python -mjson.tool > policy.json + ``` + +2. **Edit the file we just created** + + The original file will look something like this, notice the two ID fields: + + ```{.json} + { + "_id": "5777ecdb0a91ff0001000003", + "access_rights": { + "xxxxx": { + "allowed_urls": [], + "api_id": "xxxxx", + "api_name": "Test", + "versions": [ + "Default" + ] + } + }, + "active": true, + "date_created": "0001-01-01T00:00:00Z", + "hmac_enabled": false, + "id": "", + "is_inactive": false, + "key_expires_in": 0, + "name": "Test Policy", + "org_id": "xxxxx", + "partitions": { + "acl": false, + "quota": false, + "rate_limit": false + }, + "per": 60, + "quota_max": -1, + "quota_renewal_rate": 60, + "rate": 1000, + "tags": [] + } + ``` + +3. **Move the id field value** + + Remove the `_id` field and put the value of the `_id` field into the `id` field, so `policy.json` should look like this: + + ```{.json} + { + "access_rights": { + "xxxxx": { + "allowed_urls": [], + "api_id": "xxxxx", + "api_name": "Test", + "versions": [ + "Default" + ] + } + }, + "active": true, + "date_created": "0001-01-01T00:00:00Z", + "hmac_enabled": false, + "id": "5777ecdb0a91ff0001000003", <------ NEW ID FIELD + "is_inactive": false, + "key_expires_in": 0, + "name": "Test Policy", + "org_id": "xxxxx", + "partitions": { + "acl": false, + "quota": false, + "rate_limit": false + }, + "per": 60, + "quota_max": -1, + "quota_renewal_rate": 60, + "rate": 1000, + "tags": [] + } + ``` + +4. **Update the policy via the API** + + Save the new `policies.json` file and then let's POST it back to the new environment: + + ```{.copyWrapper} + curl -X POST -H "authorization: {API-TOKEN}" \ + -s \ + -H "Content-Type: application/json" \ + -d @policies.json \ + https://{YOUR-NEW-ENV}/api/portal/policies | python -mjson.tool + ``` + +That's it, Tyk will now load this policy, and you will be able to manage and edit it the same way in your new environment, if you are re-creating tokens in your new environment, then those tokens' ACL does not need to be changed to a new policy ID since the legacy one will always be used as the reference for the policy. + +#### Policy IDs in the Dashboard + +After migrating a Policy from one environment to another, it is important to note that the **displayed** Policy ID is not going to match. **That is okay**. It happens because Tyk Dashboard displays the [`Mongo ObjectId`](https://docs.mongodb.com/manual/reference/glossary/#term-id), which is the `_id` field, but the `id` is the important part. + +**For example:** + +Policies in source environment +Policy ID Before + +Policies in target environment after migration +Policy ID After + +Notice that the IDs appear to be different. These are the BSON IDs and are expected to be different. But if we look for the underlying GUID `id`, you can see it's been mapped properly in the target environment. + +``` +$ curl dash-host-source/api/portal/policies/ + + .... + "_id": "5eb1b133e7644400013e54ec", + "id": "", + "name": "credit score", + +$ curl dash-host-target/api/portal/policies/ + + .... + "_id": "5f03be2ce043fe000177b047", + "id": "5eb1b133e7644400013e54ec", + "name": "credit score", +``` + +As you can see, under the hood, the policy has been migrated correctly with target Tyk Dashboard saving the proper ID inside `id`. That is the value that will be referred to inside Key Creation, etc. + +### Use Tyk-Sync + +You can also use our new Tyk-Sync tool which allows you to sync your Policies (and APIs) with a Version Control System (VCS). You can then move your Policies between environments. See [Tyk-Sync](/api-management/automations/sync) for more details. + diff --git a/api-management/observability-getting-started.mdx b/api-management/observability-getting-started.mdx new file mode 100644 index 0000000000..94b5b7d717 --- /dev/null +++ b/api-management/observability-getting-started.mdx @@ -0,0 +1,385 @@ +--- +title: "Get Started with Tyk Observability" +description: "Add logs, distributed traces, and metrics to a Tyk Gateway deployment using Docker Compose with an OpenTelemetry Collector, Prometheus, Loki, Tempo, and Grafana." +sidebarTitle: "Getting Started" +keywords: ["opentelemetry", "OTLP", "logs", "metrics", "traces", "observability", "getting started", "docker compose", "grafana", "prometheus", "loki", "tempo"] +--- + +## Introduction + +Tyk Gateway exports all three observability signals (structured logs, distributed traces, and metrics) via the OpenTelemetry Protocol (OTLP). This guide shows you how to enable each signal and route them to a local Grafana stack (Prometheus, Loki, Tempo) using an OpenTelemetry Collector. + +By the end of this guide you will have: + +- Structured JSON access logs from Tyk Gateway shipping to [Loki](https://grafana.com/oss/loki/) +- Distributed traces exporting to [Tempo](https://grafana.com/oss/tempo/) +- Gateway metrics (request rate, latency, error rate) flowing to [Prometheus](https://grafana.com/oss/prometheus/) +- A Grafana instance wired up to all three backends + +## Availability + +| Signal | Minimum Gateway version | +|--------|------------------------| +| Structured JSON logs (`log_format: json`) | 5.6.0 | +| Access logs (`access_logs.enabled`) | 5.8.0 | +| Distributed traces (OTLP) | 5.3.0 | +| OTLP metrics | 5.13.0 | + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed +- A running Tyk deployment. We recommend following the [Tyk getting started guide](https://tyk.io/docs/getting-started/quick-start#getting-started-with-tyk-self-managed) to set up a local Docker environment +- Basic familiarity with Docker Compose + +## Architecture + +All three signals flow from Tyk Gateway to a single OpenTelemetry Collector endpoint. The Collector fans them out to the appropriate backend. Grafana queries all three. + +```mermaid +flowchart LR + TGW[Tyk Gateway] + COL[OTel Collector] + PRO[Prometheus] + LOK[Loki] + TMP[Tempo] + GRA[Grafana] + + TGW -->|"OTLP gRPC :4317\n(traces + metrics)"| COL + TGW -->|"container logs\n(filelog receiver)"| COL + COL -->|metrics| PRO + COL -->|logs| LOK + COL -->|traces| TMP + PRO --> GRA + LOK --> GRA + TMP --> GRA +``` + + +Tyk Gateway does not export logs via OTLP. It writes structured JSON to `stderr`. The OTel Collector reads those logs from the Docker container log path using the `filelog` receiver. + + +## Instructions + +### Step 1: Configure Tyk Gateway + +Add the following environment variables to your `tyk-gateway` service. These enable all three signals: + +```yaml expandable +services: + tyk-gateway: + environment: + # Structured JSON logs (requires v5.6.0+) + - TYK_GW_LOGFORMAT=json + # Per-request access logs with trace ID correlation (requires v5.8.0+) + - TYK_GW_ACCESSLOGS_ENABLED=true + + # Distributed tracing + - TYK_GW_OPENTELEMETRY_TRACES_ENABLED=true + - TYK_GW_OPENTELEMETRY_TRACES_ENDPOINT=otel-collector:4317 + - TYK_GW_OPENTELEMETRY_TRACES_SAMPLING_TYPE=TraceIDRatioBased + - TYK_GW_OPENTELEMETRY_TRACES_SAMPLING_RATE=1.0 + + # OTLP metrics + - TYK_GW_OPENTELEMETRY_METRICS_ENABLED=true + - TYK_GW_OPENTELEMETRY_METRICS_ENDPOINT=otel-collector:4317 + - TYK_GW_OPENTELEMETRY_METRICS_EXPORTINTERVAL=15 +``` + +If you prefer `tyk.conf`, the equivalent configuration is: + +```json expandable +{ + "log_format": "json", + "access_logs": { + "enabled": true + }, + "opentelemetry": { + "traces": { + "enabled": true, + "endpoint": "otel-collector:4317", + "sampling": { + "type": "TraceIDRatioBased", + "rate": 1.0 + } + }, + "metrics": { + "enabled": true, + "endpoint": "otel-collector:4317", + "export_interval": 15 + } + } +} +``` + + +`sampling.rate: 1.0` captures every request, which is suitable for getting started. In production, lower this to `0.1` (10%) or use `ParentBased` sampling. See the [Tyk Gateway configuration reference](/tyk-oss-gateway/configuration#opentelemetry-sampling) for all options. + + +### Step 2: Add the OTel Collector + +Create an `otelcol-config.yml` file in your deployment directory: + +```yaml expandable +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + + filelog/tyk-gateway: + include: ["/var/lib/docker/containers/*/*-json.log"] + include_file_name: false + include_file_path: true + start_at: end + operators: + - type: json_parser + parse_from: body + parse_to: attributes.docker + on_error: drop + - type: move + from: attributes.docker.log + to: body + - type: json_parser + parse_from: body + parse_to: attributes.tyk + on_error: drop + - type: filter + expr: 'attributes.tyk.prefix == nil and attributes.tyk.msg == nil' + - type: time_parser + parse_from: attributes.tyk.time + layout: '%Y-%m-%dT%H:%M:%SZ' + on_error: send_quiet + - type: severity_parser + parse_from: attributes.tyk.level + on_error: send_quiet + +processors: + batch: {} + resource/tyk_gateway_logs: + attributes: + - action: upsert + key: service.name + value: tyk-gateway + memory_limiter: + check_interval: 1s + limit_percentage: 75 + spike_limit_percentage: 20 + transform/tyk_gw_resource_attrs: + error_mode: ignore + metric_statements: + - context: datapoint + statements: + - set(attributes["tyk_gw_id"], resource.attributes["tyk.gw.id"]) where resource.attributes["tyk.gw.id"] != nil + - set(attributes["tyk_gw_group_id"], resource.attributes["tyk.gw.group.id"]) where resource.attributes["tyk.gw.group.id"] != nil + - set(attributes["tyk_gw_tags"], resource.attributes["tyk.gw.tags"]) where resource.attributes["tyk.gw.tags"] != nil + +exporters: + otlphttp/prometheus: + endpoint: "http://prometheus:9090/api/v1/otlp" + tls: + insecure: true + otlphttp/loki: + endpoint: "http://loki:3100/otlp" + tls: + insecure: true + otlp/tempo: + endpoint: "tempo:4317" + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [otlp/tempo] + metrics: + receivers: [otlp] + processors: [memory_limiter, transform/tyk_gw_resource_attrs, batch] + exporters: [otlphttp/prometheus] + logs: + receivers: [filelog/tyk-gateway] + processors: [memory_limiter, resource/tyk_gateway_logs, batch] + exporters: [otlphttp/loki] +``` + +Then add the `otel-collector` service to your `docker-compose.yml`: + +```yaml expandable +services: + otel-collector: + image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.133.0 + user: "0" # Required — without this, permission denied reading /var/lib/docker/containers + volumes: + - ./otelcol-config.yml:/etc/otelcol-contrib/config.yaml + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - "4317:4317" + - "4318:4318" + networks: + - tyk +``` + +The volume mounts give the Collector read access to Docker container logs for the `filelog` receiver. + +### Step 3: Add the Grafana stack + +Add Prometheus, Loki, Tempo, and Grafana to your `docker-compose.yml`: + +```yaml expandable +services: + prometheus: + image: prom/prometheus:v3.4.0 + command: + - --config.file=/etc/prometheus/prometheus.yml + - --web.enable-otlp-receiver + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + networks: + - tyk + + loki: + image: grafana/loki:3.5.0 + command: -config.file=/etc/loki/local-config.yaml + ports: + - "3100:3100" + networks: + - tyk + + tempo: + image: grafana/tempo:2.7.2 + command: -config.file=/etc/tempo.yaml + volumes: + - ./tempo.yaml:/etc/tempo.yaml + ports: + - "3200:3200" + - "4317" + networks: + - tyk + + grafana: + image: grafana/grafana:12.0.0 + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + volumes: + - ./grafana/provisioning:/etc/grafana/provisioning + ports: + - "3001:3000" + networks: + - tyk + depends_on: + - prometheus + - loki + - tempo +``` + + +Grafana is mapped to port `3001` to avoid conflicting with Tyk Dashboard on port `3000`. Adjust if needed. + + +Create a minimal `prometheus.yml` to allow OTLP ingest: + +```yaml +global: + scrape_interval: 15s + +storage: + tsdb: + out_of_order_time_window: 10m +``` + +Create a minimal `tempo.yaml`: + +```yaml expandable +server: + http_listen_port: 3200 + +distributor: + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + +storage: + trace: + backend: local + local: + path: /tmp/tempo/blocks +``` + +#### Provision Grafana datasources + +Create `grafana/provisioning/datasources/tyk.yaml`: + +```yaml expandable +apiVersion: 1 +datasources: + - name: Prometheus + type: prometheus + url: http://prometheus:9090 + isDefault: true + + - name: Loki + type: loki + url: http://loki:3100 + + - name: Tempo + type: tempo + url: http://tempo:3200 +``` + +### Step 4: Start and verify + +Restart your deployment to apply the configuration changes to your Gateway and bring up the new services: + +```bash +docker compose up -d +``` + +Send test requests through Tyk Gateway, then open Grafana at **http://localhost:3001** and verify each signal. + + +If you are following this guide using the Tyk Self-Managed getting started setup, an **httpbingo** API is pre-configured. Generate test traffic with: + +```bash +for i in $(seq 1 10); do curl -s -o /dev/null -w "HTTP %{http_code}\n" \ + -H "Authorization: " http://localhost:8080/httpbingo/get; done +``` + + +**Metrics**: in Explore, select the Prometheus datasource and run: +```promql +{__name__="tyk.http.requests_total"} +``` +You should see request counts rise as traffic flows through the Gateway. + +![Metrics in Grafana](/img/observability/grafana-metrics.png) + +**Logs**: in Explore, select the Loki datasource and run: +```logql +{service_name="tyk-gateway"} | json +``` +You should see structured access log entries with fields like `api_id`, `path`, `status`, `latency_total`, and `trace_id`. + +![Logs in Grafana](/img/observability/grafana-logs.png) + +**Traces**: in Explore, select the Tempo datasource and search for recent traces. Each trace should show one span for the request. + +![Traces in Grafana](/img/observability/grafana-traces.png) + + +If traces are not appearing, verify that `TYK_GW_OPENTELEMETRY_TRACES_ENABLED=true` is set and that the Gateway can reach `otel-collector:4317`. Check the OTel Collector logs with `docker compose logs otel-collector` for any connection errors. + + +## Next steps + +- **Explore default metrics**: see all Gateway metrics available out of the box in [Default Metrics](/api-management/logs-metrics) +- **Add custom metrics**: attach request headers, JWT claims, and response codes as metric dimensions using [Custom Metrics](/api-management/metrics/custom-metrics) +- **Kubernetes deployment**: for log collection in Kubernetes using the OTel Collector DaemonSet, see [Collecting Gateway Logs with OTel on Kubernetes](/api-management/collecting-gateway-logs-otel-kubernetes) diff --git a/api-management/observability.mdx b/api-management/observability.mdx new file mode 100644 index 0000000000..28237a1fe8 --- /dev/null +++ b/api-management/observability.mdx @@ -0,0 +1,121 @@ +--- +title: "OpenTelemetry in Tyk Gateway: Tracing, Metrics, and Logs" +description: "Tyk's native OpenTelemetry support for distributed tracing, metrics export, and log collection. The recommended observability approach for Tyk Gateway." +keywords: "OpenTelemetry, OTel, OTLP, Distributed Tracing, Metrics, Observability, Tyk Gateway, Prometheus, Datadog, Jaeger" +sidebarTitle: "Overview" +--- + +[OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/) is an open-source observability framework providing a vendor-neutral standard for collecting traces, metrics, and logs. Since Tyk Gateway v5.2, it is the recommended observability approach for Tyk Gateway. + +Tyk supports all three signals. Traces and metrics are exported natively via the [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/) to any compatible backend. Logs are written as structured JSON to stderr and collected by an external agent. No additional components are required alongside Tyk to get started. + +Tyk Gateway exporting traces and metrics via OTLP to tracing and metrics backends, and logs via stdout to an OTel Collector and log backend + +## Tracing + +Distributed tracing gives you an end-to-end view of individual API requests as they flow through Tyk Gateway and into your upstream services. Traces help you pinpoint latency bottlenecks, trace error propagation across services, and understand the full request lifecycle. + +Tyk generates parent and child [spans](https://opentelemetry.io/docs/concepts/signals/traces/#spans) for each proxied request. For each API, you can optionally enable **detailed tracing** to produce a span per middleware, giving granular visibility into authentication, rate limiting, transformation, and routing steps. + +**Supported propagation formats:** W3C TraceContext (default), B3, and custom or composite header modes for proprietary correlation headers. + +**Supported backends:** Datadog, Dynatrace, Jaeger, New Relic, Elastic, and any OTLP-compatible endpoint. + +For configuration details and vendor-specific integration guides, see [Distributed Tracing with Tyk](/api-management/traces). + +## Metrics + +Tyk Gateway natively pushes metrics to any OTLP-compatible backend with a configurable push interval. No sidecar or agent is required alongside the Gateway. + +When enabled, the Gateway automatically exports three groups of metrics: + +- **Request metrics:** Rate, Errors, and Duration (RED) with a three-way latency split: total, Gateway-only, and upstream-only +- **Go runtime metrics:** Memory usage, goroutine count, and GC health +- **Configuration state metrics:** Number of APIs and policies loaded, config reload counts and durations + +You can extend these with [custom counters and histograms](/api-management/metrics/custom-metrics) that use request context, JWT claims, session data, response headers, or static API metadata as dimensions. + +**Compatible backends:** Prometheus, Grafana Mimir, Datadog, New Relic, Dynatrace, Elastic, and any OTel Collector. + +For full configuration details, see: + +- [OpenTelemetry Metrics Configuration](/api-management/logs-metrics): enabling metrics, export config, and cardinality control +- [Default Gateway Metrics](/api-management/metrics/default-metrics): all automatically exported metrics and their dimensions +- [Custom Metrics](/api-management/metrics/custom-metrics): defining your own counters and histograms + +## Logs + +Tyk Gateway writes logs to `stdout`/`stderr` in structured JSON format. It does not have a native OTLP log exporter. To ship gateway logs to an observability backend, deploy the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) with the [Filelog Receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver). The Collector tails container log files on each Kubernetes node, optionally enriches them with pod and namespace metadata, and forwards them to a backend like Elasticsearch. + +For a step-by-step guide, see [Collecting Gateway Logs with OTel on Kubernetes](/api-management/collecting-gateway-logs-otel-kubernetes). + +## Dashboard Analytics + +Separately from OpenTelemetry, Tyk Dashboard has its own built-in Traffic Analytics UI and Log Browser, populated by [Tyk Pump](/api-management/tyk-pump) rather than OTel. This is not an alternative to OTel for external observability tooling, it's the only route into Tyk Dashboard's native analytics. See [Dashboard Analytics](/api-management/dashboard-analytics). + +## Resource Attributes + +All OTel signals produced by Tyk Gateway include resource attributes: metadata set once at startup that identifies the source instance. Use these to filter and correlate signals across nodes, edge groups, and environments. + +| Attribute | Always Present | Description | +|:----------|:--------------|:------------| +| `tyk.gw.id` / `service.instance.id` | Yes | Unique ID for this Gateway instance. | +| `tyk.gw.dataplane` | Yes | Whether the Gateway is running in a distributed Data Plane. | +| `tyk.gw.group.id` | No | Data Plane group ID. Populated only for Gateways in distributed Data Planes. | +| `tyk.gw.tags` | No | Segment tags. Populated only when the Gateway is [segmented](/api-management/api-sharding#what-is-api-sharding-). | + +Standard OTel attributes (`service.name`, `service.version`, `host.name`, `host.arch`, `host.ip`, `process.pid`) are also included automatically. + + +All OpenTelemetry configuration options are documented in the [Tyk Gateway configuration reference](/tyk-oss-gateway/configuration#opentelemetry). + + +## Signal Correlation + +Tyk's three signal types (traces, metrics, and logs) are produced independently, but three mechanisms let you correlate them in your observability backend. + +### Trace and Span IDs in Logs + +When OpenTelemetry tracing is enabled, Tyk Gateway injects trace context into both log types: + +[**Access logs**](/api-management/logs/access-logs) include `trace_id`, the W3C trace ID for the request, matching the root span exported to your tracing backend. + +``` +... status=200 trace_id=4bf92f3577b34da6a3ce929d0e0e4736 upstream_latency=61 ... +``` + +[**Application logs**](/api-management/logs/application-logs) include both `trace_id` and `span_id` on all request-scoped entries (middleware execution, errors, debug output): + +``` +... level=error msg="Rate limit exceeded" prefix=rate-limit api_id=b1a41c9a89984ffd7bb7d4e3c6844ded trace_id=4bf92f3577b34da6a3ce929d0e0e4736 span_id=00f067aa0ba902b7 +``` + +The `span_id` identifies the exact span active when the log was emitted, so you can navigate from an error log directly to the span in a trace waterfall. + +For the full log field reference, see [Logging in Tyk](/api-management/logs). + + +Trace and span IDs are only present in log entries associated with a sampled request. Non-request-scoped entries (startup, configuration reload, health-checks) and unsampled requests do not carry these fields. + + +### Exemplars in Histogram Metrics + +When both OpenTelemetry tracing and metrics are enabled, Tyk Gateway automatically attaches exemplars whenever a histogram is recorded during an active sampled request. + +An exemplar embeds the `trace_id` and `span_id` of the active request directly inside a histogram bucket, creating a direct link from an aggregated metric to a specific trace. + +**What this enables:** When you see a latency spike on a `http.server.request.duration` histogram in Grafana, click the exemplar marker on the chart to navigate directly to the offending trace in Jaeger or Tempo, with no manual trace ID search required. + +For full setup details, see [Exemplars](/api-management/metrics/default-metrics#exemplars) in the default metrics reference. + +### Resource Attributes Across All Signals + +Traces and metrics both carry the same [resource attributes](#resource-attributes) set at Gateway startup. The key correlating attribute is `tyk.gw.id` (also exported as `service.instance.id`), which uniquely identifies the Gateway that produced each signal. + +When running multiple Gateway replicas, this lets you filter metrics to a specific instance using `tyk.gw.id` and find that same Gateway's traces using the same attribute. + +## What's Next + +- **Getting Started:** A hands-on walkthrough spinning up a full observability stack (Loki, Grafana, Tempo, Prometheus) with Tyk Gateway. *(Coming soon)* +- **[Best Practices](/api-management/observability/guides/best-practices):** Production guidance on export topology, cardinality control, trace sampling, and log collection. +- **[Gateway Observability Playbook](/api-management/observability/guides/gateway-playbook):** Diagnosing common failures using RED metrics, response flags, and PromQL alert rules. diff --git a/api-management/observability/guides/best-practices.mdx b/api-management/observability/guides/best-practices.mdx new file mode 100644 index 0000000000..d523fddd16 --- /dev/null +++ b/api-management/observability/guides/best-practices.mdx @@ -0,0 +1,182 @@ +--- +title: "Tyk Gateway Observability Best Practices" +description: "Production-ready guidance for configuring Tyk Gateway observability: OTLP export topology, metrics cardinality control, trace sampling strategy, and log collection." +keywords: "OpenTelemetry, OTel, Observability, Best Practices, Cardinality, Sampling, Metrics, Traces, Logs, OTLP, Prometheus, Grafana, Jaeger, Performance" +sidebarTitle: "Observability Best Practices" +--- + +Tyk Gateway exports **metrics and traces** via the OpenTelemetry Protocol (OTLP) and writes **logs** to stderr for external collection. + +This page covers production-ready configuration for each signal: which export topology to use, how to control metrics cardinality, how to tune trace sampling, and how to correlate logs with traces. + +## Exporting Data via OTLP + +Use OTLP as the export protocol. It is vendor-neutral and supported by every modern observability backend. The Gateway supports both gRPC (default, more efficient for high throughput) and HTTP transports. Choose gRPC unless your network or backend requires HTTP. + +### Export Topology + +**Direct to backend**: simpler setup, works well for managed cloud backends (Datadog, Dynatrace, New Relic, Elastic Cloud). Suitable for lower traffic volumes where buffering and retry are handled by the backend. + +**Via OTel Collector**: recommended for production. Decouples Tyk from the backend, adds buffering and retry, enables tail-based sampling (see [Trace Sampling](#traces-sampling-strategy)), and fans out to multiple backends simultaneously. + +### Open Source Backends + +| Backend | Best for | Notes | +|---------|----------|-------| +| **Grafana LGTM** (Loki, Grafana, Tempo, Mimir) | All signals | Tempo for traces, Mimir/Prometheus for metrics, Loki for logs | +| **Jaeger** | Tracing only | Accepts OTLP natively since v1.35; simple to self-host | +| **Prometheus + Grafana** | Metrics only | Pull model; use OTel Collector's Prometheus exporter or `remote_write` | +| **ELK / OpenSearch** | Logs + APM | Elastic APM accepts OTLP; Logstash can ingest OTel Collector output | + +For vendor-specific configuration (Datadog, Dynatrace, New Relic, Elastic, Jaeger), see the [Traces](/api-management/traces) configuration guide. + +## Metrics: Cardinality and Performance + +### What Is the Cardinality Problem? + +Each unique combination of dimension values for a metric creates a separate time series. Unbounded dimensions, such as one label per user, per IP address, or per request ID, cause exponential growth in series count. This consumes memory in Tyk Gateway, increases storage costs in your backend, and slows query performance. + +### Tyk's Built-In Cardinality Limit + +Tyk ordinarily caps each metric instrument at **2,000 unique label-value combinations**. A different limit can be set using the [cardinality control](/api-management/logs-metrics#cardinality-control). + +If the configured limit is exceeded, the additional combinations are aggregated into an overflow bucket marked with the attribute `otel.metric.overflow=true`. Aggregate counts are preserved in the overflow bucket, but you lose the ability to break down data by the overflowing dimension combination. + +Alert on overflow to catch cardinality issues early. The following example uses PromQL: + +```promql +increase({otel_metric_overflow="true"}[5m]) > 0 +``` + +If you are hitting the cardinality cap then the limit can be raised, but the recommended action is to reduce cardinality in your [custom metrics configuration](/api-management/metrics/custom-metrics). + +### Custom Metrics: Stay at 10 Dimensions or Fewer + +The OTel SDK processes metrics on a fast path when an instrument has 10 or fewer dimensions. Exceeding this threshold increases memory allocations and slows metric recording. Keep each custom metric instrument to **10 or fewer dimensions**. + +### Dimension Safety Guide + +When defining [custom metrics](/api-management/metrics/custom-metrics), choose dimension sources based on their cardinality characteristics: + +| Cardinality | Sources | Examples | +|-------------|---------|---------| +| **Safe** | `metadata`, `config_data` bounded fields | `listen_path`, `endpoint`, `method`, `http.response.status_code` | +| **Caution** | `session` fields, bounded JWT claims | `api_key`, `oauth_id`, `alias` (bounded per tenant but can be large) | +| **Avoid** | `header` / `context` with unbounded values | `ip_address`, `user_id` JWT claim, `request_id`, raw `path` | +| **Never** | Token or bearer values | Unique per request; exhausts the cardinality limit immediately | + +## Traces: Sampling Strategy + +Trace data is the most expensive observability signal to collect, store, and query. Sampling controls what fraction of traces you keep. + +### Head-Based Sampling + +With *head-based* sampling, the decision whether to create a trace for a request is made before any span data exists. In other words, the sampling logic is applied in the Gateway. This is controlled using the `TraceIDRatioBased` approach as described in the [trace sampling](/api-management/traces#sampling) section. + +```json +{ + "opentelemetry": { + "traces": { + "sampling": { + "type": "TraceIDRatioBased", + "rate": 0.1 + } + } + } +} +``` + +**Characteristics:** +- Fast: no buffering required, predictable overhead +- Limitation: cannot guarantee capture of all errors or slow outliers at low sample rates +- **Recommended default: 10% (`rate: 0.1`)** for most production deployments + +### Tail-Based Sampling + +With *tail-based* sampling, the sampling decision is made in the OTel Collector after the full trace has been collected. The OTel Collector buffers spans in memory, then applies policies to decide what to keep. This allows you to guarantee 100% retention of error traces and slow requests regardless of the overall sample rate. + +To use tail-based sampling, send all traces from Tyk to the Collector using the `AlwaysOn` approach as described in the [trace sampling](/api-management/traces#sampling) section, then apply policies in the Collector. This is the default setting. + +```yaml +# OTel Collector processors section +processors: + tail_sampling: + decision_wait: 10s + policies: + - name: keep-errors + type: status_code + status_code: {status_codes: [ERROR]} + - name: keep-slow + type: latency + latency: {threshold_ms: 1000} + - name: sample-rest + type: probabilistic + probabilistic: {sampling_percentage: 5} +``` + +### Sampling Trade-Offs + +| Strategy | Cost | Accuracy | Best for | +|----------|------|----------|---------| +| `AlwaysOn` (100%) | High | Perfect | Dev/staging, low-traffic APIs | +| `TraceIDRatioBased`, `rate: 0.1` (10%) | Low | Good average | Most production deployments | +| `TraceIDRatioBased`, `rate: 0.01` (1%) | Very low | Poor for rare events | Very high traffic, budget-constrained | +| Tail-based via OTel Collector | Medium (Collector infra) | Best | When you need all errors and outliers | + +## Logs: Collection and Correlation + +Tyk Gateway writes logs to stderr, not via OTLP. They must be collected by an external agent. + +### Enable Access Logs + +Access logs record one line per request: HTTP method, path, upstream latency, status code, response size, and client IP. They are the fastest way to observe Gateway traffic without a full tracing setup and are essential for log-based alerting such as 5xx spike detection. + +Access logs are enabled in the Gateway configuration or the equivalent [environment variable](/tyk-oss-gateway/configuration#access_logs-enabled): + +```json +{ + "access_logs": { + "enabled": true + } +} +``` + +See [Access Logs](/api-management/logs/access-logs) for the full list of configurable fields and custom template options. + +### Use JSON Log Format + +JSON format has lower parsing overhead than the default text format and is directly indexable by log backends (Loki, Elasticsearch, CloudWatch). Enable this mode in the Gateway configuration or the equivalent [environment variable](/tyk-oss-gateway/configuration#log_format): + +```json +{ + "log_format": "json" +} +``` + +JSON log format is recommended for all production deployments. + +### Trace Correlation + +When OpenTelemetry is enabled, Tyk injects `trace_id` and `span_id` into request-scoped log entries. This lets you pivot from a log line directly to the corresponding trace in Tempo or Jaeger. + + +`trace_id` and `span_id` are only present on **sampled requests**. At 10% head-based sampling, 90% of log lines will not carry trace IDs. + + +### Log Collection Options + +| Stack | Recommended Approach | +|-------|---------------------| +| Grafana LGTM | Promtail or Grafana Alloy → Loki | +| ELK / OpenSearch | Filebeat or Logstash | +| Kubernetes | OTel Collector with `filelog` receiver; see [Collecting Gateway Logs with OTel on Kubernetes](/api-management/collecting-gateway-logs-otel-kubernetes) | +| Cloud (AWS/GCP/Azure) | CloudWatch agent, GCP Logging agent, or Azure Monitor agent | + +## Performance Impact Summary + +| Signal | Baseline Cost | Main Risk | Mitigation | +|--------|--------------|-----------|------------| +| **Metrics** | Low (batched OTLP export) | Cardinality explosion in custom metrics | ≤10 dimensions per instrument; monitor `otel.metric.overflow` | +| **Traces** | Medium (per-request spans) | High sampling rate at scale | Head-based 10% default; tail-based for error capture | +| **Logs** | Low (stdout write) | Log volume at debug verbosity | Use `info` level in production; enable JSON format | +| **Analytics (Tyk Pump)** | Medium–High (Redis writes) | ~13% RPS reduction when tracking all requests | Use [Do-Not-Track](/api-management/traffic-transformation/do-not-track) middleware for non-critical endpoints | diff --git a/api-management/observability/guides/gateway-playbook.mdx b/api-management/observability/guides/gateway-playbook.mdx new file mode 100644 index 0000000000..fcc3bac6c7 --- /dev/null +++ b/api-management/observability/guides/gateway-playbook.mdx @@ -0,0 +1,574 @@ +--- +title: "Tyk Gateway Observability Playbook" +description: "Use native OTLP metrics to monitor Tyk Gateway RED signals, diagnose common failures, and configure Prometheus alerting rules." +keywords: "opentelemetry, metrics, prometheus, alerting, latency, error rate, goroutines, observability, RED metrics, response flags" +sidebarTitle: "Gateway Observability Playbook" +--- + +## Overview + +Tyk Gateway 5.13+ exports metrics natively via OTLP, removing the need for Tyk Pump as an intermediary for Prometheus-based observability: + +Tyk Gateway pushing OTLP metrics to an OTel Collector, which exports to any OTLP backend and exposes a Prometheus scrape endpoint + +Every request the Gateway handles generates three signal types that share common identifiers, enabling end-to-end correlation: + +| Identifier | In logs | In metrics (Prometheus label) | In traces | +|-----------|---------|-------------------------------|-----------| +| API ID | `api_id` | `tyk_api_id` | `tyk.api.id` attribute | +| Response flag | `response_flag` | `tyk_response_flag` | — | +| Consumer key | `api_key` | Available via custom `api_metrics` config | `tyk.api.apikey` | +| Trace ID | `trace_id` | — (use exemplars) | span `traceId` | +| Gateway instance | — | `service_name` | `service.instance.id` | + +This guide covers two categories of metrics: + +- **RED metrics**: What your APIs are doing right now: request rate, error classification, and latency decomposition. +- **Gateway health metrics**: What the Gateway process itself is doing: memory, goroutines, and configuration state. + +## Prerequisites + +- Tyk Gateway 5.13 or later +- OpenTelemetry metrics enabled (`opentelemetry.metrics.enabled: true` in `tyk.conf`, or `TYK_GW_OPENTELEMETRY_METRICS_ENABLED=true`) +- An OTel Collector configured to export to Prometheus (or a compatible OTLP backend) +- Prometheus scraping the Collector's metrics endpoint + +If you haven't set up the OTel pipeline yet, see [OpenTelemetry tracing and metrics](/api-management/traces). + +## Key thresholds at a glance + + +Use these as starting points and adjust based on your API's traffic profile and SLOs. + +| Signal | Healthy threshold | Alert threshold | +|--------|------------------|-----------------| +| p95 end-to-end latency | < 500ms | > 1s for 5 min | +| p99 end-to-end latency | < 1s | > 2s for 5 min | +| Error rate (non-2xx) | < 2% | > 10% for 5 min | +| Gateway-only avg latency | < 10ms | > 50ms | +| Goroutine count | < 2,000 | > 5,000 for 10 min | +| Auth failure rate | < 0.01/s baseline | > 0.1/s for 2 min | +| Upstream error rate (URS) | 0 | Any sustained for 3 min | + + +--- + +## Core RED Metrics + +### Request Rate + +Request rate tells you how much traffic the Gateway is handling. Sudden drops indicate that requests are not reaching the Gateway: DNS failures, network partitions between clients and the gateway, or client-side misconfiguration. + +**Metrics:** + +| Metric name (OTel) | Prometheus name | Type | Key dimensions | +|-------------------|-----------------|------|----------------| +| `tyk.api.requests.total` | `tyk_api_requests_total` | counter | `http_request_method`, `http_response_status_code`, `tyk_api_id` | + +**What are normal request rate thresholds?** + +There is no universal baseline. Request rate varies by deployment. Establish a baseline over 7 days and alert on deviations greater than ±30% from the rolling average for the same hour of the prior week. + +A more actionable check is to watch for a sudden drop to near-zero on a previously active API: + +```promql +# Alert if rate drops > 80% compared to 1 hour ago +rate(tyk_api_requests_total{tyk_api_id=""}[5m]) + < 0.2 * rate(tyk_api_requests_total{tyk_api_id=""}[5m] offset 1h) +``` + +**Troubleshooting unexpected changes in request rate:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| Rate drops to zero on one API, others normal | API definition deleted or disabled; DNS for that API's listen path changed | Check Gateway logs for `api_id` config errors; verify API is active in Tyk Dashboard | +| Rate drops uniformly across all APIs | Network partition between clients and Gateway; load balancer health check failure; OTel Collector not receiving (metrics gap, not a real traffic drop) | Check Gateway health endpoint; confirm traffic is actually dropping and not just metric export failing | +| Rate spikes suddenly on one API | Traffic surge, DDoS, or runaway batch client | Filter logs by `api_id` and group by `api_key` to identify the calling consumer | +| Rate split unevenly across Gateway instances | Sticky sessions or uneven load balancer weights | Check `service.instance.id` label in metrics to compare per-instance rates | + +--- + +### Error Rate + +Error rate is the primary SLI for API availability. Tyk classifies every non-success response with a `response_flag`: a two- or three-character code that tells you exactly where and why a request failed, before you read a single log line. + +**Metrics:** + +| Metric name (OTel) | Prometheus name | Type | Key dimensions | +|-------------------|-----------------|------|----------------| +| `http.server.request.duration` | `http_server_request_duration_seconds` | histogram | `http_request_method`, `http_response_status_code`, `tyk_api_id`, `tyk_response_flag` | +| `tyk.api.requests.total` | `tyk_api_requests_total` | counter | `http_request_method`, `http_response_status_code`, `tyk_api_id` | + +**Response flag reference** (full list: [Error classification](/api-management/logs/access-logs#error-classification)): + +| Flag | HTTP Status | Upstream called? | `error_source` | Meaning | +|------|------------|-----------------|----------------|---------| +| *(HTTP status code, e.g. `200`)* | 200 | **Yes** | *(absent)* | Successful request — `tyk_response_flag` is set to the HTTP status code | +| `AMF` | 401 | **No** | `AuthKey` | Auth header entirely absent | +| `AKI` | 403 | **No** | `AuthKey` | Auth header present, key invalid or expired | +| `QEX` | 403 | **No** | `RateLimitAndQuotaCheck` | Key's quota window exhausted | +| `RLT` | 429 | **No** | `RateLimitAndQuotaCheck` | Per-second rate limit exceeded | +| `URS` | 500 | **Yes** | `Upstream` | Upstream returned a 5xx error | + +**What error rate thresholds should I set?** + +A non-2xx rate below **2%** is a healthy baseline for most APIs with authenticated consumers. Alert at **10%** over a 5-minute window for most APIs. For payment or health endpoints, tighten to 1%. + +Calculate the current error rate: + +```promql +( + rate(tyk_api_requests_total{http_response_status_code!~"2..", tyk_api_id=""}[5m]) + / + rate(tyk_api_requests_total{tyk_api_id=""}[5m]) +) * 100 +``` + +Classify errors by flag to route to the right runbook: + +```promql +sum by (tyk_response_flag) ( + rate(http_server_request_duration_seconds_count{tyk_api_id=""}[5m]) +) +``` + + +Check `upstream_latency` in logs first. If it is `0`, the request never left the Gateway: the error originated in auth, quota, or rate-limit middleware. If `upstream_latency > 0` and `response_flag = URS`, the upstream itself failed. + + +**Troubleshooting elevated error rates:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| Surge in `AMF` (401) | Auth header entirely absent at Tyk — the client never sends it, or an intermediary (load balancer, CDN, reverse proxy) strips it before reaching the Gateway | Identify source: `{prefix="access-log"} \| json \| response_flag="AMF" \| line_format "ip={{.client_ip}} api={{.api_name}}"`. Single IP after a network change → suspect header stripping by an intermediary. Wide IP spread → clients calling the wrong listen path | +| Surge in `AKI` (403) | Key was rotated, expired, or revoked; credential stuffing attack | Single source IP → contact consumer. Wide IP spread → credential attack; tighten rate limits | +| Sustained `QEX` (403) | Consumer legitimately exhausted their quota tier | Identify consumer via `key` field in logs; invite upgrade or raise quota ceiling in policy | +| `RLT` (429) climbing | Legitimate traffic burst; retry storm after upstream error | Check whether RLT is protecting a degraded upstream (correct) or blocking legitimate traffic (adjust limit). Add backoff guidance to consumer | +| Sustained `URS` (500) | Backend service degraded; upstream 5xx errors | Extract `error_target` (upstream hostname) and `upstream_addr` from logs; escalate to backend team. Check retry/circuit-breaker plugin config | +| Non-2xx with no `response_flag` | Auth plugin returning custom status; unhandled middleware error | Search Gateway error logs for the request's `trace_id` | + +--- + +### Latency + +Tyk exports three latency histograms that let you decompose end-to-end response time into what the Gateway spent versus what the upstream spent. Use them together. Any one alone gives an incomplete picture. + +**Metrics:** + +| Prometheus name | Measures | Key labels | +|----------------|---------|------------| +| `http_server_request_duration_seconds` | Total time from first byte received to last byte sent (client view) | `http_request_method`, `http_response_status_code`, `tyk_api_id`, `tyk_response_flag` | +| `tyk_gateway_request_duration_seconds` | Gateway-only processing time (middleware chain, auth, rate limiting) | `http_request_method`, `tyk_api_id`, `tyk_response_flag` | +| `tyk_upstream_request_duration_seconds` | Time waiting for the upstream service to respond | `http_request_method`, `tyk_api_id`, `tyk_response_flag` | + +**Histogram bucket boundaries (all three metrics, in seconds):** + +``` +0.001 0.005 0.01 0.025 0.05 0.1 0.25 0.5 1 2.5 5 10 +Inf +``` + +**What latency thresholds should I set?** + +| Percentile | Target | Alert | +|-----------|--------|-------| +| p50 (median) | < 100ms | — | +| p95 | < 500ms | > 1s for 5 min | +| p99 | < 1s | > 2s for 5 min | +| Gateway-only average | < 10ms | > 50ms | + + +**Latency isolation rule**: If `http_server_request_duration_seconds` p95 is high but `tyk_gateway_request_duration_seconds` p95 is < 10ms, **the Gateway is healthy**: the latency is upstream. If both histograms are elevated, the gateway is the bottleneck. + + +Query p95 and p99 end-to-end: + +```promql +histogram_quantile(0.95, rate(http_server_request_duration_seconds_bucket{tyk_api_id=""}[5m])) +histogram_quantile(0.99, rate(http_server_request_duration_seconds_bucket{tyk_api_id=""}[5m])) +``` + +Query Gateway-only average: + +```promql +rate(tyk_gateway_request_duration_seconds_sum{tyk_api_id=""}[5m]) + / rate(tyk_gateway_request_duration_seconds_count{tyk_api_id=""}[5m]) +``` + +**Troubleshooting high latency:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| High p95 overall, gateway avg < 10ms | Upstream service degradation; slow backend endpoint | Check `tyk_upstream_request_duration_seconds` p95. Extract `error_target` from URS logs. Escalate to backend team | +| Both gateway and upstream histograms elevated | Gateway CPU saturation; goroutine backpressure | Check `go_goroutine_count` trend and `go_memory_used_bytes`. See [Goroutines](#goroutines) below | +| p99 widening without p50 or p95 change | Load-induced queuing tail; a fraction of requests hitting a slow upstream path | Filter slow requests in logs: `latency_total > 1000`. Copy `trace_id` → Jaeger/Tempo to see which span is slow | +| High latency scoped to one client | Client calling a slow upstream endpoint (not a gateway issue) | Per-key filter in logs: `api_id="" \| line_format "key={{.api_key}} latency={{.latency_total}} path={{.path}}"` | +| Latency spike after config reload | Cold cache or policy recalculation during reload | Check `tyk_gateway_config_reload_duration_seconds`. Latency spike should resolve within 1–2 minutes | + +--- + +## Gateway Health Metrics + +Gateway health metrics reflect the internal state of the Gateway process itself, independent of API traffic. They are available when `opentelemetry.metrics.enabled: true` (Tyk Gateway 5.13+) and can be disabled independently with `runtime_metrics: false` if you only need RED signals. + +### Memory + +**Metrics:** + +| Metric name (OTel) | Prometheus name | Type | What it tells you | +|-------------------|-----------------|------|------------------| +| `go.memory.used` | `go_memory_used_bytes` | Gauge | Memory in use by the Go runtime, broken down by `go_memory_type` label (`"stack"` or `"other"`). Monitor `go_memory_type="other"` for leak detection; the stack series grows proportionally with goroutine count | +| `go.memory.gc.goal` | `go_memory_gc_goal_bytes` | Gauge | Target heap size before next GC cycle | +| `go.memory.limit` | `go_memory_limit_bytes` | Gauge | Configured `GOMEMLIMIT` value; use as the denominator for utilization alerts (alert when `go_memory_used_bytes` exceeds 85% of this) | +| `go.memory.allocated` | `go_memory_allocated_bytes_total` | Counter | Total bytes allocated since startup | +| `go.memory.allocations` | `go_memory_allocations_total` | Counter | Total allocation count since startup. High rate = allocation pressure | + +**What memory thresholds should I set?** + +There is no fixed absolute limit. Thresholds depend on how many APIs are loaded. Alert on **rate of growth** instead: + +- `go_memory_used_bytes{go_memory_type="other"}` growing > 10% per hour with stable traffic and stable API count → investigate +- If you set `GOMEMLIMIT`, alert when `go_memory_used_bytes{go_memory_type="other"}` exceeds 85% of `go_memory_limit_bytes`; GC will become aggressive and start impacting request latency + +**Troubleshooting memory issues:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| `go_memory_used_bytes{go_memory_type="other"}` growing monotonically over hours with stable API count | Memory leak in a middleware or connection pool | Requires `"enable_http_profiler": true` in `tyk.conf`. Capture heap profile: `curl http://gateway:/debug/pprof/heap > heap.pprof`. Contact Tyk support with the profile and trend chart | +| Memory growing proportionally with API count | Normal — each API definition has memory overhead | Increase instance memory; review whether all loaded APIs are still needed | +| Memory growing faster than expected with stable API count | High allocation pressure from request handling | Check rate of `go_memory_allocations_total`; if climbing steeply, contact Tyk support with a heap profile | + +--- + +### Goroutines + +**Metrics:** + +| Metric name (OTel) | Prometheus name | Type | What it tells you | +|-------------------|-----------------|------|------------------| +| `go.goroutine.count` | `go_goroutine_count` | Gauge | Number of active goroutines. Monotonic growth = leak | +| `go.processor.limit` | `go_processor_limit` | Gauge | Number of OS threads available (GOMAXPROCS) | +| `go.config.gogc` | `go_config_gogc_percent` | Gauge | GC target percentage (GOGC env var) | + +**What goroutine thresholds should I set?** + +A healthy Gateway at moderate load runs with 500–2,000 goroutines. Alert at **5,000 goroutines sustained for 10 minutes**. A one-time spike during a traffic burst is normal; a monotonically increasing trend over hours is not. + +```promql +go_goroutine_count{service_name="tyk-gateway"} +``` + +**Troubleshooting goroutine growth:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| Monotonically increasing goroutine count over hours | Goroutine leak in connection handler or background worker | Requires `"enable_http_profiler": true` in `tyk.conf` (off by default). Collect from the Control API port: `curl http://gateway:/debug/pprof/goroutine > goroutine.pprof`. Share with Tyk support along with the trend chart | +| Goroutine count high relative to traffic | CPU saturation; too many goroutines contending for OS threads | Check `go_processor_limit` (GOMAXPROCS). Consider scaling horizontally | +| Goroutine spike correlated with config reload | Reload spawning goroutines before previous ones complete | Check `tyk_gateway_config_reload_total` rate. Avoid overlapping reloads | + +--- + +### Configuration State + +**Metrics:** + +| Metric name (OTel) | Prometheus name | Type | What it tells you | +|-------------------|-----------------|------|------------------| +| `tyk.gateway.apis.loaded` | `tyk_gateway_apis_loaded` | Gauge | API definitions currently loaded. Sudden drop = sync failure | +| `tyk.gateway.policies.loaded` | `tyk_gateway_policies_loaded` | Gauge | Policies currently loaded | +| `tyk.gateway.config.reload` | `tyk_gateway_config_reload_total` | Counter | Total config reloads since startup | +| `tyk.gateway.config.reload.duration` | `tyk_gateway_config_reload_duration_seconds` | Histogram | Time per reload. High values indicate large API counts | + +**What to watch for:** + +- A **sudden drop** in `tyk_gateway_apis_loaded` (not a gradual decrease) typically means a failed sync from the Dashboard or an accidental bulk delete. Alert if the value drops by more than 10% in a single scrape interval. +- A **steady increase** in `tyk_gateway_config_reload_total` at a rate faster than your deployment cadence suggests a reload loop; investigate what is triggering reloads. +- Reload duration p95 growing over time suggests the API definition set is expanding and reload times need to be accounted for in SLOs. + +--- + +## Common Anti-Patterns + +### Auth Failure Surge + +A sudden spike in `AMF` or `AKI` response flags means clients are failing authentication. Sustained rates above **0.1 req/s** (6 per minute) warrant investigation. + +**Identify:** + +```promql +rate(http_server_request_duration_seconds_count{tyk_response_flag=~"AMF|AKI"}[5m]) +``` + +In Loki: + +```logql +{prefix="access-log"} | json | response_flag=~"AMF|AKI" + | line_format "{{.time}} {{.response_flag}} ip={{.client_ip}} api={{.api_name}}" +``` + +**Classify:** + +| Pattern | Likely cause | Action | +|---------|-------------|--------| +| Single source IP, sustained after deployment | Credential rotation missed in deploy config | Contact consumer; verify keys still valid in Tyk Dashboard | +| Single source IP, random key attempts | Misconfigured integration (wrong env endpoint) | Contact consumer | +| Wide IP spread, varied keys | Credential stuffing or API scanning | Add IP allowlisting; tighten rate limits | +| All APIs, after Tyk Dashboard restart | Gateway missed key sync | Trigger manual key sync; check Dashboard connectivity | + + +`AMF` and `AKI` are identical from a consumer-impact perspective: both result in the request never reaching the upstream. The distinction matters for root cause: `AMF` means the client didn't send a key at all; `AKI` means the client sent a key that Tyk cannot resolve. + + +--- + +### Cardinality Overflow + +The Gateway caps at **2,000 unique attribute combinations per instrument** by default (see [cardinality control](/api-management/logs-metrics#cardinality-control)). When this cap is reached, additional data points are recorded with `otel.metric.overflow = true` rather than creating new time series. + +**Detect:** + +```promql +# Any non-zero result means cardinality overflow is occurring +tyk_api_requests_total{otel_metric_overflow="true"} +``` + +**Troubleshoot:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| Overflow on `tyk_api_requests_total` | More than 2,000 unique `api_id × method × status_code` combinations | Audit which APIs are generating unusual method/status combinations; consider raising `cardinality_limit` — contact Tyk support before doing so in production | +| Overflow after adding custom dimensions | Custom dimension like `client_ip` or `user_id` is high-cardinality | Remove the high-cardinality dimension or scope it with a hash/prefix | + + +Cardinality overflow does **not** drop data. The aggregate counts are preserved in the overflow bucket. You will see correct totals but lose the ability to break the data down by the overflowing dimension combination. + + +--- + +### Retry Storm + +Client retries without exponential backoff amplify an upstream failure. `RLT` (429) responses trigger more retries, which hit rate limits, which trigger more retries, a self-reinforcing loop that increases load on both the Gateway and the upstream. + +**Identify:** + +A retry storm shows `RLT` rate climbing while overall request rate is also climbing: + +```promql +# RLT rate climbing +rate(http_server_request_duration_seconds_count{tyk_response_flag="RLT"}[1m]) + +# If this is also climbing, clients are retrying +rate(tyk_api_requests_total[1m]) +``` + +**Troubleshoot:** + +| Issue | Possible Causes | Remediation | +|-------|----------------|-------------| +| RLT climbing with overall rate climbing | Clients retrying 429s without backoff | Identify consumer via logs: `response_flag="RLT" \| line_format "key={{.key}} ip={{.client_ip}}"`. Advise consumer to implement exponential backoff with jitter | +| RLT starts immediately after upstream error (URS) | Upstream degradation triggers client retries which hit rate limits | Fix the upstream first. The rate limit is correctly protecting the backend during degradation | +| RLT on a single consumer, others unaffected | Single consumer batch job sending bursts | Work with consumer to spread requests or raise their rate limit ceiling | + +--- + +## Set Up Alerting + +Prometheus Alertmanager handles alert routing. The Gateway emits Prometheus-compatible metrics via the OTel Collector; alert rules evaluate against those metrics. + +**Configure Prometheus to load alert rules:** + +```yaml +# prometheus.yml +rule_files: + - "tyk_alerts.yml" + +alerting: + alertmanagers: + - static_configs: + - targets: ["alertmanager:9093"] +``` + +Restart Prometheus after adding the rule file. + +**Recommended alert rules:** + +```yaml expandable +groups: + - name: tyk-gateway + rules: + + # Error rate > 10% over 5 minutes + - alert: TykHighErrorRate + expr: | + ( + rate(tyk_api_requests_total{http_response_status_code!~"2..", tyk_api_id=""}[5m]) + / + rate(tyk_api_requests_total{tyk_api_id=""}[5m]) + ) > 0.10 + for: 5m + labels: + severity: warning + annotations: + summary: "Error rate > 10% for {{ $labels.tyk_api_id }}" + + # p95 latency > 1 second + - alert: TykHighLatency + expr: | + histogram_quantile(0.95, + rate(http_server_request_duration_seconds_bucket{tyk_api_id=""}[5m]) + ) > 1 + for: 5m + labels: + severity: warning + annotations: + summary: "p95 latency > 1s for {{ $labels.tyk_api_id }}" + + # Auth failure surge (AMF or AKI) + - alert: TykAuthFailureSurge + expr: | + rate(http_server_request_duration_seconds_count{tyk_response_flag=~"AMF|AKI"}[5m]) > 0.1 + for: 2m + labels: + severity: warning + annotations: + summary: "Auth failures > 6/min: client misconfiguration or credential attack" + + # Quota exhaustion + - alert: TykQuotaExhaustion + expr: | + rate(http_server_request_duration_seconds_count{tyk_response_flag="QEX"}[5m]) > 0 + for: 1m + labels: + severity: info + annotations: + summary: "Consumers hitting quota limits" + + # Rate limit rejections + - alert: TykRateLimitRejections + expr: | + rate(http_server_request_duration_seconds_count{tyk_response_flag="RLT"}[1m]) > 0 + for: 1m + labels: + severity: info + annotations: + summary: "Rate limit rejections (RLT)" + + # Upstream 5xx errors + - alert: TykUpstreamErrors + expr: | + rate(http_server_request_duration_seconds_count{tyk_response_flag="URS"}[5m]) > 0 + for: 3m + labels: + severity: critical + annotations: + summary: "Upstream returning 5xx for {{ $labels.tyk_api_id }}" + + # Goroutine growth + - alert: TykGoroutineGrowth + expr: go_goroutine_count{service_name="tyk-gateway"} > 5000 + for: 10m + labels: + severity: warning + annotations: + summary: "Goroutine count elevated, possible leak" + + # API count dropped suddenly + - alert: TykApisLoadedDrop + expr: | + (tyk_gateway_apis_loaded - tyk_gateway_apis_loaded offset 2m) + / tyk_gateway_apis_loaded offset 2m < -0.1 + for: 0m + labels: + severity: critical + annotations: + summary: "API definition count dropped > 10%: possible sync failure" +``` + +**Alert summary:** + +| Alert | Threshold | Severity | What it signals | +|-------|----------|----------|----------------| +| `TykHighErrorRate` | > 10% non-2xx for 5 min | warning | API availability degraded | +| `TykHighLatency` | p95 > 1s for 5 min | warning | Slow responses | +| `TykAuthFailureSurge` | > 0.1/s for 2 min | warning | Credential problem or attack | +| `TykQuotaExhaustion` | Any QEX for 1 min | info | Consumer tier management needed | +| `TykRateLimitRejections` | Any RLT for 1 min | info | Consumer hitting rate limits | +| `TykUpstreamErrors` | Any URS for 3 min | critical | Backend degraded | +| `TykGoroutineGrowth` | > 5,000 for 10 min | warning | Possible goroutine leak | +| `TykApisLoadedDrop` | > 10% drop in 2 min | critical | Config sync failure | + +--- + +## PromQL Quick Reference + +Replace `` with your Tyk API definition ID. + +```promql expandable +## Error rates + +# Overall non-2xx rate (as percentage) +( + rate(tyk_api_requests_total{http_response_status_code!~"2..", tyk_api_id=""}[5m]) + / rate(tyk_api_requests_total{tyk_api_id=""}[5m]) +) * 100 + +# Error breakdown by response flag +sum by (tyk_response_flag) ( + rate(http_server_request_duration_seconds_count{tyk_api_id=""}[5m]) +) + +# Upstream error rate (URS only) +rate(http_server_request_duration_seconds_count{tyk_response_flag="URS", tyk_api_id=""}[5m]) + +## Latency + +# p50 / p95 / p99 end-to-end +histogram_quantile(0.50, rate(http_server_request_duration_seconds_bucket{tyk_api_id=""}[5m])) +histogram_quantile(0.95, rate(http_server_request_duration_seconds_bucket{tyk_api_id=""}[5m])) +histogram_quantile(0.99, rate(http_server_request_duration_seconds_bucket{tyk_api_id=""}[5m])) + +# Gateway-only average latency +rate(tyk_gateway_request_duration_seconds_sum{tyk_api_id=""}[5m]) + / rate(tyk_gateway_request_duration_seconds_count{tyk_api_id=""}[5m]) + +# Upstream average latency +rate(tyk_upstream_request_duration_seconds_sum{tyk_api_id=""}[5m]) + / rate(tyk_upstream_request_duration_seconds_count{tyk_api_id=""}[5m]) + +## Gateway health + +# Goroutine count trend +go_goroutine_count{service_name="tyk-gateway"} + +# Memory in use (go_memory_type="other" = heap-adjacent; use for leak detection) +go_memory_used_bytes{service_name="tyk-gateway", go_memory_type="other"} + +# GC target heap size +go_memory_gc_goal_bytes{service_name="tyk-gateway"} + +# Configured GOMEMLIMIT (denominator for utilization alert) +go_memory_limit_bytes{service_name="tyk-gateway"} + +# APIs and policies loaded +tyk_gateway_apis_loaded{service_name="tyk-gateway"} +tyk_gateway_policies_loaded{service_name="tyk-gateway"} + +## Consumer-level breakdown (requires custom api_metrics configuration) +# These metrics are NOT emitted by default. They must be defined in +# opentelemetry.metrics.api_metrics in tyk.conf. + +# Example: requests by API key (last 6 chars), if configured with api_key session dimension +# increase(tyk_requests_by_apikey_total{tyk_api_id=""}[1h]) by (api_key) + +# Example: 5xx errors by route, if configured with endpoint metadata dimension +# increase(tyk_requests_by_route_total{tyk_api_id="", http_response_status_code="500"}[1h]) by (tyk_endpoint) +``` + +--- + +## Next Steps + +1. **Enable the OTel pipeline**: if you haven't yet, follow the [setup instructions](/api-management/observability) to enable native OTLP export and route signals to your observability backend. + +2. **Try the reference Grafana dashboards**: see the [observability setup guide](/api-management/observability) for a full modern observability stack (Loki, Grafana, Tempo, Prometheus) with pre-built panels for all the metrics in this guide. + +3. **Configure Prometheus alerting**: copy the alert rules from [Set Up Alerting](#set-up-alerting), replace `` with your real API IDs, save as `tyk_alerts.yml`, and restart Prometheus. \ No newline at end of file diff --git a/api-management/performance-monitoring.mdx b/api-management/performance-monitoring.mdx new file mode 100644 index 0000000000..2dd9f3f1aa --- /dev/null +++ b/api-management/performance-monitoring.mdx @@ -0,0 +1,156 @@ +--- +title: "Performance Monitoring" +description: "Learn how to monitor and analyze the performance of your Tyk Gateway" +keywords: "Performance, Monitoring, Observability" +sidebarTitle: "Performance Monitoring" +--- + +## What is the performance impact of analytics + +Tyk Gateway allows analytics to be recorded and stored in a persistent data store (MongoDB/SQL) for all APIs by default, via [Tyk Pump](/api-management/tyk-pump). + +Tyk Gateway generates transaction records for each API request and response, containing [analytics data](/api-management/logs/traffic-logs) relating to: the originating host (where the request is coming from), which Tyk API version was used, the HTTP method requested and request path etc. + +The transaction records are transmitted to Redis and subsequently transferred to a persistent [data store](/api-management/logs/external-data-sinks) of your choice via Tyk Pump. Furthermore, Tyk Pump can also be configured to [aggregate](/api-management/dashboard-analytics#how-aggregation-works) the transaction records (using different data keys - API ID, access key, endpoint, response status code, location) and write to a persistent data store. Tyk Dashboard uses this data for: +- [Aggregated analytics](/api-management/dashboard-analytics#traffic-analytics) - Displaying analytics based on the aggregated data. +- [Log Browser](/api-management/dashboard-analytics#activity-logs) to display raw transaction records. + +### How Do Analytics Impact Performance? + +Analytics may introduce the problem of increased CPU load and a decrease in the number of requests per second (RPS). + +In the *Tyk Dashboard API* screen below, there are two APIs, *track* and *notrack*. The APIs were created to conduct a simple load test, to show the gateway's RPS (requests per second) for each API: + +- **track**: Traffic to this API is tracked, i.e. transaction records are generated for each request/response. +- **notrack**: Traffic to this API is not tracked, i.e. transaction records are not generated for each request/response. + +apis measured in Tyk Dashboard + +100,000 requests were sent to each API and the rate at which Tyk was able to handle those requests (number of requests per second) was measured. The results for the *tracked* API are displayed in the left pane terminal window; with the right pane showing the results for the *untracked* API. + +### Tracked API Performance + +measuring tracked API performance impact + +### Untracked API Performance + +measuring do_not_track API performance impact + +### Explaining the results + +We can see that **19,253.75** RPS was recorded for the *untracked* API; with **16,743.6011** RPS reported for the *tracked* API. The number of requests per second decreased by **~13%** when analytics was enabled. + +### What Can Be Done To Address This Performance Impact? + +Tyk is configurable, allowing fine grained control over which information should be recorded and which can be skipped, thus reducing CPU cycles, traffic and storage. + +Users can selectively prevent the generation of analytics for +[do_not_track](/api-management/traffic-transformation/do-not-track) middleware: +- **Per API**: Tyk Gateway will not create records for requests/responses for any endpoints of an API. +- **Per Endpoint**: Tyk Gateway will not create records for requests/responses for specific endpoints. + +When set, this prevents Tyk Gateway from generating the transaction records. Without transaction records, Tyk Pump will not transfer analytics to the chosen persistent data store. It's worth noting that the [track middleware](/api-management/dashboard-analytics#activity-by-endpoint) exclusively influences the generation of *endpoint popularity* aggregated data by Tyk Pump. + +### Conclusion + +[Disabling](/api-management/traffic-transformation/do-not-track) the creation of analytics (either per API or for specific endpoints) helps to reduce CPU cycles and network requests for systems that exhibit high load and traffic, e.g. social media platforms, streaming, financial services and trading platforms. + +Application decisions need to be made concerning which endpoints are non critical and can thus have analytics disabled. Furthermore, benchmarking and testing will be required to evaluate the actual benefits for the application specific use case. + +Subsequently, it is worthwhile monitoring traffic and system load and using this feature to improve performance. + +## What is the performance impact of OpenTelemetry metrics + +Tyk Gateway can emit RED (Rate, Errors, Duration) metrics, Go runtime metrics, and distributed traces via [OpenTelemetry](/api-management/traces). Enabling these signals adds a per-request cost that shows up as higher latency and resource usage, not as lost throughput. + +For a broader overview of what Tyk exposes, see [Logs and Metrics](/api-management/logs-metrics). These numbers go alongside those in [What is the performance impact of analytics](/api-management/performance-monitoring#what-is-the-performance-impact-of-analytics). Most production deployments run both pipelines, and the costs add up rather than multiply. + +### How do OTel metrics impact performance? + +The figures below come from internal load tests on Tyk Gateway `v5.13.0` (GCP `c2-standard-4`, 30-minute runs at 15k rps against 10 routes, with a OpenTelemetry collector on a 5s export interval) and Go micro-benchmarks. Horizontal Pod Autoscaler was bounded to 2–12 pods and the fleet sat near the cap in every scenario (~11.8 pods on average), so pod counts were roughly constant across configurations. A higher HPA cap might absorb tracing overhead as extra pods rather than higher per-pod CPU, so treat the figures as an upper bound on per-instance cost. + +- **Default RED metrics are cheap.** Compared to a baseline (OTel off) at 0.57 ms p75 and 25,259 rps, enabling default RED metrics raises p75 to 0.78 ms (+37%) and adds +4.6% mean CPU and +2.3% mean memory. Still sub-millisecond. +- **Tracing is the expensive part.** Adding tracing on top of metrics pushes p99 from 11.16 ms to 29.68 ms at 50% sampling, and to 35.53 ms at 100% sampling. CPU goes up ~27% and memory ~34% over baseline. +- **Sampling has a floor.** Dropping from 100% to 50% sampling saves about 17% at p99, but CPU and memory are identical at both rates. Most of the tracing cost is fixed: span context propagation and SDK overhead are paid even when a span is dropped. +- **Runtime metrics are free.** Adding Go runtime metrics on top of RED produces no measurable difference in either load tests or micro-benchmarks. +- **Cardinality and instrument count scale flat.** Going from 2 to 14 dimensions on a single counter, or from 1 to 6 instruments, keeps per-request overhead inside a +3% to +7% band +- **Throughput holds.** RPS stayed within ±1.5% of baseline across every configuration. The cost is in latency and resource usage, not in dropped requests. + +The table below shows per-configuration overhead against the OTel-off baseline (10 routes, 15k rps, 30-minute runs): + +| Configuration | p75 Δ | p99 Δ | CPU mean Δ | Memory mean Δ | RPS Δ | +|---|---:|---:|---:|---:|---:| +| Baseline (OTel off, analytics off) — 0.57 ms p75 / 13.0 cores / 13.1 GiB / 25,259 rps | — | — | — | — | — | +| Analytics only | +28.1% | +78.5% | +6.9% | +1.5% | -1.0% | +| Metrics only (default RED) | +36.8% | +76.9% | +4.6% | +2.3% | -0.6% | +| Metrics + runtime | +17.5% | +26.2% | +2.3% | 0% | -0.7% | +| Metrics + tracing (50% sampling) + runtime | +189.5% | +235.7% | +29.2% | +34.4% | -0.9% | +| Full OTel: tracing (100%) + metrics + runtime | +173.7% | +301.9% | +30.0% | +34.4% | -1.4% | +| Metrics + analytics (Pump) | +31.6% | +100.1% | +12.3% | +6.1% | -1.1% | + +These are internal benchmark numbers. Absolute figures won't transfer to different hardware or collector setups, but the relative deltas have been verified across both the micro-benchmark suite and the GCP load tests. + +### What can be done to address this performance impact? + +- **Enable runtime metrics.** They add GC, goroutine, and heap data at no measurable cost. The load test shows CPU +0.5% and memory +0% versus metrics-only. There's no reason to leave them off. +- **Treat custom metrics as a memory problem, not a CPU one.** In load tests, 3 custom metric definitions across 50 routes pushed mean memory up +28% and peak memory up +32%, with no CPU change. Avoid high-cardinality label values like user IDs, JWT subjects, full URLs, or trace IDs. Each unique combination creates a new time series held in memory until export. Before adding custom metrics, estimate `routes × instruments × dimension cardinality`. +- **Sample traces aggressively, but don't expect linear savings.** Configure [opentelemetry.sampling.type](/tyk-oss-gateway/configuration#opentelemetry-sampling-type) as `TraceIDRatioBased` and set [opentelemetry.sampling.rate](/tyk-oss-gateway/configuration#opentelemetry-sampling-rate) to 0.05–0.1 for high-RPS gateways. Use [opentelemetry.sampling.parent_based](/tyk-oss-gateway/configuration#opentelemetry-sampling-parent_based) to keep spans coherent. See [Sampling Strategies](/api-management/traces#sampling) for all options. Since dropping from 100% to 50% only saves ~17% at p99 and nothing on CPU/memory, the real fix is to disable tracing for APIs that don't need it. +- **Enable detailed tracing per API, not globally.** The Tyk OAS API [server.detailedTracing](/api-management/gateway-config-tyk-oas#detailedtracing) field (and its [Tyk Classic equivalent](/api-management/gateway-config-tyk-classic#opentelemetry)) turns on middleware-level spans for specific APIs only. Use it to limit span volume to where it's useful. +- **Keep the default 5s export interval.** Gateway-side cost stays under +5% even at 1,000 APIs. Changing the interval doesn't help the gateway; it only shifts pressure to the collector. Tune [opentelemetry.span_batch_config](/tyk-oss-gateway/configuration#opentelemetry-span_batch_config) (`max_queue_size`, `max_export_batch_size`, `batch_timeout`) only if traces are being dropped because the collector can't keep up. +- **Don't run both signal pipelines unless you need to.** Running OTel metrics alongside Tyk analytics adds about +3% p75 and +12% p99 over analytics alone. If a Pump-driven Prometheus pipeline already covers your KPIs, the overlap may not be worth it. +- **Avoid 100% trace sampling on high-RPS gateways.** The worst-case configuration (full OTel at 100% sampling) hit +174% p75, +302% p99, +30% CPU, and +34% memory. Use head-based sampling at a low ratio, or limit tracing to a subset of APIs. + +### Conclusion + +The default RED instruments and Go runtime metrics are safe to enable in production. The cost is small, throughput barely moves, and adding more dimensions or instruments doesn't compound overhead. Tracing is the expensive part: the cost is largely fixed, and the only real way to eliminate it is to disable tracing, not to sample it low. Keep sample rates low, use per-API detailed tracing where possible, and size pod memory against your API count. For more detail on OTel configuration, see [Distributed Tracing with OpenTelemetry](/api-management/traces) and [Sampling Strategies](/api-management/traces#sampling). + +## How to reduce CPU usage in a Redis Cluster + +### What does high CPU usage in a Redis node within a Redis Cluster mean ? + +When a single Redis node within a Redis Cluster exhibits high CPU usage, it indicates that the CPU resources of that particular node are being heavily utilized compared to others in the cluster. + +The illustration below highlights the scenario where a single Redis node is exhibiting high CPU usage of 1.20% within a Redis Cluster. + +analytics keys stored in one Redis server + +### What could be causing this high CPU usage ? + +One possible reason for high CPU usage in a single Redis node within a Redis Cluster is that analytics features are enabled and keys are being stored within that specific Redis node. + +### How does storing keys within a single Redis server contribute to high CPU usage ? + +A high volume of analytics traffic can decrease performance, since all analytics keys are stored within one Redis server. Storing keys within a single Redis server can lead to increased CPU usage because all operations related to those keys, such as retrieval, updates and analytics processing, are concentrated on that server. This can result in heavier computational loads on that particular node. This leads to high CPU usage. + +### What can be done to address high CPU usage in this scenario ? + +Consider distributing the analytics keys across multiple Redis nodes within the cluster. This can help distribute the computational load more evenly, reducing the strain on any single node and potentially alleviating the high CPU usage. + +In Redis, *key sharding* is a term used to describe the practice of distributing data across multiple Redis instances or *shards* based on the keys. This feature is provided by [Redis Cluster](https://redis.io/docs/management/scaling/) and provides horizontal scalability and improved performance. + +Tyk supports configuring this behavior so that analytics keys are distributed across multiple servers within a Redis cluster. The image below illustrates that CPU usage is reduced across two Redis servers after making this configuration change. + +analytics keys distributed across Redis servers + +### How do I configure Tyk to distribute analytics keys to multiple Redis shards ? + +Follow these steps: + +1. **Check that your Redis Cluster is correctly configured** + + Confirm that the `enable_cluster` configuration option is set to true in the [Tyk Gateway](/tyk-oss-gateway/configuration#storage-enable_cluster), [Tyk Dashboard](/tyk-dashboard/configuration#enable_cluster) and [Tyk Pump](/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables#analytics_storage_config-enable_cluster) configuration files. This setting + informs Tyk that a Redis Cluster is in use for key storage. + + Ensure that the `addrs` array is populated in the [Tyk Gateway](/tyk-oss-gateway/configuration#storage-addrs) and [Tyk Pump](/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables#analytics_storage_config-addrs) configuration files (*tyk.conf* and *pump.conf*) with the addresses of all Redis Cluster nodes. If you are using Tyk Self Managed (the licensed product), also update [Tyk Dashboard](/tyk-dashboard/configuration#redis_addrs) configuration file (*tyk_analytics.conf*). This ensures that the Tyk components can interact with the entire Redis Cluster. Please refer to the [configure Redis Cluster](/tyk-configuration-reference/redis-cluster-sentinel#configure-redis-cluster) guide for further details. + +2. **Configure Tyk to distribute analytics keys to multiple Redis shards** + + To distribute analytics keys across multiple Redis shards effectively you need to configure the Tyk components to leverage the Redis cluster's sharding capabilities: + + 1. **Optimize Analytics Configuration**: In the Tyk Gateway configuration (tyk.conf), set [analytics_config.enable_multiple_analytics_keys](/tyk-oss-gateway/configuration#analytics_config-enable_multiple_analytics_keys) to true. This option allows Tyk to distribute analytics data across Redis nodes, using multiple keys for the analytics. There's a corresponding option for Self Managed MDCB, also named [enable_multiple_analytics_keys](/tyk-multi-data-centre/mdcb-configuration-options#enable_multiple_analytics_keys). Useful only if the gateways in the data plane are configured to send analytics to MDCB. + 2. **Optimize Connection Pool Settings**: Adjust the [optimization_max_idle](/tyk-oss-gateway/configuration#storage-optimisation_max_idle) and [optimization_max_active](/tyk-oss-gateway/configuration#storage-optimisation_max_active) settings in the configuration files to ensure that the connection pool can handle the analytics workload without overloading any Redis shard. + 3. **Use a Separate Analytics Store**: For high analytics traffic, you can opt to use a dedicated *Redis Cluster* for analytics by setting [enable_separate_analytics_store](/tyk-oss-gateway/configuration#enable_separate_analytics_store) to true in the Tyk Gateway configuration file (*tyk.conf*) and specifying the separate Redis cluster configuration in the `analytics_storage` section. See [Separate Analytics Storage](/planning-for-production/database-settings#separate-analytics-storage) for a worked example. + 4. **Review and Test**: After implementing these changes, thoroughly review your configurations and conduct load testing to verify that the analytics traffic is now evenly distributed across all Redis shards. + + By following these steps you can enhance the distribution of analytics traffic across the Redis shards. This should lead to improved scalability and performance of your Tyk deployment. + diff --git a/api-management/plugins/advance-config.mdx b/api-management/plugins/advance-config.mdx new file mode 100644 index 0000000000..2104068cf4 --- /dev/null +++ b/api-management/plugins/advance-config.mdx @@ -0,0 +1,625 @@ +--- +title: "Custom Plugins Advance Configuration" +description: "Explore advanced configuration options for custom plugins in Tyk, including CICD automation, OpenTelemetry instrumentation, and gRPC server health checks in Kubernetes." +sidebarTitle: "Advanced Configuration" +--- + +## CICD - Automating Your Plugin Builds + +It's very important to automate the deployment of your infrastructure. + +Ideally, you store your configurations and code in version control, and then through a trigger, have the ability to deploy everything automatically into higher environments. + +With custom plugins, this is no different. + +To illustrate this, we can look at the GitHub Actions of the [example repo][0]. + +We see that upon every pull request, a section of steps are taken to "Build, [Bundle](/api-management/plugins/overview#plugin-bundles), Release Go Plugin". + +Let's break down the [workflow file][1]: + + +### Compiling the Plugin + +We can see the first few steps replicate our first task, bootstrapping the environment and compiling the plugin into a binary format. + +```make + steps: + - uses: actions/checkout@v3 + + - name: Copy Env Files + run: cp tyk/confs/tyk_analytics.env.example tyk/confs/tyk_analytics.env + + - name: Build Go Plugin + run: make go-build +``` + +We can look at the [Makefile][2] to further break down the last `go-build` command. + +### Bundle The Plugin + +The next step of the workflow is to "[bundle](/api-management/plugins/overview#plugin-bundles)" the plugin. + +``` +- name: Bundle Go Plugin + run: docker-compose run --rm --user=1000 --entrypoint "bundle/bundle-entrypoint.sh" tyk-gateway +``` + +This command generates a "bundle" from the sample Go plugin in the repo. + + + +For added security, please consider signing your [bundles](/api-management/plugins/overview#plugin-deployment-types), especially if the connection between the Gateways and the Bundler server traverses the internet. + + + + +Custom plugins can be "bundled", (zipped/compressed) into a standard format, and then uploaded to some server so that they can be downloaded by the Gateways in real time. + +This process allows us to decouple the building of our custom plugins from the runtime of the Gateways. + +In other words, Gateways can be scaled up and down, and pointed at different plugin repos very easily. This makes it easier to deploy Custom plugins especially in containerized environments such as Kubernetes, where we don't have to worry about persistent volumes. + +You can read more about plugin bundles [here][3]. + +### Deploy The Plugin + +Next step of the workflow is to publish our bundle to a server that's reachable by the Gateways. + +```make +- name: Upload Bundle + uses: actions/upload-artifact@v3 + with: + name: customgoplugin.zip + path: tyk/bundle/bundle.zip + + - uses: jakejarvis/s3-sync-action@master + with: + args: --acl public-read --follow-symlinks + env: + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: 'us-east-1' + SOURCE_DIR: 'tyk/bundle' +``` + +This step uploads the Bundle to both GitHub and an AWS S3 bucket. Obviously, your workflow will look slightly different here. + + + +For seamless deployments, take a look at multi-version [plugin support](/tyk-cloud/using-plugins) to enable zero downtime deployments of your Tyk Gateway installs + + + +### Configure the Gateway + +In order to instruct the Gateway to download the bundle, we need two things: + +1. The root server - The server which all bundles will be downloaded from. This is set globally in the Tyk conf file [here](/tyk-oss-gateway/configuration#enable_bundle_downloader). + +2. The name of the bundle - this is generated during your workflow usually. This is defined at the API level (this is where you declare Custom plugins, as evident in task 2) + +The field of the API Definition that needs to be set is `custom_middleware_bundle`. + +### Summary + +That's it! + +We've set up our dev environment, built, compiled, a Custom Go plugin, loaded onto a Tyk Gateway, and tested it by sending an API request. Finally, we've talked about deploying a Bundle in a production grade set up. + +Have a look at our [examples repo][4] for more inspiration. + +[0]: https://github.com/TykTechnologies/custom-go-plugin +[1]: https://github.com/TykTechnologies/custom-go-plugin/blob/master/.github/workflows/makefile.yml +[2]: https://github.com/TykTechnologies/custom-go-plugin/blob/master/Makefile#L59 +[3]: https://github.com/TykTechnologies/custom-go-plugin#deploying-the-go-plugin +[4]: https://github.com/TykTechnologies/custom-plugin-examples + +## Instrumenting Plugins with OpenTelemetry + +By instrumenting your custom plugins with Tyk's *OpenTelemetry* library, you can gain additional insights into custom plugin behavior like time spent and exit status. Read on to see some examples of creating span and setting attributes for your custom plugins. + + + +**Note:** +Although this documentation is centered around Go plugins, the outlined principles are universally applicable to plugins written in other languages. Ensuring proper instrumentation and enabling detailed tracing will integrate the custom plugin span into the trace, regardless of the underlying programming language. + + + +### Prerequisites + +- Go v1.19 or higher +- Gateway instance with OpenTelemetry and DetailedTracing enabled: + +Add this field within your [Gateway config file](/tyk-oss-gateway/configuration): + +```json +{ + "opentelemetry": { + "enabled": true + } +} +``` + +And this field within your [API definition](/api-management/gateway-config-introduction): + +```json +{ + "detailed_tracing": true +} +``` + +You can find more information about enabling OpenTelemetry [here](/api-management/traces). + + + +DetailedTracing must be enabled in the API definition to see the plugin spans in the traces. + + + +In order to instrument our plugins we will be using Tyk's OpenTelemetry library implementation. +You can import it by running the following command: + +```console +$ go get github.com/TykTechnologies/opentelemetry +``` + +
+ + + +In this case, we are using our own OpenTelemetry library for convenience. You can also use the [Official OpenTelemetry Go SDK](https://github.com/open-telemetry/opentelemetry-go) + + + +### Create a new span from the request context + +`trace.NewSpanFromContext()` is a function that helps you create a new span from the current request context. When called, it returns two values: a fresh context with the newly created span embedded inside it, and the span itself. This method is particularly useful for tracing the execution of a piece of code within a web request, allowing you to measure and analyze its performance over time. + +The function takes three parameters: + +1. `Context`: This is usually the current request's context. However, you can also derive a new context from it, complete with timeouts and cancelations, to suit your specific needs. +2. `TracerName`: This is the identifier of the tracer that will be used to create the span. If you do not provide a name, the function will default to using the `tyk` tracer. +3. `SpanName`: This parameter is used to set an initial name for the child span that is created. This name can be helpful for later identifying and referencing the span. + +Here's an example of how you can use this function to create a new span from the current request context: + +```go +package main +import ( + "net/http" + "github.com/TykTechnologies/opentelemetry/trace" +) +// AddFooBarHeader adds a custom header to the request. +func AddFooBarHeader(rw http.ResponseWriter, r *http.Request) { + // We create a new span using the context from the incoming request. + _, newSpan := trace.NewSpanFromContext(r.Context(), "", "GoPlugin_first-span") + // Ensure that the span is properly ended when the function completes. + defer newSpan.End() + // Add a custom "Foo: Bar" header to the request. + r.Header.Add("Foo", "Bar") +} +func main() {} +``` + +In your exporter (in this case, Jaeger) you should see something like this: + +OTel Span from context + +As you can see, the name we set is present: `GoPlugin_first-span` and it's the first child of the `GoPluginMiddleware` span. + +### Modifying span name and set status + +The span created using `trace.NewSpanFromContext()` can be further configured after its creation. You can modify its name and set its status: + +```go +func AddFooBarHeader(rw http.ResponseWriter, r *http.Request) { + _, newSpan := trace.NewSpanFromContext(r.Context(), "", "GoPlugin_first-span") + defer newSpan.End() + + // Set a new name for the span. + newSpan.SetName("AddFooBarHeader Testing") + + // Set the status of the span. + newSpan.SetStatus(trace.SPAN_STATUS_OK, "") + + r.Header.Add("Foo", "Bar") +} +``` + +This updated span will then appear in the traces as `AddFooBarHeader Testing` with an **OK Status**. + +The second parameter of the `SetStatus` method can accept a description parameter that is valid for **ERROR** statuses. + +The available span statuses in ascending hierarchical order are: + +- `SPAN_STATUS_UNSET` + +- `SPAN_STATUS_ERROR` + +- `SPAN_STATUS_OK` + +This order is important: a span with an **OK** status cannot be overridden with an **ERROR** status. However, the reverse is possible - a span initially marked as **UNSET** or **ERROR** can later be updated to **OK**. + +OTel Span name and status + +Now we can see the new name and the `otel.status_code` tag with the **OK** status. + +### Setting attributes + +The `SetAttributes()` function allows you to set attributes on your spans, enriching each trace with additional, context-specific information. + +The following example illustrates this functionality using the OpenTelemetry library's implementation by Tyk + +```go +func AddFooBarHeader(rw http.ResponseWriter, r *http.Request) { + _, newSpan := trace.NewSpanFromContext(r.Context(), "", "GoPlugin_first-span") + defer newSpan.End() + + // Set an attribute on the span. + newSpan.SetAttributes(trace.NewAttribute("go_plugin", "1")) + + r.Header.Add("Foo", "Bar") +} +``` + +In the above code snippet, we set an attribute `go_plugin` with a value of `1` on the span. This is just a demonstration; in practice, you might want to set attributes that carry meaningful data relevant to your tracing needs. + +Attributes are key-value pairs. The value isn't restricted to string data types; it can be any value, including numerical, boolean, or even complex data types, depending on your requirements. This provides flexibility and allows you to include rich, structured data within your spans. + +The illustration below, shows how the `go_plugin` attribute looks in Jaeger: + +OTel Span attributes + +### Multiple functions = Multiple spans + +To effectively trace the execution of your plugin, you can create additional spans for each function execution. By using context propagation, you can link these spans, creating a detailed trace that covers multiple function calls. This allows you to better understand the sequence of operations, pinpoint performance bottlenecks, and analyze application behavior. + +Here's how you can implement it: + +```go +func AddFooBarHeader(rw http.ResponseWriter, r *http.Request) { + // Start a new span for this function. + ctx, newSpan := trace.NewSpanFromContext(r.Context(), "", "GoPlugin_first-span") + defer newSpan.End() + + // Set an attribute on this span. + newSpan.SetAttributes(trace.NewAttribute("go_plugin", "1")) + + // Call another function, passing in the context (which includes the new span). + NewFunc(ctx) + + // Add a custom "Foo: Bar" header to the request. + r.Header.Add("Foo", "Bar") +} + +func NewFunc(ctx context.Context) { + // Start a new span for this function, using the context passed from the calling function. + _, newSpan := trace.NewSpanFromContext(ctx, "", "GoPlugin_second-span") + defer newSpan.End() + + // Simulate some processing time. + time.Sleep(1 * time.Second) + + // Set an attribute on this span. + newSpan.SetAttributes(trace.NewAttribute("go_plugin", "2")) +} +``` + +In this example, the `AddFooBarHeader` function creates a span and then calls `NewFunc`, passing the updated context. The `NewFunc` function starts a new span of its own, linked to the original through the context. It also simulates some processing time by sleeping for 1 second, then sets a new attribute on the second span. In a real-world scenario, the `NewFunc` would contain actual code logic to be executed. + +The illustration below, shows how this new child looks in Jaeger: + +OTel Span attributes + +### Error handling + +In OpenTelemetry, it's essential to understand the distinction between recording an error and setting the span status to error. The `RecordError()` function records an error as an exception span event. However, this alone doesn't change the span's status to error. To mark the span as error, you need to make an additional call to the `SetStatus()` function. + +> RecordError will record err as an exception span event for this span. An additional call to SetStatus is required if the Status of the Span should be set to Error, as this method does not change the Span status. If this span is not being recorded or err is nil then this method does nothing. + +Here's an illustrative example with function calls generating a new span, setting attributes, setting an error status, and recording an error: + +```go +func AddFooBarHeader(rw http.ResponseWriter, r *http.Request) { + // Create a new span for this function. + ctx, newSpan := trace.NewSpanFromContext(r.Context(), "", "GoPlugin_first-span") + defer newSpan.End() + + // Set an attribute on the new span. + newSpan.SetAttributes(trace.NewAttribute("go_plugin", "1")) + + // Call another function, passing in the updated context. + NewFunc(ctx) + + // Add a custom header "Foo: Bar" to the request. + r.Header.Add("Foo", "Bar") +} + +func NewFunc(ctx context.Context) { + // Create a new span using the context passed from the previous function. + ctx, newSpan := trace.NewSpanFromContext(ctx, "", "GoPlugin_second-span") + defer newSpan.End() + + // Simulate some processing time. + time.Sleep(1 * time.Second) + + // Set an attribute on the new span. + newSpan.SetAttributes(trace.NewAttribute("go_plugin", "2")) + + // Call a function that will record an error and set the span status to error. + NewFuncWithError(ctx) +} + +func NewFuncWithError(ctx context.Context) { + // Start a new span using the context passed from the previous function. + _, newSpan := trace.NewSpanFromContext(ctx, "", "GoPlugin_third-span") + defer newSpan.End() + + // Set status to error. + newSpan.SetStatus(trace.SPAN_STATUS_ERROR, "Error Description") + + // Set an attribute on the new span. + newSpan.SetAttributes(trace.NewAttribute("go_plugin", "3")) + + // Record an error in the span. + newSpan.RecordError(errors.New("this is an auto-generated error")) +} +``` + +In the above code, the `NewFuncWithError` function demonstrates error handling in OpenTelemetry. First, it creates a new span. Then it sets the status to error, and adds an attribute. Finally, it uses `RecordError()` to log an error event. This two-step process ensures that both the error event is recorded and the span status is set to reflect the error. + +OTel Span error handling + + +## Highly Available gRPC Servers in Kubernetes + +When deploying gRPC servers to host [rich plugins](/api-management/plugins/rich-plugins#using-grpc) in Kubernetes, implementing proper health checks on those servers is crucial so that you can achieve high availability and enable seamless rolling updates. + +Kubernetes needs to know when your gRPC server is ready to accept traffic otherwise: + +- Traffic may be routed to pods that aren't ready +- Failed pods won't be automatically restarted +- Load balancing becomes unreliable +- Rolling deployments can cause service disruption + +When using gRPC plugins to implement custom processing of API requests, this is especially important since the Gateway depends on having reliable access to these services to process requests. If Tyk is unable to reach the gRPC server, then it will be unable to correctly execute the plugins hosted there, leading to API requests failing. + +### Key Benefits of Health Checks for Rolling Updates + +1. **Zero-Downtime Deployments** +- Readiness probes ensure new pods are fully ready before receiving traffic +- Old pods continue serving until new ones are ready +- Traffic is never routed to non-functional pods + +2. **Graceful Shutdown** +- SIGTERM triggers immediate graceful shutdown sequence +- Health status changes to "not serving" during shutdown +- gRPC server stops gracefully, finishing in-flight requests +- 30-second timeout ensures forced shutdown if graceful shutdown hangs + +3. **Failure Recovery** +- Liveness probes detect and restart unhealthy pods +- Startup probes give adequate time for slow-starting services +- Failed deployments are automatically rolled back + +### Using gRPC Health Probes with Tyk + +Tyk's native support for gRPC health probes is provided via the Health Checking service in the standard gRPC Go library and provides several benefits: + +1. **Native Integration**: Uses the same transport protocol as your main service +2. **Service-Specific Checks**: Can check the health of individual gRPC services +3. **Better Resource Utilization**: No need for a separate HTTP server +4. **Consistent Protocol**: Maintains gRPC throughout the stack + +#### gRPC Health Checking Protocol + +The following example uses the [gRPC Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) instead of HTTP endpoints. + +#### Health Check Implementation Details + +The gRPC health server manages two service states: + +- `""` (empty string): Overall server health +- `"coprocess.Dispatcher"`: Specific service health for readiness checks + +Health states transition as follows: +- **Startup**: `NOT_SERVING` → `SERVING` (when ready) +- **Shutdown**: `SERVING` → `NOT_SERVING` (immediate) +- **Error**: `SERVING` → `NOT_SERVING` (on failure) + +#### Example Implementation + +##### Required Dependencies + +You should add this to your `go.mod`: + +```go +require ( + google.golang.org/grpc v1.50.0 // or later +) +``` + +##### main.go Setup + +```go +package main + +import ( + "context" + "log" + "net" + "os" + "os/signal" + "sync/atomic" + "syscall" + "time" + + "github.com/TykTechnologies/tyk/coprocess" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" +) + +const ( + ListenAddress = ":50051" +) + +func main() { + // Track server readiness + var isReady int32 + + lis, err := net.Listen("tcp", ListenAddress) + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } + + log.Printf("starting grpc server on %v", ListenAddress) + s := grpc.NewServer() + + // Register the main coprocess service + coprocess.RegisterDispatcherServer(s, &Dispatcher{}) + + // Register health service + healthServer := health.NewServer() + grpc_health_v1.RegisterHealthServer(s, healthServer) + + // Initially mark all services as not serving + healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + healthServer.SetServingStatus("coprocess.Dispatcher", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + + // Channel to listen for errors coming from the listener. + serverErrors := make(chan error, 1) + + // Start the service listening for requests. + go func() { + // Mark as ready once server is initialized + atomic.StoreInt32(&isReady, 1) + + // Set health status to serving + healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING) + healthServer.SetServingStatus("coprocess.Dispatcher", grpc_health_v1.HealthCheckResponse_SERVING) + + log.Printf("gRPC server is ready to accept connections") + serverErrors <- s.Serve(lis) + }() + + // Channel to listen for an interrupt or terminate signal from the OS. + shutdown := make(chan os.Signal, 1) + signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM) + + // Blocking main and waiting for shutdown. + select { + case err := <-serverErrors: + atomic.StoreInt32(&isReady, 0) + // Mark services as not serving + healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + healthServer.SetServingStatus("coprocess.Dispatcher", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + log.Fatalf("Server error: %v", err) + case sig := <-shutdown: + atomic.StoreInt32(&isReady, 0) + log.Printf("Received signal: %v", sig) + + // Mark services as not serving during shutdown + healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + healthServer.SetServingStatus("coprocess.Dispatcher", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + + // Give outstanding requests 5 seconds to complete. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + stopped := make(chan struct{}) + go func() { + s.GracefulStop() + close(stopped) + }() + + select { + case <-ctx.Done(): + log.Printf("Graceful shutdown timed out") + s.Stop() + case <-stopped: + log.Printf("Graceful shutdown completed") + } + } +} + +``` + +##### Kubernetes Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tyk-grpc-coprocess + labels: + app: tyk-grpc-coprocess +spec: + replicas: 3 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + selector: + matchLabels: + app: tyk-grpc-coprocess + template: + metadata: + labels: + app: tyk-grpc-coprocess + spec: + containers: + - name: grpc-server + image: your-registry/tyk-grpc-coprocess:latest + ports: + - containerPort: 50051 + name: grpc + + ## Readiness probe - determines when pod is ready for traffic + readinessProbe: + grpc: + port: 50051 + service: coprocess.Dispatcher + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + + ## Liveness probe - determines when to restart pod + livenessProbe: + grpc: + port: 50051 + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + + ## Startup probe - gives more time for initial startup + startupProbe: + grpc: + port: 50051 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 30 + +--- +apiVersion: v1 +kind: Service +metadata: + name: tyk-grpc-coprocess-service +spec: + selector: + app: tyk-grpc-coprocess + ports: + - name: grpc + port: 50051 + targetPort: 50051 + protocol: TCP + type: ClusterIP +``` + diff --git a/api-management/plugins/golang.mdx b/api-management/plugins/golang.mdx new file mode 100644 index 0000000000..508e2ec396 --- /dev/null +++ b/api-management/plugins/golang.mdx @@ -0,0 +1,1040 @@ +--- +title: "Golang Plugins" +description: "Learn how to extend Tyk Gateway functionality by writing custom middleware plugins in Go" +sidebarTitle: "Golang Plugins" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + +## Introduction + +Golang plugins are a very flexible and powerful way to extend the functionality of Tyk by attaching custom logic written in Go to [hooks](/api-management/plugins/plugin-types#plugin-types) in the Tyk [middleware chain](/api-management/traffic-transformation#request-middleware-chain). +The chain of middleware is specific to an API and gets created at API load time. When Tyk Gateway performs an API re-load it also loads any custom middleware and "injects" them into a chain to be called at different stages of the HTTP request life cycle. + +For a quick-start guide to working with Go plugins, start [here](/api-management/plugins/overview#getting-started). + +The [Go plugin writing guide](/api-management/plugins/golang#writing-custom-go-plugins) provides details of how to access dynamic data (such as the key session object) from your Go functions. Combining these resources provides you with a powerful set of tools for shaping and structuring inbound traffic to your API. + +## Supported plugin types + +All of Tyk's [custom middleware hooks](/api-management/plugins/plugin-types#plugin-types) support Go plugins. They represent different stages in the request and response [middleware chain](/api-management/traffic-transformation#request-middleware-chain) where custom functionality can be added. + +- **Pre** - supports an array of middleware that run before any others (i.e. before authentication) +- **Auth** - this middleware performs custom authentication and adds API key session info into the request context and can be used only if the API definition has both: + - `"use_keyless": false` + - `"use_go_plugin_auth": true` +- **Post-Auth** - supports an array of middleware to be run after authentication; at this point, we have authenticated the session API key for the given key (in the request context) so we can perform any extra checks. This can be used only if the API definition has both: + - `"use_keyless": false` + - an authentication method specified +- **Post** - supports an array of middleware that run at the very end of the middleware chain, just before Tyk makes a round-trip to the upstream target +- **Response** - run only at the point the response has returned from a service upstream of the API Gateway; note that the [method signature for Response Go plugins](/api-management/plugins/golang#creating-a-custom-response-plugin) is slightly different from the other hook types + + + + + The `use_keyless` and `use_go_plugin_auth` fields are populated automatically with the correct values if you add a plugin to the **Auth** or **Post-Auth** hooks when using the Tyk Dashboard. + + +## Custom Go plugin development flow + +Go Plugins need to be compiled to native shared object code, which can then be loaded by Tyk Gateway. + +We recommend that you familiarize yourself with the following official Go documentation to help you work effectively with Go plugins: + +- [The official plugin package documentation - Warnings](https://pkg.go.dev/plugin) +- [Tutorial: Getting started with multi-module workspaces](https://go.dev/doc/tutorial/workspaces) + + + + + Plugins are currently supported only on Linux, FreeBSD, and macOS, making them unsuitable for applications intended to be portable. + + + +### Tyk Plugin Compiler + +We provide the [Tyk Plugin Compiler](/api-management/plugins/golang#plugin-compiler) docker image, which we **strongly recommend** is used to build plugins compatible with the official Gateway releases. That tool provides the cross compilation toolchain, Go version used to build the release, ensures that compatible flags are used when compiling plugins (such as `-trimpath`, `CC`, `CGO_ENABLED`, `GOOS`, `GOARCH`) and also works around known Go issues such as: + +- https://github.com/golang/go/issues/19004 +- https://www.reddit.com/r/golang/comments/qxghjv/plugin_already_loaded_when_a_plugin_is_loaded/ + +#### Understanding Plugin Compiler Security Scans + +The Tyk Plugin Compiler Docker image is a development tool used exclusively during the build phase to compile custom Go plugins for the Tyk Gateway. This tool: + +1. **Is not a runtime component** - It is never deployed as part of your production Tyk environment +2. **Operates in isolated build environments** - It should only be used in controlled development or CI/CD pipelines +3. **Is not designed to be network-exposed** - It should never be deployed as a service or exposed to untrusted networks + +##### Technical Context +The Plugin Compiler is built on Debian Bullseye to ensure binary compatibility with RHEL8 environments, which are commonly used in enterprise Tyk deployments. Security scanning tools may flag numerous Common Vulnerabilities and Exposures (CVEs) in the base Debian Bullseye libraries included in this image. + +##### Security Clarification +These CVEs do not represent an exploitable attack surface in your Tyk deployment for several reasons: + +1. **Build-time vs. Runtime Separation:** The Plugin Compiler is strictly a build-time tool. The compiled plugins that it produces are what get deployed to your Tyk Gateway, not the compiler itself. + +2. **Ephemeral Usage Pattern**: The recommended usage pattern is to run the compiler only when needed to generate plugin binaries, then discard the container. + +3. **Air-gapped Operation**: The compilation process typically occurs in development environments or CI/CD pipelines that are separate from production systems. + +4. **No Persistent Deployment**: Unlike the Tyk Gateway, Dashboard, and other runtime components, the Plugin Compiler is never deployed as a long-running service in your API management infrastructure. + +For optimal security, we recommend running the Plugin Compiler in isolated build environments and transferring only the compiled plugin binaries to your production Tyk deployment. + + +### Setting up your environment + +It's important to understand the need for plugins to be compiled using exactly the same environment and build flags as the Gateway. To simplify this and minimise the risk of compatibility problems, we recommend the use of [Go workspaces](https://go.dev/blog/get-familiar-with-workspaces), to provide a consistent environment. + +To develop plugins without using the Tyk Plugin Compiler, you'll need: + +- Go (matching the version used in the Gateway, which you can determine using `go.mod`). +- Git to check out Tyk Gateway source code. +- A folder with the code that you want to build into plugins. + +We recommend that you set up a *Go workspace*, which, at the end, is going to contain: + +- `/tyk-release-x.y.z` - the Tyk Gateway source code +- `/plugins` - the plugins +- `/go.work` - the *Go workspace* file +- `/go.work.sum` - *Go workspace* package checksums + +Using the *Go workspace* ensures build compatibility between the plugins and Gateway. + +### Steps for Configuration: + +1. **Checking out Tyk Gateway source code** + + ``` + git clone --branch release-5.3.6 https://github.com/TykTechnologies/tyk.git tyk-release-5.3.6 || true + ``` + + This example uses a particular `release-5.3.6` branch, to match Tyk Gateway release 5.3.6. With newer `git` versions, you may pass `--branch v5.3.6` and it would use the tag. In case you want to use the tag it's also possible to navigate into the folder and issue `git checkout tags/v5.3.6`. + +2. **Preparing the Go workspace** + + Your Go workspace can be very simple: + + 1. Create a `.go` file containing the code for your plugin. + 2. Create a `go.mod` file for the plugin. + 3. Ensure the correct Go version is in use. + + As an example, we can use the [CustomGoPlugin.go](https://github.com/TykTechnologies/custom-go-plugin/blob/master/go/src/CustomGoPlugin.go) sample as the source for our plugin as shown: + + ``` + mkdir -p plugins + cd plugins + go mod init testplugin + go mod edit -go $(go mod edit -json go.mod | jq -r .Go) + wget -q https://raw.githubusercontent.com/TykTechnologies/custom-go-plugin/refs/heads/master/go/src/CustomGoPlugin.go + cd - + ``` + + The following snippet provides you with a way to get the exact Go version used by Gateway from it's [go.mod](https://github.com/TykTechnologies/tyk/blob/release-5.3.6/go.mod#L3) file: + + - `go mod edit -json go.mod | jq -r .Go` (e.g. `1.22.7`) + + This should be used to ensure the version matches between gateway and the plugin. + + To summarize what was done: + + 1. We created a plugins folder and initialzed a `go` project using `go mod` command. + 2. Set the Go version of `go.mod` to match the one set in the Gateway. + 3. Initialzied the project with sample plugin `go` code. + + At this point, we don't have a *Go workspace* but we will create one next so that we can effectively share the Gateway dependency across Go modules. + +3. **Creating the Go workspace** + + To set up the Go workspace, start in the directory that contains the Gateway and the Plugins folder. You'll first, create the `go.work` file to set up your Go workspace, and include the `tyk-release-5.3.6` and `plugins` folders. Then, navigate to the plugins folder to fetch the Gateway dependency at the exact commit hash and run `go mod tidy` to ensure dependencies are up to date. + + Follow these commands: + + ``` + go work init ./tyk-release-5.3.6 + go work use ./plugins + commit_hash=$(cd tyk-release-5.3.6 && git rev-parse HEAD) + cd plugins && go get github.com/TykTechnologies/tyk@${commit_hash} && go mod tidy && cd - + ``` + + The following snippet provides you to get the commit hash exactly, so it can be used with `go get`. + + - `git rev-parse HEAD` + + The Go workspace file (`go.work`) should look like this: + + ``` + go 1.22.7 + + use ( + ./plugins + ./tyk-release-5.3.6 + ) + ``` + +4. **Building and validating the plugin** + + Now that your *Go workspace* is ready, you can build your plugin as follows: + + ``` + cd tyk-release-5.3.6 && go build -tags=goplugin -trimpath . && cd - + cd plugins && go build -trimpath -buildmode=plugin . && cd - + ``` + + These steps build both the Gateway and the plugin. + + You can use the Gateway binary that you just built to test that your new plugin loads into the Gateway without having to configure and then make a request to an API using this command: + + ``` + ./tyk-release-5.3.6/tyk plugin load -f plugins/testplugin.so -s AuthCheck + ``` + + You should see an output similar to: + + ``` + time="Oct 14 13:39:55" level=info msg="--- Go custom plugin init success! ---- " + [file=plugins/testplugin.so, symbol=AuthCheck] loaded ok, got 0x76e1aeb52140 + ``` + + The log shows that the plugin has correctly loaded into the Gateway and that its `init` function has been successfully invoked. + +5. **Summary** + + In the preceding steps we have put together an end-to-end build environment for both the Gateway and the plugin. Bear in mind that runtime environments may have additional restrictions beyond Go version and build flags to which the plugin developer must pay attention. + + Compatibility in general is a big concern when working with Go plugins: as the plugins are tightly coupled to the Gateway, consideration must always be made for the build restrictions enforced by environment and configuration options. + + Continue with [Loading Go Plugins into Tyk](/api-management/plugins/golang#loading-custom-go-plugins-into-tyk). + +### Debugging Golang Plugins + +Plugins are native Go code compiled to a binary shared object file. The code may depend on `cgo` and require libraries like `libc` provided by the runtime environment. The following are some debugging steps for diagnosing issues arising from using plugins. + +#### Warnings + +The [Plugin package - Warnings](https://pkg.go.dev/plugin#hdr-Warnings) section in the Go documentation outlines several requirements which can't be ignored when working with plugins. The most important restriction is the following: + +> Runtime crashes are likely to occur unless all parts of the program (the application and all its plugins) are compiled using exactly the same version of the toolchain, the same build tags, and the same values of certain flags and environment variables. + +#### Using Incorrect Build Flags + +When working with Go plugins, it's easy to miss the restriction that the plugin at the very least must be built with the same Go version, and the same flags (notably `-trimpath`) as the Tyk Gateway on which it is to be used. + +If you miss an argument (for example `-trimpath`) when building the plugin, the Gateway will report an error when your API attempts to load the plugin, for example: + +``` +task: [test] cd tyk-release-5.3.6 && go build -tags=goplugin -trimpath . +task: [test] cd plugins && go build -buildmode=plugin . +task: [test] ./tyk-release-5.3.6/tyk plugin load -f plugins/testplugin.so -s AuthCheck +tyk: error: unexpected error: plugin.Open("plugins/testplugin"): plugin was built with a different version of package internal/goarch, try --help +``` + +Usually when the error hints at a standard library package, the build flags between the Gateway and plugin binaries don't match. + +Other error messages may be reported, depending on what triggered the issue. For example, if you omitted `-race` in the plugin but the gateway was built with `-race`, the following error will be reported: + +``` +plugin was built with a different version of package runtime/internal/sys, try --help +``` + +Strictly speaking: + +- Build flags like `-trimpath`, `-race` need to match. +- Go toolchain / build env needs to be exactly the same. +- For cross compilation you must use the same `CC` value for the build (CGO). +- `CGO_ENABLED=1`, `GOOS`, `GOARCH` must match with runtime. + +When something is off, you can check what is different by using the `go version -m` command for the Gateway (`go version -m tyk`) and plugin (`go version -m plugin.so`). Inspecting and comparing the output of `build` tokens usually yields the difference that caused the compatibility issue. + +#### Plugin Compatibility Issues + +Below are some common situations where dependencies might cause issues: + +- The `Gateway` has a dependency without a `go.mod` file, but the plugin needs to use it. +- Both the `Gateway` and the plugin share a dependency. In this case, the plugin must use the exact same version as the `Gateway`. +- The plugin requires a different version of a shared dependency. + +Here’s how to handle each case: + +**Case 1: Gateway dependency lacks `go.mod`** + +- The plugin depends on the `Gateway`, which uses dependency *A*. +- *A* doesn’t have a `go.mod` file, so a pseudo version is generated during the build. +- Result: The build completes, but the plugin fails to load due to a version mismatch. + +**Solution:** Update the code to remove dependency *A*, or use a version of *A* that includes a `go.mod` file. + +**Case 2: Shared dependency with version matching** + +- The plugin and `Gateway` share a dependency, and this dependency includes a `go.mod` file. +- The version matches, and the dependency is promoted to *direct* in `go.mod`. +- Outcome: You’ll need to keep this dependency version in sync with the `Gateway`. + +**Case 3: Plugin requires a different version of a shared dependency** + +- The plugin and `Gateway` share a dependency, but the plugin needs a different version. +- If the other version is a major release (e.g., `/v4`), it’s treated as a separate package, allowing both versions to coexist. +- If it’s just a minor/patch difference, the plugin will likely fail to load due to a version conflict. + +**Recommendation:** For best results, use Go package versions that follow the Go module versioning (metaversion). However, keep in mind that many `Gateway` dependencies use basic `v1` semantic versioning, which doesn’t always enforce strict versioned import paths. + +#### List plugin symbols + +Sometimes it's useful to list symbols from a plugin. For example, we can list the symbols as they are compiled into our testplugin: + +``` +# nm -gD testplugin.so | grep testplugin +00000000014db4b0 R go:link.pkghashbytes.testplugin +000000000170f7d0 D go:link.pkghash.testplugin +000000000130f5e0 T testplugin.AddFooBarHeader +000000000130f900 T testplugin.AddFooBarHeader.deferwrap1 +000000000130f980 T testplugin.AuthCheck +0000000001310100 T testplugin.AuthCheck.deferwrap1 +000000000130f540 T testplugin.init +0000000001310ce0 T testplugin.init.0 +0000000001ce9580 D testplugin..inittask +0000000001310480 T testplugin.InjectConfigData +0000000001310180 T testplugin.InjectMetadata +0000000001d2a3e0 B testplugin.logger +0000000001310cc0 T testplugin.main +0000000001310820 T testplugin.MakeOutboundCall +0000000001310c40 T testplugin.MakeOutboundCall.deferwrap1 +``` + +This command prints other symbols that are part of the binary. In the worst case, a build compatibility issue may cause a crash in the Gateway due to an unrecoverable error and this can be used to further debug the binaries produced. + +A very basic check to ensure Gateway/plugin compatibility is using the built in `go version -m `: + +``` +[output truncated] + build -buildmode=exe + build -compiler=gc + build -race=true + build -tags=goplugin + build -trimpath=true + build CGO_ENABLED=1 + build GOARCH=amd64 + build GOOS=linux + build GOAMD64=v1 + build vcs=git + build vcs.revision=1db1935d899296c91a55ba528e7b653aec02883b + build vcs.time=2024-09-24T12:54:26Z + build vcs.modified=false +``` + +These options should match between the Gateway binary and the plugin. You can use the command for both binaries and then compare the outputs. + + +## Writing Custom Go Plugins + +Tyk's custom Go plugin middleware is very powerful as it provides you with access to different data types and functionality as explained in this section. + +Golang plugins are a very flexible and powerful way to extend the functionality of Tyk and uses the native Golang plugins API (see [go pkg/plugin docs](https://golang.org/pkg/plugin) for more details). + +Custom Go plugins can access various data objects relating to the API request: + +- [session](/api-management/plugins/golang#accessing-the-session-object): the key session object provided by the client when making the API request +- [API definition](/api-management/plugins/golang#accessing-the-api-definition): the Tyk OAS or Tyk Classic API definition for the requested API + +Custom Go plugins can also [terminate the request](/api-management/plugins/golang#terminating-the-request) and stop further processing of the API request such that it is not sent to the upstream service. + +For more resources for writing plugins, please visit our [Plugin Hub](/api-management/plugins/overview#plugins-hub). +To see an example of a Go plugin, please visit our [Go plugin examples](/api-management/plugins/golang#example-custom-go-plugins) page. + +### Accessing the internal state of a custom plugin + +A Golang plugin can be treated as a normal Golang package but: + +- the package name is always `"main"` and this package cannot be imported +- this package loads at run-time by Tyk and loads after all other Golang packages +- this package has to have an empty `func main() {}`. + +A Go plugin can have a declared `func init()` and it gets called only once (when Tyk loads this plugin for the first time for an API). + +It is possible to create structures or open connections to 3d party services/storage and then share them within every call and export the function in your Golang plugin. + +For example, here is an example of a Tyk Golang plugin with a simple hit counter: + +```go {linenos=true, linenostart=1} +package main + +import ( + "encoding/json" + "net/http" + "sync" + + "github.com/TykTechnologies/tyk/ctx" + "github.com/TykTechnologies/tyk/log" + "github.com/TykTechnologies/tyk/user" +) + +var logger = log.Get() + +// plugin exported functionality +func MyProcessRequest(rw http.ResponseWriter, r *http.Request) { + endPoint := r.Method + " " + r.URL.Path + logger.Info("Custom middleware, new hit:", endPoint) + + hitCounter := recordHit(endPoint) + logger.Debug("New hit counter value:", hitCounter) + + if hitCounter > 100 { + logger.Warning("Hit counter to high") + } + + reply := myReply{ + Session: ctx.GetSession(r), + Endpoint: endPoint, + HitCounter: hitCounter, + } + + jsonData, err := json.Marshal(reply) + if err != nil { + logger.Error(err.Error()) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + rw.Write(jsonData) +} + +// called once plugin is loaded, this is where we put all initialisation work for plugin +// i.e. setting exported functions, setting up connection pool to storage and etc. +func init() { + hitCounter = make(map[string]uint64) +} + +// plugin internal state and implementation +var ( + hitCounter map[string]uint64 + hitCounterMu sync.Mutex +) + +func recordHit(endpoint string) uint64 { + hitCounterMu.Lock() + defer hitCounterMu.Unlock() + hitCounter[endpoint]++ + return hitCounter[endpoint] +} + +type myReply struct { + Session *user.SessionState `json:"session"` + Endpoint string `json:"endpoint"` + HitCounter uint64 `json:"hit_counter"` +} + +func main() {} +``` + +Here we see how the internal state of the Golang plugin is used by the exported function `MyProcessRequest` (the one we set in the API spec in the `"custom_middleware"` section). The map `hitCounter` is used to send internal state and count hits to different endpoints. Then our exported Golang plugin function sends an HTTP reply with endpoint hit statistics. + +### Accessing the API definition + +When Tyk passes a request to your plugin, the API definition is made available as part of the request context. + + + +The API definition is accessed differently for Tyk OAS APIs and Tyk Classic APIs, as indicated in the following sections. If you use the wrong call for your API type, it will return `nil`. + + + +#### Working with Tyk OAS APIs + +The API definition can be accessed as follows: + +```go +package main + +import ( + "fmt" + "net/http" + + "github.com/TykTechnologies/tyk/ctx" +) + +func MyPluginFunction(w http.ResponseWriter, r *http.Request) { + oas := ctx.GetOASDefinition(r) + fmt.Println("OAS doc title is", oas.Info.Title) +} + +func main() {} +``` + +The invocation of `ctx.GetOASDefinition(r)` returns an `OAS` object containing the Tyk OAS API definition. +The Go data structure can be found [here](https://github.com/TykTechnologies/tyk/blob/master/apidef/oas/oas.go#L28). + +#### Working with Tyk Classic APIs + +The API definition can be accessed as follows: + +```go +package main + +import ( + "fmt" + "net/http" + + "github.com/TykTechnologies/tyk/ctx" +) + +func MyPluginFunction(w http.ResponseWriter, r *http.Request) { + apidef := ctx.GetDefinition(r) + fmt.Println("API name is", apidef.Name) +} + +func main() {} +``` + +The invocation of `ctx.GetDefinition(r)` returns an APIDefinition object containing the Tyk Classic API Definition. +The Go data structure can be found [here](https://github.com/TykTechnologies/tyk/blob/master/apidef/api_definitions.go#L583). + +### Accessing the session object + +When Tyk passes a request to your plugin, the key session object is made available as part of the request context. This can be accessed as follows: + +```go +package main +import ( + "fmt" + "net/http" + "github.com/TykTechnologies/tyk/ctx" +) +func main() {} +func MyPluginFunction(w http.ResponseWriter, r *http.Request) { + session := ctx.GetSession(r) + fmt.Println("Developer ID:", session.MetaData["tyk_developer_id"] + fmt.Println("Developer Email:", session.MetaData["tyk_developer_email"] +} +``` + + + +Tyk Gateway sets the session in the [Authentication layer](/api-management/traffic-transformation#request-middleware-chain) of the middleware chain. Because of this, the session object does not exist until the middleware chain runs after the authentication middleware. If you call `ctx.GetSession` inside a custom auth plugin, it will always return an empty object. + + + +The invocation of `ctx.GetSession(r)` returns an SessionState object. +The Go data structure can be found [here](https://github.com/TykTechnologies/tyk/blob/master/user/session.go#L106). + +Here is an [example](https://github.com/TykTechnologies/custom-plugin-examples/blob/master/plugins/go-auth-multiple_hook_example/main.go#L135) custom Go plugin that makes use of the session object. + +### Terminating the request + +You can terminate the request within your custom Go plugin and provide an HTTP response to the originating client, such that the plugin behaves similarly to a [virtual endpoint](/api-management/traffic-transformation/virtual-endpoints). + +- the HTTP request processing is stopped and other middleware in the chain won't be used +- the HTTP request round-trip to the upstream target won't happen +- analytics records will still be created and sent to the analytics processing flow + +This [example](/api-management/plugins/golang#using-a-custom-go-plugin-as-a-virtual-endpoint) demonstrates a custom Go plugin configured as a virtual endpoint. + +### Logging from a custom plugin + +Your plugin can write log entries to Tyk's logging system. + +To do so you just need to import the package `"github.com/TykTechnologies/tyk/log"` and use the exported public method `Get()`: + +```go {linenos=true, linenostart=1} +package main + +import ( + "net/http" + + "github.com/TykTechnologies/tyk/log" +) + +var logger = log.Get() + +// AddFooBarHeader adds custom "Foo: Bar" header to the request +func AddFooBarHeader(rw http.ResponseWriter, r *http.Request) { + logger.Info("Processing HTTP request in Golang plugin!!") + r.Header.Add("Foo", "Bar") +} + +func main() {} +``` + +#### Monitoring instrumentation for custom plugins + +All custom middleware implemented as Golang plugins support Tyk's current built in instrumentation. + +The format for an event name with metadata is: `"GoPluginMiddleware:" + Path + ":" + SymbolName`, e.g., for our example, the event name will be: + +```text +"GoPluginMiddleware:/tmp/AddFooBarHeader.so:AddFooBarHeader" +``` + +The format for a metric with execution time (in nanoseconds) will have the same format but with the `.exec_time` suffix: + +```text +"GoPluginMiddleware:/tmp/AddFooBarHeader.so:AddFooBarHeader.exec_time" +``` + +### Creating a custom response plugin + +As explained [here](/api-management/plugins/plugin-types#response-plugins), you can register a custom Go plugin to be triggered in the response middleware chain. You must configure the `driver` field to `goplugin` in the API definition when registering the plugin. + +#### Response plugin method signature + +To write a response plugin in Go you need it to have a method signature as in the example below i.e. `func(http.ResponseWriter, *http.Response, *http.Request)`. +You can then access and modify any part of the request or response. User session and API definition data can be accessed as with other Go plugin hook types. + +```go +package main + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + +) + +// MyPluginResponse intercepts response from upstream +func MyPluginResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) { + // add a header to our response object + res.Header.Add("X-Response-Added", "resp-added") + + // overwrite our response body + var buf bytes.Buffer + buf.Write([]byte(`{"message":"Hi! I'm a response plugin"}`)) + res.Body = ioutil.NopCloser(&buf) + +} + +func main() {} +``` + +## Plugin compiler + +Tyk provides a Plugin Compiler tool that will create a file that can be [loaded into Tyk](/api-management/plugins/golang#loading-custom-go-plugins-into-tyk) to implement your desired custom logic. + + + +The plugin compiler is not supported on Ubuntu 16.04 (Xenial Xerus) as it uses glibc 2.23 which is incompatible with our standard build environment. If you absolutely must have Go plugin support on Xenial, please contact Tyk support. + + + + + +### Compiler options + +Most of the following arguments are applied only to developer flows. These aid development and testing purposes, and support of these varies across releases, due to changes in the Go ecosystem. + +The latest plugin compiler implements the following options: + +- `plugin_name`: output root file name (for example `plugin.so`) +- `build_id`: [optional] provides build uniqueness +- `GOOS`: [optional] override of GOOS (add `-e GOOS=linux`) +- `GOARCH`: [optional] override of GOARCH (add `-e GOARCH=amd64`) + +By default, if `build_id` is not provided, the gateway will not allow the plugin to be loaded twice. This is a restriction of the Go plugins standard library implementation. As long as the builds are made with a unique `build_id`, the same plugin can be loaded multiple times. + +When you provide a unique `build_id` argument, that also enables hot-reload compatibility of your `.so` plugin build, so that you would not need to restart the gateway, only reload it. + +- before 5.1: the plugin would be built in a filesystem path based on `build_id` +- since 5.2.4: the plugin compiler adjusts the Go module in use for the plugin. + +As the plugins are built with `-trimpath`, to omit local filesystem path details and improve plugin compatibility, the plugin compiler relies on the Go module itself to ensure each plugin build is unique. It modifies the plugin build `go.mod` file and imports to ensure a unique build. + +- [plugin package: Warnings](https://pkg.go.dev/plugin#hdr-Warnings) +- [golang#29525 - plugin: cannot open the same plugin with different names](https://github.com/golang/go/issues/29525) + +### Output filename + +Since v4.1.0 the plugin compiler has automatically added the following suffixes to the root filename provided in the `plugin_name` argument: + +- `{Gw-version}`: the Tyk Gateway version, for example, `v5.3.0` +- `{OS}`: the target operating system, for example `linux` +- `{arch}`: the target CPU architecture, for example, `arm64` + +Thus, if `plugin_name` is set to `plugin.so` then given these example values the output file will be: `plugin_v5.3.0_linux_arm64.so`. + +This enables you to have one directory with multiple versions of the same plugin targeting different Gateway versions. + +#### Cross-compiling for different architectures and operating systems + +The Tyk Go Plugin Compiler can generate output for different architectures and operating systems from the one in which the compiler is run (cross-compiling). When you do this, the output filename will be suffixed with the target OS and architecture. + +You simply provide the target `GOOS` and `GOARCH` arguments to the plugin compiler, for example: + +```yaml +docker run --rm -v `pwd`:/plugin-source \ + --platform=linux/amd64 \ + tykio/tyk-plugin-compiler:v5.2.1 plugin.so $build_id linux arm64 +``` + +This command will cross-compile your plugin for a `linux/arm64` architecture. It will produce an output file named `plugin_v5.2.1_linux_arm64.so`. + + + +If you are using the plugin compiler on MacOS, the docker run argument `--platform=linux/amd64` is necessary. The plugin compiler is a cross-build environment implemented with `linux/amd64`. + + + +### Experimental options + +The plugin compiler also supports a set of environment variables being passed: + +- `DEBUG=1`: enables debug output from the plugin compiler process. +- `GO_TIDY=1`: runs go mod tidy to resolve possible dependency issues. +- `GO_GET=1`: invokes go get to retrieve the exact Tyk gateway dependency. + +These environment options are only available in the latest gateway and plugin compiler versions. +They are unsupported and are provided to aid development and testing workflows. + +## Loading Custom Go Plugins into Tyk + +For development purposes, we are going to load the plugin from local file storage. For production, you can use [bundles](#loading-a-tyk-golang-plugin-from-a-bundle) to deploy plugins to multiple gateways. + +In this example we are using a Tyk Classic API. In the API definition find the `custom_middleware` section and make it look similar to the snippet below. Tyk Dashboard users should use RAW API Editor to access this section. + +```json +"custom_middleware": { + "pre": [], + "post_key_auth": [], + "auth_check": {}, + "post": [ + { + "name": "AddFooBarHeader", + "path": "/plugin.so" + } + ], + "driver": "goplugin" +} +``` + +Here we have: + +- `driver` - Set this to `goplugin` (no value created for this plugin) which says to Tyk that this custom middleware is a Golang native plugin. +- `post` - This is the hook name. We use middleware with hook type `post` because we want this custom middleware to process the request right before it is passed to the upstream target (we will look at other types later). +- `post.name` - is your function name from the Go plugin project. +- `post.path` - is the full or relative (to the Tyk binary) path to the built plugin file (`.so`). Make sure Tyk has read access to this file. + +Also, let's set fields `"use_keyless": true` and `"target_url": "http://httpbin.org/"` - for testing purposes. We will test what request arrives to our upstream target and `httpbin.org` is a perfect fit for that. + +The API needs to be reloaded after that change (this happens automatically when you save the updated API in the Dashboard). + +Now your API with its Golang plugin is ready to process traffic: + +```bash +# curl http://localhost:8181/my_api_name/get +{ + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Foo": "Bar", + "Host": "httpbin.org", + "User-Agent": "curl/7.54.0" + }, + "url": "https://httpbin.org/get" +} +``` + +We see that the upstream target has received the header `"Foo": "Bar"` which was added by our custom middleware implemented as a native Golang plugin in Tyk. + +### Updating the plugin + +Loading an updated version of your plugin requires one of the following actions: + +- An API reload with a NEW path or file name of your `.so` file with the plugin. You will need to update the API spec section `"custom_middleware"`, specifying a new value for the `"path"` field of the plugin you need to reload. +- Tyk main process reload. This will force a reload of all Golang plugins for all APIs. + +If a plugin is loaded as a bundle and you need to update it you will need to update your API spec with a new `.zip` file name in the `"custom_middleware_bundle"` field. Make sure the new `.zip` file is uploaded and available via the bundle HTTP endpoint before you update your API spec. + +### Loading a Tyk Golang plugin from a bundle + +Currently we have loaded Golang plugins only directly from the file system. However, when you have multiple gateway instances, you need a more dynamic way to load plugins. Tyk offer bundle instrumentation [Plugin Bundles](/api-management/plugins/overview#plugin-bundles). Using the bundle command creates an archive with your plugin, which you can deploy to the HTTP server (or AWS S3) and then your plugins will be fetched and loaded from that HTTP endpoint. + +You will need to set in `tyk.conf` these two fields: + +- `"enable_bundle_downloader": true` - enables the plugin bundles downloader +- `"bundle_base_url": "http://mybundles:8000/abc"` - specifies the base URL with the HTTP server where you place your bundles with Golang plugins (this endpoint must be reachable by the gateway) + +Also, you will need to specify the following field in your API spec: + +`"custom_middleware_bundle"` - here you place your filename with the bundle (`.zip` archive) to be fetched from the HTTP endpoint you specified in your `tyk.conf` parameter `"bundle_base_url"` + +To load a plugin, your API spec should set this field like so: + +```json +"custom_middleware_bundle": "FooBarBundle.zip" +``` + +Let's look at `FooBarBundle.zip` contents. It is just a ZIP archive with two files archived inside: + +- `AddFooBarHeader.so` - this is our Golang plugin +- `manifest.json` - this is a special file with meta information used by Tyk's bundle loader + +The contents of `manifest.json`: + +```yaml +{ + "file_list": [ + "AddFooBarHeader.so" + ], + "custom_middleware": { + "post": [ + { + "name": "AddFooBarHeader", + "path": "AddFooBarHeader.so" + } + ], + "driver": "goplugin" + }, + + ... +} +``` + +Here we see: + +- field `"custom_middleware"` with exactly the same structure we used to specify `"custom_middleware"` in API spec without bundle +- field `"path"` in section `"post"` now contains just a file name without any path. This field specifies `.so` filename placed in a ZIP archive with the bundle (remember how we specified `"custom_middleware_bundle": "FooBarBundle.zip"`). + +## Using custom Go plugins with Tyk Cloud + +The following supporting resources are provided for developing plugins on Tyk Cloud: + +- [Enabling Plugins On The Control Plane](/tyk-cloud/using-plugins) +- [Uploading Your Plugin Bundle To S3 Bucket](/tyk-cloud/using-plugins#uploading-your-bundle) + +## Example custom Go plugins + +This document provides a working example for providing specific functionality with a custom Go plugin. + +For more resources for writing plugins, please visit our [Plugin Hub](/api-management/plugins/overview#plugins-hub). + +### Using a custom Go plugin as a virtual endpoint + +It is possible to send a response from the Golang plugin custom middleware. In the case that the HTTP response was sent: + +- The HTTP request processing is stopped and other middleware in the chain won't be used. +- The HTTP request round-trip to the upstream target won't happen +- Analytics records will still be created and sent to the analytics processing flow. + +Let's look at an example of how to send an HTTP response from the Tyk Golang plugin. Imagine that we need middleware which would send JSON with the current time if the request contains the parameter `get_time=1` in the request query string: + +```go +package main + +import ( + "encoding/json" + "net/http" + "time" +) + +func SendCurrentTime(rw http.ResponseWriter, r *http.Request) { + // check if we don't need to send reply + if r.URL.Query().Get("get_time") != "1" { + // allow request to be processed and sent to upstream + return + } + + //Prepare data to send + replyData := map[string]interface{}{ + "current_time": time.Now(), + } + + jsonData, err := json.Marshal(replyData) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + //Send HTTP response from the Golang plugin + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + rw.Write(jsonData) +} + +func main() {} +``` + +Let's build the plugin by running this command in the plugin project folder: + +```bash +go build -trimpath -buildmode=plugin -o /tmp/SendCurrentTime.so +``` + +Then let's edit the API spec to use this custom middleware: + +```json +"custom_middleware": { + "pre": [ + { + "name": "SendCurrentTime", + "path": "/tmp/SendCurrentTime.so" + } + ], + "post_key_auth": [], + "auth_check": {}, + "post": [], + "driver": "goplugin" +} +``` + +Let's check that we still perform a round trip to the upstream target if the request query string parameter `get_time` is not set: + +```bash +# curl http://localhost:8181/my_api_name/get +{ + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Host": "httpbin.org", + "User-Agent": "curl/7.54.0" + }, + "url": "https://httpbin.org/get" +} +``` + +Now let's check if our Golang plugin sends an HTTP 200 response (with JSON containing current time) when we set `get_time=1` query string parameter: + +```bash +# curl http://localhost:8181/my_api_name/get?get_time=1 +{"current_time":"2019-09-11T23:44:10.040878-04:00"} +``` + +Here we see that: + +- We've got an HTTP 200 response code. +- The response body has a JSON payload with the current time. +- The upstream target was not reached. Our Tyk Golang plugin served this request and stopped processing after the response was sent. + +### Performing custom authentication with a Golang plugin + +You can implement your own authentication method, using a Golang plugin and custom `"auth_check"` middleware. Ensure you set the two fields in Post Authentication Hook. + +Let's have a look at the code example. Imagine we need to implement a very trivial authentication method when only one key is supported (in the real world you would want to store your keys in some storage or have some more complex logic). + +```go +package main + +import ( + "net/http" + + "github.com/TykTechnologies/tyk/ctx" + "github.com/TykTechnologies/tyk/headers" + "github.com/TykTechnologies/tyk/user" +) + +func getSessionByKey(key string) *user.SessionState { + //Here goes our logic to check if the provided API key is valid and appropriate key session can be retrieved + + // perform auth (only one token "abc" is allowed) + if key != "abc" { + return nil + } + + // return session + return &user.SessionState{ + OrgID: "default", + Alias: "abc-session", + } +} + +func MyPluginAuthCheck(rw http.ResponseWriter, r *http.Request) { + //Try to get a session by API key + key := r.Header.Get(headers.Authorization) + session := getSessionByKey(key) + if session == nil { + // auth failed, reply with 403 + rw.WriteHeader(http.StatusForbidden) + return + } + + // auth was successful, add the session to the request's context so other middleware can use it + ctx.SetSession(r, session, true) + + // if compiling on a version older than 4.0.1, use this instead + // ctx.SetSession(r, session, key, true) +} + +func main() {} +``` + +A couple of notes about this code: + +- the package `"github.com/TykTechnologies/tyk/ctx"` is used to set a session in the request context - this is something `"auth_check"`-type custom middleware is responsible for. +- the package `"github.com/TykTechnologies/tyk/user"` is used to operate with Tyk's key session structure. +- our Golang plugin sends a 403 HTTP response if authentication fails. +- our Golang plugin just adds a session to the request context and returns if authentication was successful. + +Let's build the plugin by running the following command in the folder containing your plugin project: + +```bash +go build -trimpath -buildmode=plugin -o /tmp/MyPluginAuthCheck.so +``` + +Now let's check if our custom authentication works as expected (only one key `"abc"` should work). + +Authentication will fail with the wrong API key: + +```bash +# curl -v -H "Authorization: xyz" http://localhost:8181/my_api_name/get +* Trying ::1... +* TCP_NODELAY set +* Connected to localhost (::1) port 8181 (#0) +> GET /my_api_name/get HTTP/1.1 +> Host: localhost:8181 +> User-Agent: curl/7.54.0 +> Accept: */* +> Authorization: xyz +> +< HTTP/1.1 403 Forbidden +< Date: Wed, 11 Sep 2019 04:31:34 GMT +< Content-Length: 0 +< +* Connection #0 to host localhost left intact +``` + +Here we see that our custom middleware replied with a 403 response and request processing was stopped at this point. + +Authentication successful with the right API key: + +```bash +# curl -v -H "Authorization: abc" http://localhost:8181/my_api_name/get +* Trying ::1... +* TCP_NODELAY set +* Connected to localhost (::1) port 8181 (#0) +> GET /my_api_name/get HTTP/1.1 +> Host: localhost:8181 +> User-Agent: curl/7.54.0 +> Accept: */* +> Authorization: abc +> +< HTTP/1.1 200 OK +< Content-Type: application/json +< Date: Wed, 11 Sep 2019 04:31:39 GMT +< Content-Length: 257 +< +{ + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Authorization": "abc", + "Host": "httpbin.org", + "User-Agent": "curl/7.54.0" + }, + "url": "https://httpbin.org/get" +} +* Connection #0 to host localhost left intact +``` + +Here we see that our custom middleware successfully authenticated the request and we received a reply from the upstream target. + +## Upgrading your Tyk Gateway + +When upgrading your Tyk Gateway deployment, you need to re-compile your plugin with the new version. At the moment of loading a plugin, the Gateway will try to find a plugin with the name provided in the API definition. If none is found then it will fall back to search the plugin file with the name: `{plugin-name}_{Gw-version}_{OS}_{arch}.so`. + +Since Tyk v4.1.0, the compiler [automatically](/api-management/plugins/golang#output-filename) creates plugin files following this convention so when you upgrade, say from Tyk v5.2.5 to v5.3.0 you only need to have the plugins compiled for v5.3.0 before performing the upgrade. + +This diagram shows how every Tyk Gateway will search and load the plugin binary that it is compatible with. +APIs Menu diff --git a/api-management/plugins/javascript.mdx b/api-management/plugins/javascript.mdx new file mode 100644 index 0000000000..4e85877dc1 --- /dev/null +++ b/api-management/plugins/javascript.mdx @@ -0,0 +1,743 @@ +--- +title: "Javascript Plugins" +description: "Learn how to script custom middleware, dynamic event handlers, and virtual endpoints in Tyk using JavaScript" +sidebarTitle: "JavaScript Plugins" +--- + +## Introduction + +There are three middleware components that can be scripted with Tyk: + +1. **Custom JavaScript plugins**: These execute either *pre* or *post* validation. A *pre* middleware component will execute before any session validation or token validation has taken place, while a *post* middleware component will execute after the request has been passed through all checks and is ready to be proxied upstream. + +2. **Dynamic event handlers**: These components fire on certain API events (see the event handlers section), these are fired Async and do not have a cooldown timer. These are documented [here](/api-management/gateway-events#set-up-a-webhook-event-handler-in-the-tyk-oas-api-definition). + +3. **Virtual endpoints**: These are powerful programmable middleware invoked towards the end of the request processing chain. Unlike the custom JavaScript plugins, the virtual endpoint terminates the request. These are documented [here](/api-management/traffic-transformation/virtual-endpoints). + +The JavaScript (JS) [scripting guide](/api-management/plugins/javascript#using-javascript-with-tyk) provides details of how to access dynamic data (such as the key session object) from your JS functions. Combining these resources provides you with a powerful set of tools for shaping and structuring inbound traffic to your API. + +### Declared plugin functions + +JavaScript functions are available globally in the same namespace. So, if you include two or more JSVM plugins that call the same function, the last declared plugin implementation of the function will be returned. + +### Enabling the JavaScript Virtual Machine (JSVM) + +The JavaScript Virtual Machine (JSVM) provided in the Gateway is a traditional ECMAScript5 compatible environment. + +Before you can use JavaScript customization in any component you will need to enable the JSVM. + +You do this by setting `enable_jsvm` to `true` in your `tyk.conf` [file](/tyk-oss-gateway/configuration#enable_jsvm). + +### Installing JavaScript middleware + +Installing middleware is different for different Tyk deployments, for example, in Tyk OSS it is possible to directly specify a path to a file in the API Definition, while in Tyk Self-Managed, we recommend using a directory-based loader. + +We've provided the following guides: + +- [Tyk OSS](/api-management/plugins/javascript#installing-middleware-on-tyk-oss) +- [Tyk Self-Managed](/api-management/plugins/javascript#installing-middleware-on-tyk-self-managed) +- [Tyk Hybrid](/api-management/plugins/javascript#installing-middleware-on-tyk-hybrid) + + + + +## Using JavaScript with Tyk + +Tyk's JavaScript Virtual Machine (JSVM) provides a serverless compute function that allows for the execution of custom logic directly within the gateway itself. This can be accessed from [multiple locations](/api-management/plugins/javascript#) in the API processing chain and allows significant customization and optimization of your request handling. + +In this guide we will cover the features and resources available to you when creating custom functions, highlighting where there are limitations for the different middleware stages. + +### Scripting basics + +Here we cover various facets that you need to be aware of when creating custom functions for Tyk. + +#### Accessing external and dynamic data + +JS functions can be given access to external data objects relating to the API request. These allow for the modification of both the request itself and the session: + +- `request`: an [object](/api-management/plugins/javascript#the-request-object) describing the API request that invoked the middleware +- `session`: the key session [object](/api-management/plugins/javascript#the-session-object) provided by the client when making the API request +- `config`: an [object](/api-management/plugins/javascript#the-config-object) containing fields from the API definition + + + + + There are other ways of accessing and editing a session object using the [Tyk JavaScript API functions](/api-management/plugins/javascript#working-with-the-key-session-object). + + + +#### Creating a middleware component + +Tyk injects a `TykJS` namespace into the JSVM, which can be used to initialise a new middleware component. The JS for each middleware component should be in its own `*.js` file. + +You create a middleware object by calling the `TykJS.TykMiddleware.NewMiddleware({})` constructor with an empty object and then initialising it with your function using the `NewProcessRequest()` closure syntax. This is where you expose the [external data objects](/api-management/plugins/javascript#accessing-external-and-dynamic-data) to your custom function. + + + +- For Custom JS plugins and Dynamic Event Handlers, the source code filename must match the function name +- Virtual Endpoints do not have this limitation + + + +#### Returning from the middleware + +When returning from the middleware, you provide specific return data depending upon the type of middleware. + +##### Returning from Custom JS plugin + +A custom JS plugin can modify fields in the API request and the session metadata, however this is not performed directly within the JSVM so the required updates must be passed out of the JSVM for Tyk to apply the changes. This is a requirement and omitting them can cause the middleware to fail. + +The JS function must provide the `request` and `session.meta_data` objects in the `ReturnData` as follows: + +```js +return sampleMiddleware.ReturnData(request, session.meta_data); +``` + +Custom JS plugins sit in the [middleware processing chain](/api-management/traffic-transformation#request-middleware-chain) and pass the request onto the next middleware before it is proxied to the upstream. If required, however, a custom JS plugin can terminate the request and provide a custom response to the client if you configure the `ReturnOverrides` in the `request` object, as described [here](/api-management/plugins/javascript#using-returnoverrides). + +##### Returning from Virtual Endpoint + +Unlike custom JS plugins, Virtual Endpoints always [terminate the request](/api-management/traffic-transformation/virtual-endpoints#working) so have a different method of returning from the JS function. + +The function must return a `responseObject`. This is crucial as it determines the HTTP response that will be sent back to the client. The structure of this object is defined to ensure that the virtual endpoint can communicate the necessary response details back to the Tyk Gateway, which then forwards it to the client. + +The `responseObject` has the following structure: + +- `code`: an integer representing the HTTP status code of the response +- `headers`: an object containing key-value pairs representing the HTTP headers of the response +- `body`: a string that represents the body of the response which can be plain text, JSON, or XML, depending on what your API client expects to receive + +You must provide the `responseObject` together with the `session.meta_data` as parameters in a call to `TykJsResponse` as follows: + +```js +return TykJsResponse(responseObject, session.meta_data); +``` + +You can find some examples of how this works [here](/api-management/traffic-transformation/virtual-endpoints#examples). + +### JavaScript resources + +JavaScript (JS) functions have access to a [system API](/api-management/plugins/javascript#javascript-api) and [library of functions](/api-management/plugins/javascript#underscore-js-library). They can also be given access to certain Tyk data objects relating to the API request. + +The system API provides access to resources outside of the JavaScript Virtual Machine sandbox, the ability to make outbound HTTP requests and access to the key management REST API functions. + +#### The `request` object + +The `request` object provides a set of arrays that describe the API request. These can be manipulated and, when changed, will affect the request as it passes through the middleware pipeline. For [virtual endpoints](/api-management/traffic-transformation/virtual-endpoints) the request object has a [different structure](#VirtualEndpoint-Request). + +The structure of the `request` object is: + +```typesecript +class ReturnOverrides { + ResponseCode: number = 200; + ResponseBody: string = ""; + ResponseHeaders: string[] = []; +} + +class Request { + Headers: { [key: string]: string[] } = {}; + SetHeaders: { [key: string]: string } = {}; + DeleteHeaders: string[] = []; + Body: string = ""; + URL: string = ""; + AddParams: { [key: string]: string } = {}; + DeleteParams: string[] = []; + ReturnOverrides: ReturnOverrides = new ReturnOverrides(); + IgnoreBody: boolean = false; + Method: string = ""; + RequestURI: string = ""; + Scheme: string = ""; +} +``` + +{/* ```go +struct { + Headers map[string][]string + SetHeaders map[string]string + DeleteHeaders []string + Body string + URL string + AddParams map[string]string + DeleteParams []string + ReturnOverrides { + ResponseCode: int + ResponseBody: string + ResponseHeaders []string + } + IgnoreBody bool + Method string + RequestURI string + Scheme string +} +``` */} + +- `Headers`: this is an object of string arrays, and represents the current state of the request header; this object cannot be modified directly, but can be used to read header data +- `SetHeaders`: this is a key-value map that will be set in the header when the middleware returns the object; existing headers will be overwritten and new headers will be added +- `DeleteHeaders`: any header name that is in this list will be deleted from the outgoing request; note that `DeleteHeaders` happens before `SetHeaders` +- `Body`: this represents the body of the request, if you modify this field it will overwrite the request +- `URL`: this represents the path portion of the outbound URL, you can modify this to redirect a URL to a different upstream path +- `AddParams`: you can add parameters to your request here, for example internal data headers that are only relevant to your network setup +- `DeleteParams`: these parameters will be removed from the request as they pass through the middleware; note `DeleteParams` happens before `AddParams` +- `ReturnOverrides`: values stored here are used to stop or halt middleware execution and return an error code +- `IgnoreBody`: if this parameter is set to `true`, the original request body will be used; if set to `false` the `Body` field will be used (`false` is the default behavior) +- `Method`: contains the HTTP method (`GET`, `POST`, etc.) +- `RequestURI`: contains the request URI, including the query string, e.g. `/path?key=value` +- `Scheme`: contains the URL scheme, e.g. `http`, `https` + + +##### Using `ReturnOverrides` + +If you configure values in `request.ReturnOverrides` then Tyk will terminate the request and provide a response to the client when the function completes. The request will not be proxied to the upstream. + +The response will use the parameters configured in `ReturnOverrides`: + +- `ResponseCode` +- `ResponseBody` +- `ResponseHeaders` + +In this example, if the condition is met, Tyk will return `HTTP 403 Access Denied` with the custom header `"X-Error":"the-condition"`: + +```js +var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({}); + +testJSVMData.NewProcessRequest(function(request, session, config) { + // Logic to determine if the request should be overridden + if (someCondition) { + request.ReturnOverrides.ResponseCode = 403; + request.ReturnOverrides.ResponseBody = "Access Denied"; + request.ReturnOverrides.headers = {"X-Error": "the-condition"}; + // This stops the request from proceeding to the upstream + } + return testJSVMData.ReturnData(request, session.meta_data); +}); +``` + +##### The virtual endpoint `request` object + + +For [virtual endpoint](/api-management/traffic-transformation/virtual-endpoints) functions the structure of a Javascript `request` object is: + +```typescript +class VirtualEndpointRequest { + Body: string = ""; + Headers: { [key: string]: string[] } = {}; + Params: { [key: string]: string[] } = {}; + Scheme: string = ""; + URL: string = ""; +} +``` + +- `Body`: HTTP request body, e.g. `""` +- `Headers`: HTTP request headers, e.g. `"Accept": ["*/*"]` +- `Params`: Decoded query and form parameters, e.g. `{ "confirm": ["true"], "userId": ["123"] }` +- `Scheme`: The scheme of the URL ( e.g. `http` or `https`) +- `URL`: The full URL of the request, e.g `/vendpoint/anything?user_id=123\u0026confirm=true` + +
+ + + +Each query and form parameter within the request is stored as an array field in the `Params` field of the request object. + +Repeated parameter assignments are appended to the corresponding array. For example, a request against `/vendpoint/anything?user_id[]=123&user_id[]=234` would result in a Javascript request object similar to that shown below: + +```javascript +const httpRequest = { + Headers: { + "Accept": ["*/*"], + "User-Agent": ["curl/8.1.2"] + }, + Body: "", + URL: "/vendpoint/anything?user_id[]=123\u0026user_id[]=234", + Params: { + "user_id[]": ["123", "234"] + }, + Scheme: "http" +}; +``` + + + +#### The `session` object + +Tyk uses an internal [session object](/api-management/access-control/sessions-and-keys/understanding-sessions) to handle the quota, rate limits, access allowances and auth data of a specific key. JS middleware can be granted access to the session object but there is also the option to disable it as deserialising it into the JSVM is computationally expensive and can add latency. Other than the `meta_data` field, the session object itself cannot be directly edited as it is crucial to the correct functioning of Tyk. + +##### Limitations + +- Custom JS plugins at the [pre-](/api-management/plugins/plugin-types#request-plugins) stage do not have access to the session object (as it has not been created yet) +- When scripting for Virtual Endpoints, the `session` data will only be available to the JS function if enabled in the middleware configuration. To enable this, set `requireSession` to `true` in the `virtualEndpoint` object of your Tyk OAS API definition (or `use_session` to `true` in the `virtual` object for Tyk Classic APIs). + +###### Tyk OAS API Definition example: +``` +"virtualEndpoint": { + "enabled": true, + "functionName": "myVirtualEndpoint", + "path": "my_script.js", + "requireSession": true +} +``` +###### Tyk Classic API Definition example: +``` +"extended_paths": { + "virtual": [ + { + "path": "/my-endpoint", + "method": "GET", + "response_function_name": "myVirtualEndpoint", + "function_source_type": "file", + "function_source_uri": "my_script.js", + "use_session": true + } + ] +} +``` + + +##### Sharing data between middleware using the `session` object + +For different middleware to be able to transfer data between each other, the session object makes available a `meta_data` key/value field that is written back to the session store (and can be retrieved by the middleware down the line) - this data is permanent, and can also be retrieved by the REST API from outside of Tyk using the `/tyk/keys/` method. + + + +A new JSVM instance is created for *each* API that is managed. Consequently, inter-API communication is not possible via shared methods, since they have different bounds. However, it *is* possible using the session object if a key is shared across APIs. + + + +#### The `config` object + +The third Tyk data object that is made available to the script running in the JSVM contains data from the API Definition. This is read-only and cannot be modified by the JS function. The structure of this object is: + +- `APIID`: the unique identifier for the API +- `OrgID`: the organization identifier +- `config_data`: custom attributes defined in the API description + +##### Adding custom attributes to the API Definition + +When working with Tyk OAS APIs, you can add custom attributes in the `data` object in the `x-tyk-api-gateway.middleware.global.pluginConfig` section of the API definition, for example: + +```json {linenos=true, linenostart=1} +{ + "x-tyk-api-gateway": { + "middleware": { + "global": { + "pluginConfig": { + "data": { + "enabled": true, + "value": { + "foo": "bar" + } + } + } + } + } + } +} +``` + +When working with Tyk Classic APIs, you simply add the attributes in the `config_data` object in the root of the API definition: + +```json {linenos=true, linenostart=1} +{ + "config_data": { + "foo": "bar" + } +} +``` + +#### Underscore.js Library + +In addition to our Tyk JavaScript API functions, you also have access to all the functions from the [underscore](http://underscorejs.org) library. + +Underscore.js is a JavaScript library that provides a lot of useful functional programming helpers without extending any built-in objects. Underscore provides over 100 functions that support your favorite functional helpers: + +- map +- filter +- invoke + +There are also more specialized goodies, including: + +- function binding +- JavaScript templating +- creating quick indexes +- deep equality testing + +### Example + +In this basic example, we show the creation and initialisation of a middleware object. Note how the three Tyk data objects (`request`, `session`, `config`) are made available to the function and the two objects that are returned from the function (in case the external objects need to be updated). + +```js {linenos=true, linenostart=1} +/* --- sampleMiddleware.js --- */ + +// Create new middleware object +var sampleMiddleware = new TykJS.TykMiddleware.NewMiddleware({}); + +// Initialise the object with your functionality by passing a closure that accepts +// two objects into the NewProcessRequest() function: +sampleMiddleware.NewProcessRequest(function(request, session, config) { + log("This middleware does nothing, but will print this to your terminal.") + + // You MUST return both the request and session metadata + return sampleMiddleware.ReturnData(request, session.meta_data); +}); +``` + +## JavaScript API + +This system API provides access to resources outside of the JavaScript Virtual Machine sandbox, the ability to make outbound HTTP requests and access to the key management REST API functions. + +Embedded JavaScript interpreters offer the bare bones of a scripting language, but not necessarily the functions that you would expect, especially with JavaScript, where objects such as `XMLHttpRequest()` are a given. However, those interfaces are actually provided by the browser / DOM that the script engine are executing in. In a similar vein, we have included a series of functions to the JSVM for convenience and give the interpreter more capability. + +This list is regularly revised and any new suggestions should be made in our [Github issue tracker](https://github.com/TykTechnologies/tyk/issues). + +Below is the list of functions currently provided by Tyk. + +- `log(string)`: Calling `log("this message")` will cause Tyk to log the string to Tyk's default logger output in the form `JSVM Log: this message` as an INFO statement. This function is especially useful for debugging your scripts. It is recommended to put a `log()` call at the end of your middleware and event handler module definitions to indicate on load that they have been loaded successfully - see the [example scripts](https://github.com/TykTechnologies/tyk/tree/master/middleware) in your Tyk installation `middleware` directory for more details. +- `rawlog(string)`: Calling `rawlog("this message")` will cause Tyk to log the string to Tyk's default logger output without any additional formatting, like adding prefix or date. This function can be used if you want to have own log format, and parse it later with custom tooling. +- `b64enc` - Encode string to base64 +- `b64dec` - Decode base64 string +- `TykBatchRequest` this function is similar to `TykMakeHttpRequest` but makes use of Tyk's [batch request feature](/api-management/batch-processing). +- `TykMakeHttpRequest(JSON.stringify(requestObject))`: This method is used to make an HTTP request, requests are encoded as JSON for deserialisation in the min binary and translation to a system HTTP call. The request object has the following structure: + +```js +newRequest = { + "Method": "POST", + "Body": JSON.stringify(event), + "Headers": {}, + "Domain": "http://foo.com", + "Resource": "/event/quotas", + "FormData": {"field": "value"} +}; +``` + + + +If you want to include querystring values, add them as part of the `Domain` property. + + + +Tyk passes a simplified response back which looks like this: + +```go +type TykJSHttpResponse struct { + Code int + Body string + Headers map[string][]string +} +``` + +The response is JSON string encoded, and so will need to be decoded again before it is usable: + +```js +usableResponse = JSON.parse(response); +log("Response code: " + usableResponse.Code); +log("Response body: " + usableResponse.Body); +``` + +This method does not execute asynchronously, so execution will block until a response is received. + +### Working with the key session object + +To work with the key session object, two functions are provided: `TykGetKeyData` and `TykSetKeyData`: + +- `TykGetKeyData(api_key, api_id)`: Use this method to retrieve a [session object](/api-management/access-control/sessions-and-keys/understanding-sessions) for the key and the API provided: + + ```js + // In an event handler, we can get the key idea from the event, and the API ID from the context variable. + var thisSession = JSON.parse(TykGetKeyData(event.EventMetaData.Key, context.APIID)) + log("Expires: " + thisSession.expires) + ``` + +- `TykSetKeyData(api_key, api_id)`: Use this method to write data back into the Tyk session store: + + ```js + // You can modify the object just like with the REST API + thisSession.expires = thisSession.expires + 1000; + + // Use TykSetKeyData to set the key data back in the session store + TykSetKeyData(event.EventMetaData.Key, JSON.stringify(thisSession)); + ``` + +All of these methods are described in functional examples in the Tyk `middleware/` and `event_handlers/` folders. + +## Installing Middleware on Tyk Self-Managed + +In some cases middleware references can't be directly embedded in API Definitions (for example, when using the Tyk Dashboard in an Self-Managed installation). However, there is an easy way to distribute and enable custom middleware for an API in a Tyk node by adding them as a directory structure. + +Tyk will load the middleware plugins dynamically on host-reload without needing a direct reference to them in the API Definition. + +The directory structure should look like this: + +```text +middleware + / {API Id} + / pre + / {middlewareObject1Name}.js + / {middlewareObject2Name}.js + / post + / {middlewareObject1Name}_with_session.js + / {middlewareObject2Name}.js +``` + +Tyk will check for a folder that matches the `API Id` being loaded, and then load the `pre` and `post` middleware from the respective directories. + + + +The filename MUST match the object to be loaded exactly. + + + +If your middleware requires session injection, then append `_with_session` to the filename. + +### Enable the JSVM + +Before you can use Javascript Middleware you will need to enable the JSVM. + +You can do this by setting `enable_jsvm` to `true` in your `tyk.conf` file. + +## Installing Middleware on Tyk Hybrid + +In some cases middleware references can't be directly embedded in API Definitions (for example, when using the dashboard in a Hybrid install). However, there is an easy way to distribute and enable custom middleware for an API on a Tyk node. + +A second method of loading API Definitions in Tyk nodes is to add them as a directory structure in the Tyk node. Tyk will load the middleware plugins dynamically on host-reload without needing a direct reference to them in the API Definition. + +The directory structure looks as follows: + +```text +middleware + / {API Id} + / pre + / {middlewareObject1Name}.js + / {middlewareObject2Name}.js + / post + / {middlewareObject1Name}_with_session.js + / {middlewareObject2Name}.js +``` + +Tyk will check for a folder that matches the `{API Id}` being loaded, and then load the `pre` and `post` middleware from the respective folders. + + + +The filename MUST match the object to be loaded exactly. + + + +If your middleware requires session injection, then append `_with_session` to the filename. + +### Enable the JSVM + +Before you can use Javascript Middleware you will need to enable the JSVM + +You can do this by setting `enable_jsvm` to `true` in your `tyk.conf` file. + +## Installing Middleware on Tyk OSS + +In order to activate middleware when using Tyk OSS or when using a file-based setup, the middleware needs to be registered as part of your API Definition. Registration of middleware components is relatively simple. + + + +It is important that your object names are unique. + + + + + +This functionality may change in subsequent releases. + + + +### Enable the JSVM + +Before you can use Javascript Middleware you will need to enable the JSVM + +You can do this by setting `enable_jsvm` to `true` in your `tyk.conf` file. + +Adding the middleware plugin is as simple as adding it to your definition file in the middleware sections: + +```json +... +"event_handlers": {}, +"custom_middleware": { + "driver": "otto", + "pre": [ + { + "name": "sampleMiddleware", + "path": "middleware/sample.js", + "require_session": false + } + ], + "post": [ + { + "name": "sampleMiddleware", + "path": "middleware/sample.js", + "require_session": false + } + ] +}, +"enable_batch_request_support": false, +... +``` + +As you can see, the parameters are all dynamic, so you will need to ensure that the path to your middleware is correct. The configuration sections are as follows: + +- `pre`: Defines a list of custom middleware objects to run *in order* from top to bottom. That will be executed *before* any authentication information is extracted from the header or parameter list of the request. Use middleware in this section to pre-process a request before feeding it through the Tyk middleware. + +- `pre[].name`: The name of the middleware object to call. This is case sensitive, and **must** match the name of the middleware object that was created, so in our example - we created `sampleMiddleware` by calling: + + `var sampleMiddleware = new TykJS.TykMiddleware.NewMiddleware({});` + +- `pre[].path`: The path to the middleware component, this will be loaded into the JSVM when the API is initialised. This means that if you reload an API configuration, the middleware will also be re-loaded. You can hot-swap middleware on reload with no service interruption. + +- `pre[].require_session`: Irrelevant for pre-processor middleware, since no auth data has been extracted by the authentication middleware, it cannot be made available to the middleware. + +- `post`: Defines a list of custom middleware objects to run *in order* from top to bottom. That will be executed *after* the authentication, validation, throttling, and quota-limiting middleware has been executed, just before the request is proxied upstream. Use middleware in this section to post-process a request before sending it to your upstream API. + +- `post[].name`: The name of the middleware object to call. This is case sensitive, and **must** match the name of the middleware object that was created, so in our example - we created `sampleMiddleware` by calling: + + `var sampleMiddleware = new TykJS.TykMiddleware.NewMiddleware({});` + +- `post[].path`: The path to the middleware component, this will be loaded into the JSVM when the API is initialised. This means that if you reload an API configuration, the middleware will also be re-loaded. You can hot-swap middleware on reload with no service interruption. + +- `post[].require_session`: Defaults to `false`, if you require access to the session object, it will be supplied as a `session` variable to your middleware processor function. + +## WAF (OSS) ModSecurity Plugin example + +Traditionally, a Web Application Firewall (WAF) would be the first layer requests would hit, before reaching the API gateway. This is not possible if the Gateway has to terminate SSL, for things such as mTLS. + +So what do you do if you still want to run your requests through a WAF to automatically scan for malicious action? We incorporate a WAF as part of the request lifecycle by using Tyk's plugin architecture. + +### Prerequisites + +* Already running Tyk - Community Edition or Pro +* Docker, to run the WAF + +### Disclaimer + +This is NOT a production ready plugin because + +* The JavaScript plugin creates a new connection with the WAF for every request +* The request is not sent over SSL +* The WAF is only sent the query params for inspection. + +For higher performance, the plugin could be written in Golang, and a connection pool would be opened and maintained over SSL + +### Steps for Configuration + +1. **Turn JSVM on your `tyk.conf` at the root level:** + + Turn on JSVM interpreter to allow Tyk to run JavaScript plugins. + + ``` + "enable_jsvm": true + ``` + +2. **Place the JavaScript plugin on Tyk file system** + + Copy the JS Plugin as a local .js file to the Gateway's file system. + + From the Gateway root, this will download the plugin called `waf.js` into the `middleware` directory: + ``` + curl https://raw.githubusercontent.com/TykTechnologies/custom-plugins/master/plugins/js-pre-post-waf/waf.js | cat > middleware/waf.js + ``` + + (Instructions) + If you are running Tyk in Docker, you can get into Tyk Gateway with `docker exec` + ``` + $ docker ps | grep gateway + 670039a3e0b8 tykio/tyk-gateway:latest "./entrypoint.sh" 14 minutes ago Up 14 minutes 0.0.0.0:8080->8080/tcp tyk-demo_tyk-gateway_1 + + ## copy container name or ID + $ docker exec -it 670039a3e0b8 bash + + ## Now SSH'd into Tyk Gateway container and can perform curl + root@670039a3e0b8:/opt/tyk-gateway# ls + + apps entrypoint.sh install middleware templates tyk-gateway.pid tyk.conf.example + coprocess event_handlers js policies tyk tyk.conf utils + + ## Download the plugin + root@670039a3e0b8:/opt/tyk-gateway# curl https://raw.githubusercontent.com/TykTechnologies/custom-plugins/master/plugins/js-pre-post-waf/waf.js | cat > middleware/waf.js + + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 100 1125 100 1125 0 0 3906 0 --:--:-- --:--:-- --:--:-- 3975 + + ``` + + [waf.js source](https://raw.githubusercontent.com/TykTechnologies/custom-plugins/master/plugins/js-pre-post-waf/waf.js) + +3. **Import API definition into Tyk** + + Copy the following Tyk API definition and import it into your environment. + + [API Definition JSON](https://raw.githubusercontent.com/TykTechnologies/custom-plugins/master/plugins/js-pre-post-waf/apidef.json) + + Here's the important section which adds the plugin to the request lifecycle for this API: + ```{.json} + "custom_middleware": { + "pre": [ + { + "name": "Waf", + "path": "./middleware/waf.js" + } + ], + ``` + + ##### How to Import? + [Tyk Self-Managed](/api-management/gateway-config-managing-classic#import-an-api) + + [Tyk OSS](/api-management/gateway-config-managing-classic#create-an-api) + +4. **Run WAF ModSecurity Using Docker** + + First run ModSecurity with the popular [Core RuleSet](https://coreruleset.org/) in Docker + ``` + $ docker run -ti -p 80:80 -e PARANOIA=1 --rm owasp/modsecurity-crs:v3.0 + ``` + + Open a second terminal and curl it + ``` + $ curl localhost + + hello world + ``` + + We should see the request show in the WAF server: + + ``` + 172.17.0.1 - - [30/Jun/2020:00:56:42 +0000] "GET / HTTP/1.1" 200 12 + ``` + + Now try a dirty payload: + ``` + $ curl 'localhost/?param=">' + + + + 403 Forbidden + +

Forbidden

+

You don't have permission to access / + on this server.
+

+ + ``` + + Our WAF catches the response and returns a `403`. + + + Now we try through Tyk. + + ``` + ## Clean requests, should get response from upstream's IP endpoint + $ curl localhost:8080/waf/ip + + { + "origin": "172.30.0.1, 147.253.129.30" + } + + ## WAF will detect malicious payload and instruct Tyk to deny + $ curl 'localhost:8080/waf/ip?param="> + { + "error": "Bad request!" + } + ``` diff --git a/api-management/plugins/overview.mdx b/api-management/plugins/overview.mdx new file mode 100644 index 0000000000..38028fe449 --- /dev/null +++ b/api-management/plugins/overview.mdx @@ -0,0 +1,1247 @@ +--- +title: "Custom Plugins" +description: "Learn how to extend Tyk's capabilities using custom plugins to enhance API functionality." +sidebarTitle: "Overview" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; + +## Introduction + +Plugins can be used to customize and enhance the capabilities of your APIs through integration with external services and databases to perform operations such as data transformation, custom authentication, logging and monitoring etc. + +When Tyk receives an API request, it works through a [chain](/api-management/traffic-transformation#request-middleware-chain) of processing *middleware* that is configured using the API definition. There are a large number of built-in middleware in the processing chain that are dedicated to performing [client authentication](/api-management/client-authentication), [request transformation](/api-management/traffic-transformation), [caching](/api-management/response-caching) and many other processes before proxying the request to the upstream. + +Tyk's custom plugin facility provides a powerful and flexible way to extend the middleware chain. It allows API developers to write custom middleware, in various programming languages, that can perform additional processing of requests and responses. + +For example, a custom authentication scheme can be implemented and executed on API requests, custom plugins can be used to provide integration with external services and databases, or additional processing can be performed on the response returned from the upstream. + +There are several different stages of the [API request lifecycle](/api-management/traffic-transformation#request-middleware-chain) where custom plugins can be attached (or *hooked*) into the middleware chain allowing significant customization to meet your specific requirements. + +Custom plugins are usually referred to by the location where they can be *hooked* into the middleware processing chain as follows: + +1. [Pre (Request)](/api-management/plugins/plugin-types#request-plugins) +2. [Authentication](/api-management/plugins/plugin-types#authentication-plugins) +3. [Post-Auth (Request)](/api-management/plugins/plugin-types#request-plugins) +4. [Post (Request)](/api-management/plugins/plugin-types#request-plugins) +5. [Response](/api-management/plugins/plugin-types#response-plugins) +6. [Analytics (Response)](/api-management/plugins/plugin-types#analytics-plugins) + +## How Plugin Works + +The diagram below illustrates a high level architectural overview for how Tyk Gateway interacts with plugins. + +plugins overview + +From the above illustration it can be seen that: + +1. The client sends a request to an API served by Tyk Gateway. +2. Tyk processes the request and forwards it to one or more plugins implemented and configured for that API. +3. A plugin performs operations (e.g., custom authentication, data transformation). +4. The processed request is then returned to Tyk Gateway, which forwards it upstream. +5. Finally, the upstream response is sent back to the client. + +## Types of Plugin + +Tyk supports four types of plugins: + +1. **[Request Plugin](/api-management/plugins/plugin-types#request-plugins)** +2. **[Authentication Plugin](/api-management/plugins/plugin-types#authentication-plugins)** +3. **[Response Plugin](/api-management/plugins/plugin-types#response-plugins)** +4. **[Analytics Plugin](/api-management/plugins/plugin-types#analytics-plugins)** + +To know more about plugin types and it's advanced configuration, refer the following [docs](/api-management/plugins/plugin-types). + +## Getting Started + +This section takes you through the process of running and building a quickstart **Go plugin**, included within Tyk's [getting started](https://github.com/TykTechnologies/custom-go-plugin) repository. Go plugins are the recommended plugin type and suitable for most use cases. + +### Expected outcome + +At the end of this process you should have a Tyk Gateway or Tyk Self-Managed environment running locally, with a simple Go plugin executing on each API request. For each reponse to an API request the example plugin will inject a *Foo* header, with a value of *Bar*. + +### Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) +- [Docker-compose](https://docs.docker.com/compose/install/) +- [Tyk license](https://tyk.io/sign-up/#self) (if using Self-Managed Tyk, which will make the process easier via UI) +- [Make](https://www.gnu.org/software/make) +- OSX (Intel) -> Not a prerequisite, though these steps are tested on OSX Intel/ARM + +### Before you begin + +Please clone the [getting started](https://github.com/TykTechnologies/custom-go-plugin) respository. + +```bash +git clone https://github.com/TykTechnologies/custom-go-plugin +cd custom-go-plugin +``` + +### Choose your environment + + + + +**Read time: 15 mins** + +Dashboard Tutorial + + + +**Read time: 15 mins** + +Tyk OSS Gateway Tutorial + + + + + +### Dashboard Plugins + +This quick start explains how to run the [getting started](https://github.com/TykTechnologies/custom-go-plugin) Go plugin within Tyk Dashboard. + +**Estimated time**: 10-15 minutes + +In this tutorial you will learn how to: + +1. Add your Tyk license. +2. Bootstrap the Tyk Dashboard environment. +3. Login to Tyk Dashboard. +4. View the pre-configured API. +5. Test the plugin. +6. View the analytics. +7. Next steps. + +**Steps for Configuration:** + +1. **Add your Tyk license** + + Create and edit the file `.env` with your Tyk Dashboard license key + + ```console + # Make a copy of the example .env file for the Tyk-Dashboard + cp .env.example .env + ``` + +2. **Bootstrap the getting started example** + + run the `make` command: + + ```bash + make + ``` + + This will take a few minutes to run as it compiles the plugin for the first time and downloads all the necessary Docker images. + +3. **Log in to Tyk Dashboard** + + Log on to the Tyk Dashboard on `http://localhost:3000` using the following Bootstrapped credentials: + ``` + demo@tyk.io + ``` + and password: + ``` + topsecretpassword + ``` + + Note: these are editable in `.env.example` + +4. **View the pre-configured API** + + Once you're logged on to the Tyk Dashboard, navigate to the *APIs* screen. + + You'll see a sample *Httpbin* API. Let's click into it for more details. + + Click on *VIEW RAW DEFINITION*. Note the *custom_middleware* block is filled out, injecting the compiled example Go plugin into the API. + +5. **Test the plugin** + + Let's send an API request to the API Gateway so it can reverse proxy to our API. + + ```terminal + curl localhost:8080/httpbin/get + ``` + + Yields the response: + ``` + { + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Foo": "Bar", + "Host": "httpbin.org", + "User-Agent": "curl/7.79.1", + "X-Amzn-Trace-Id": "Root=1-63f78c47-51e22c5b57b8576b1225984a" + }, + "origin": "172.26.0.1, 99.242.70.243", + "url": "http://httpbin.org/get" + } + ``` + + Note, we see a *Foo:Bar* HTTP Header was injected by our Go plugin and echoed back to us by the Httpbin mock server. + +6. **View the analytics** + + Navigate to the Dashboard's various *API Usage Data* to view analytics on the API request! + +### Open-Source Plugins + +This quick start guide will explain how to run the [getting started](https://github.com/TykTechnologies/custom-go-plugin) Go plugin using the Tyk OSS Gateway. + +**Steps for Configuration:** + +1. **Bootstrap the getting started example** + + Please run the following command from within your newly cloned directory to run the Tyk Stack and compile the sample plugin. This will take a few minutes as we have to download all the necessary dependencies and docker images. + + ```bash + make up-oss && make build + ``` + +2. **Test the plugin** + + Let's test the plugin by sending an API request to the pre-configured API definition: + + ``` + curl localhost:8080/httpbin/get + ``` + + Response: + ``` + { + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Foo": "Bar", + "Host": "httpbin.org", + "User-Agent": "curl/7.79.1" + }, + "origin": "172.28.0.1, 99.242.70.243", + "url": "http://httpbin.org/get" + } + ``` + + We've sent an API request to the Gateway. We can see that the sample custom plugin has injected an HTTP header with a value of *Foo:Bar*. This header was echoed back in the Response Body via the mock Httpbin server. + + The `./tyk/scripts/bootstrap-oss.sh` script creates an API definition that includes the custom plugin. + +3. **View the analytics** + + We can see that Tyk Pump is running in the background. Let's check the logs after sending the API request: + + ``` + docker logs custom-go-plugin_tyk-pump_1 + ``` + + Output: + ``` + time="Feb 23 16:29:27" level=info msg="Purged 1 records..." prefix=stdout-pump + {"level":"info","msg":"","time":"0001-01-01T00:00:00Z","tyk-analytics-record":{"method":"GET","host":"httpbin.org","path":"/get","raw_path":"/get","content_length":0,"user_agent":"curl/7.79.1","day":23,"month":2,"year":2023,"hour":16,"response_code":200,"api_key":"00000000","timestamp":"2023-02-23T16:29:27.53328605Z","api_version":"Non Versioned","api_name":"httpbin","api_id":"845b8ed1ae964ea5a6eccab6abf3f3de","org_id":"","oauth_id":"","request_time":1128,"raw_request":"...","raw_response":"...","ip_address":"192.168.0.1","geo":{"country":{"iso_code":""},"city":{"geoname_id":0,"names":null},"location":{"latitude":0,"longitude":0,"time_zone":""}},"network":{"open_connections":0,"closed_connections":0,"bytes_in":0,"bytes_out":0},"latency":{"total":1128,"upstream":1111},"tags":["key-00000000","api-845b8ed1ae964ea5a6eccab6abf3f3de"],"alias":"","track_path":false,"expireAt":"2023-03-02T16:29:27.54271855Z","api_schema":""}} + ``` + + As we can see, when we send API requests, the Tyk Pump will scrape them from Redis and then send them to a persistent store as configured in the Tyk Pump env file. + + In this example, we've configured a simple `STDOUT` Pump where the records will be printed to the Standard OUT (docker logs!) + +## API Configuration + +This page provides an overview on how to register one or more custom plugins to be executed at different stages or [hooks](/api-management/plugins/plugin-types#plugin-and-hook-types) in the API request/response lifecycle. If you wish to learn how to register custom plugins to be executed on the traffic logs generated by the Gateway please refer to the [analytics plugins](/api-management/plugins/plugin-types#analytics-plugins) page. + +If you need fine-grained control at the endpoint level then it is also possible to configure [per-endpoint plugins](/api-management/plugins/plugin-types#per-endpoint-custom-plugins). These are custom Golang plugins that are triggered at the end of the request processing chain before API-level *Post* plugins are executed. + +--- + +### Introduction + +There are three locations where Tyk Gateway can find plugin functions: + +1. **gRPC plugins**: Plugin functions are implemented by a gRPC server with the associated configuration specified with the API definition. For further details on how to configure gRPC plugins, please refer to our [gRPC](/api-management/plugins/rich-plugins#overview-1) documentation. +2. **Local plugins**: Plugins are implemented by functions within source code files located on the Gateway's file system. The API Definition allows the source code file path and function name to be configured for each plugin. For further details read on. +3. **Plugin bundles**: The plugin source code and configuration are bundled into a zip file that is served by a remote web server. For further details see the [plugin bundles](/api-management/plugins/overview#plugin-bundles) page. + +### Plugin configuration + +Each plugin for an API can be configured within the API Definition with the following details: + +| Property | Description | +| :------- | :------------- | +| `Enabled` | When true, the plugin is activated | +| `Name` | A name used to identify the plugin | +| `Path` | The path to the source code file on the Tyk Gateway file system | +| `Function name` | The name of the function that implements the plugin. The function should exist within the source code file referenced in `path` | +| `Raw body only` | When set to true, this flag indicates that only the raw request body should be processed | +| `Require session state`| When set to true, Tyk Gateway will serialize the request session state and pass it as an argument to the function that implements the plugin in the target language. This is applicable to Post, Response, and Authentication hooks only | + +--- + +### Language configuration + +For local and bundle plugins a [plugin driver](/api-management/plugins/overview#plugin-driver-names) is configured to specify the plugin implementation language. If using gRPC plugins a `grpc` plugin driver should be used to instruct Tyk to request execution of plugins from within a gRPC server that is external to the Tyk process. This offers additional language support since Tyk can integrate with a gRPC server that is implemented using any supported [gRPC language](https://grpc.io/docs/). + +For a given API it is not possible to mix the implementation language for the plugin types: Pre, Authentication, Post, Post Authentication and Response plugins. For example, it is not possible to implement a pre request plugin in *Go* and also implement a post request plugin in *Python* for the same API. + +### Tyk OAS APIs + +An API can be configured so that one or more of its associated plugins can execute at different phases of the request / response life cycle. Each plugin configuration serves to identify the plugin source file path and the name of the corresponding function, triggered at each request / response lifecycle stage. + +This guide explains how to configure plugins for Tyk OAS APIs within the [Tyk OAS API definition](#tyk-oas-apidef) or via the [API designer](#tyk-oas-dashboard) in Tyk Dashboard. + +If you’re using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/plugins/overview#tyk-classic-apis) page. + +#### Using API Definition + + +The `x-tyk-api-gateway.middleware.global` section is used to configure plugins in a Tyk OAS API. It contains a `pluginConfig` section and a list of plugins for each phase of the API request / response lifecycle. + +The `pluginConfig` section contains the `driver` parameter that is used to configure the plugin implementation [language](/api-management/plugins/overview#plugin-driver-names): + +```yaml +"pluginConfig": { + "driver": "goplugin" +} +``` + +Within the `x-tyk-api-gateway.middleware.global` section, keyed lists of plugins can be configured for each phase of the API request / response lifecycle described in the table below: + +| Phase | Description | Config Key | +| :----- | :--- | :---- | +| Pre | Executed at the start of the request processing chain | `prePlugins` | +| Post Auth | Executed after the requester has been authenticated | `postAuthenticationPlugins` | +| Post | Executed at the end of the request processing chain | `postPlugins` | +| Response | Occurs after the main request processing but before the response is sent. | `responsePlugins` | + +Each plugin configuration can have the following fields configured: + +- `enabled`: When true, enables the plugin. +- `functionName`: The name of the function that implements the plugin within the source file. +- `path`: The path to the plugin source file. +- `rawBodyOnly`: When true, indicates that only the raw body should be processed. +- `requireSession`: When true, indicates that session metadata will be available to the plugin. This is applicable only for post, post authentication and response plugins. + +For example a Post Authentication plugin would be configured within a `postAuthenticationPlugins` list as shown below: + +```yaml +"postAuthenticationPlugins": [ + { + "enabled": true, + "functionName": "post_authentication_func", + "path": "/path/to/plugin1.so", + "rawBodyOnly": true, + "requireSession": true + } +] +``` + +An full example is given below to illustrate how to set up plugins for different phases of the request / response lifecycle: + +```json {linenos=true, linenostart=1, hl_lines=["15-52"]} +{ + "x-tyk-api-gateway": { + "info": { + "dbId": "667962397f6de50001508ac4", + "id": "b4d8ac6e5a274d7c7959d069b47dc206", + "orgId": "6672f4377f6de50001508abf", + "name": "OAS APIs Plugins", + "state": { + "active": true, + "internal": false + } + }, + "middleware": { + "global": { + "pluginConfig": { + "driver": "goplugin" + }, + "postAuthenticationPlugins": [ + { + "enabled": true, + "functionName": "post_authentication_func", + "path": "/path/to/plugin1.so", + "rawBodyOnly": true, + "requireSession": true + } + ], + "postPlugins": [ + { + "enabled": true, + "functionName": "postplugin", + "path": "/path/to/plugin1.so", + "rawBodyOnly": true, + "requireSession": true + } + ], + "prePlugins": [ + { + "enabled": true, + "functionName": "pre-plugin", + "path": "/path/to/plugin1.so" + } + ], + "responsePlugins": [ + { + "enabled": true, + "functionName": "Response", + "path": "/path/to/plugin1.so", + "rawBodyOnly": true, + "requireSession": true + } + ] + } + } + } +} +``` + +In this example we can see that the plugin driver has been configured by setting the `driver` field to `goplugin` within the `pluginConfig` object. This configuration instructs Tyk Gateway that our plugins are implemented using Golang. + +We can also see that the following type of plugins are configured: + +- **Pre**: A plugin is configured within the `prePlugins` list. The plugin is enabled and implemented by function `pre-plugin` within the source file located at path `/path/to/plugin1.so`. +- **Post Authentication**: A plugin is configured within the `postAuthenticationPlugins` list. The plugin is enabled and implemented by function `post_authentication_func` within the source file located at path `/path/to/plugin1.so`. The raw request body and session metadata is available to the plugin. +- **Post**: A plugin is configured within the `responsePlugins` list. The plugin is enabled and implemented by function `postplugin` within the source file located at path `/path/to/plugin1.so`. The raw request body and session metadata is available to the plugin. +- **Response**: A plugin is configured within the `postPlugins` list. The plugin is enabled and implemented by function `Response` within the source file located at path `/path/to/plugin1.so`. The raw request body and session metadata is available to the plugin. + +The configuration above is a complete and valid Tyk OAS API Definition that you can use as a basis for trying out custom plugins. You will need to update the [driver](/api-management/plugins/overview#plugin-driver-names) parameter to reflect the target language type of your plugins. You will also need to update the `path` and `functionName` parameters for each plugin to reflect the source code. + +#### Using API Designer + + +Select your API from the list of *Created APIs* to reach the API designer and then follow these steps: + +1. **Configure plugin type and custom data** + + In the *Plugins Configuration* section, select the *Plugin Driver*, which tells Tyk which type of plugin to expect: Go, gRPC, JavaScript (OTTO), Lua or Python. + + You can configure custom data that will be made available to your plugin function as a JSON formatted object in the *Config Data* option. + + OAS API Plugins Driver Config + +2. **Configure the custom plugins** + + For each plugin that you wish to register with the API, click on the **Add Plugin** button to display a plugin configuration section: + + OAS Plugins Config Section + + Complete the following fields: + + - `Function Name`: Enter the name of the function within your plugin code that Tyk should invoke. + - `Path`: Enter the path to the source file that contains the function that implements your plugin. + - `Raw Body Only`: Optionally, toggle the *Raw Body Only* switch to true when you do not wish to fill body in request or response object for your plugins. + +3. **Save the API** + + Select **Save API** to apply the changes to your API. + +### Tyk Classic APIs + +An API can be configured so that one or more of its associated plugins can execute at different phases of the request / response lifecycle. Each plugin configuration serves to identify the plugin source file path and the name of the corresponding function, triggered at each request / response lifecycle stage. + +This guide explains how to configure plugins for Tyk Classic APIs within the [Tyk Classic API definition](#tyk-classic-apidef) or via the [API designer](#tyk-classic-dashboard) in Tyk Dashboard. + +If you’re using the newer Tyk OAS APIs, then check out the [Tyk OAS](/api-management/plugins/overview#tyk-oas-apis) page. + +#### Using API Definition + + +In Tyk Classic APIs, the *custom_middleware* section of the Tyk Classic API Definition is where you configure plugins that will run at different points during the lifecycle of an API request. + +This table illustrates the different phases of the API request lifecycle where custom plugins can be executed: + +| Phase | Description | Config | +| :----- | :--- | :---- | +| Pre | Executed at the start of the request processing chain | `pre` | +| Auth | Executed during the authentication step | `auth_check` | +| Post Auth | Executed after the requester has been authenticated | `post_key_auth` | +| Post | Executed at the end of the request processing chain | `post` | +| Response | Executed on the response received from the upstream | `response` | + +This example configuration illustrates how to set up plugins for different phases of the request lifecycle: + +```json {linenos=true, linenostart=1} +{ + "custom_middleware": { + "pre": [ + { + "name": "PreHook1", + "path": "/path/to/plugin1.so", + "disabled": false, + "require_session": false, + "raw_body_only": false + } + ], + "auth_check": { + "name": "AuthCheck", + "path": "/path/to/plugin.so", + "disabled": false, + "require_session": false, + "raw_body_only": false + }, + "post_key_auth": [ + { + "name": "PostKeyAuth", + "path": "/path/to/plugin.so", + "disabled": false, + "require_session": false, + "raw_body_only": false + } + ], + "post": [ + { + "name": "PostHook1", + "path": "/path/to/plugin1.so", + "disabled": false, + "require_session": false, + "raw_body_only": false + }, + { + "name": "PostHook2", + "path": "/path/to/plugin2.so", + "disabled": false, + "require_session": false, + "raw_body_only": false + } + ], + "response": [ + { + "name": "ResponseHook", + "path": "/path/to/plugin.so", + "disabled": false, + "require_session": false, + "raw_body_only": false + } + ], + "driver": "goplugin" + } +} +``` + +In this example we can see that there are Golang custom authentication (`auth_check`), post authentication (`post_key_auth`), post, pre and response plugins configured. + +It can be seen that each plugin is configured with the specific function name and associated source file path of the file that contains the function. Furthermore, each lifecycle phase (except `auth`) can have a list of plugins configured, allowing for complex processing workflows. For example, you might develop one plugin for logging and another for modifying the request in the pre request phase. When multiple plugins are configured for a phase they will be executed in the order that they appear in the API definition. + +The `driver` configuration parameter describes the plugin implementation language. Please refer to the [supported languages](/api-management/plugins/overview#plugin-driver-names) section for list of supported plugin driver names. + +Each plugin can have additional settings, such as: +- `disabled`: When true, disables the plugin. +- `raw_body_only`: When true, indicates that only the raw body should be processed. +- `require_session`: When true, indicates that session metadata will be available to the plugin. This is applicable only for post, post authentication and response plugins. + +#### Using API Designer + + +This section explains how to configure plugins for a Tyk Classic API using Tyk Dashboard. It specifically covers the use case where the source files of your plugins are deployed on the Tyk Gateway file system. + +Select your API from the list of *Created APIs* to reach the API designer and then follow these steps: + +Plugins Classic API screen + +1. **Display the Tyk Classic API Definition editor** + + Click on the **View Raw Definition** button to display an editor for updating the Tyk Classic API Definition. + + Plugins Classic API Definition editor screen + +2. **Edit the Tyk Classic API Definition to configure plugins** + + Use the editor to edit the `custom_middleware` section of the [Tyk Classic API Definition](/api-management/plugins/overview#tyk-classic-apis). + + Plugins Classic API Bundle Field + +3. **Save changes** + + Select the **Update** button to apply your changes to the Tyk Classic API Definition. + +## Plugin Deployment Types + +There are a variety of scenarios relating to the deployment of plugins for an API, concerning the location of the plugin source code and its associated configuration. + +### Local Plugins + +The plugin source code and associated configuration are co-located with Tyk Gateway in the same file system. The configuration is located within the API Definition. For further details please consult [API configuration](/api-management/plugins/overview#api-configuration). + +Local plugins can be supplied to Tyk Gateway in one of two ways: + +- **Volume-mounted files**: the plugin file is mounted into the Gateway's file system at runtime, for example using a Kubernetes ConfigMap, Secret, or CSI volume. See [Custom Plugins with Tyk Operator](/product-stack/tyk-operator/advanced-configurations/custom-plugins) for an example that mounts a plugin this way. +- **Baked into the container image**: the plugin file is copied into a custom Tyk Gateway image at build time, so that it ships as part of the container. + +Both variants share the same underlying constraint: Tyk Gateway only loads the plugin file that is present in its file system when the API definition referencing it is loaded. Any change to the plugin therefore requires the file to be replaced and the affected API definition to be reloaded. If the plugin is baked into the container image, that still means restarting or rolling out the Gateway pods, since the image itself has to change. If it is volume-mounted, updating the file in place followed by a Gateway reload can be enough, without a pod restart. + +#### Advantages Of Local Plugins + +- No external bundle server, network access, or signing keys are required, as the plugin ships with the Gateway. +- Tyk Gateway fails to load the API if the plugin file is missing, which makes startup issues easy to spot. +- You can roll out a new plugin version alongside the old one, for example during an upgrade, by shipping both files and pointing the relevant API definitions at the new file path. This avoids rebuilding a shared bundle for every API. + +#### Trade-Offs Of Local Plugins + +- Every plugin change requires the file to be replaced and the API definition reloaded. If the plugin is baked into the image, this also means restarting or rolling out the Gateway pods to deploy the new image, which can interrupt live traffic if it is not managed carefully. +- Each cluster or Gateway deployment needs its own copy of the image or mounted files, so a multi-cluster fleet can end up with several copies to keep in sync. +- Tyk Gateway does not version local plugins for you, so tracking which Gateway is running which plugin build depends on your own image or file naming discipline. + + +Tyk Cloud Data Planes only support plugin bundles for custom plugins. If you use Tyk Cloud, see [Configure Custom Plugins in Tyk Cloud](/tyk-cloud/using-plugins). + + +### Plugin Bundles (Remote) + +The plugin source code and associated configuration are bundled into a zip file and uploaded to a remote webserver. Multiple plugins can be stored in a single *plugin bundle*. Tyk Gateway will download the plugin bundle from the remote webserver and then extract, cache and execute plugins for each of the configured phases of the API request / response lifecycle. For further details on plugin bundles and how to configure them, please refer to the [plugin bundles](/api-management/plugins/overview#plugin-bundles) page. + +### gRPC Plugins (Remote) + +Custom plugins can be hosted on a remote server and executed from the Tyk Gateway middleware chain via gRPC. These plugins can be written in any language you prefer, as they are executed on the gRPC server. You'll configure your API definition so that Tyk Gateway will send requests to your gRPC server at the appropriate points in the API request / response lifecycle. For further details please consult our [gRPC](/api-management/plugins/rich-plugins#overview-1) documentation. + +### Choosing Between Local Plugins And Plugin Bundles + +Neither approach is universally better. Use the table below to compare them against your operational requirements. + +| Dimension | Local Plugins (Volume-Mounted Or Baked-In) | Plugin Bundles (Remote) | +| --- | --- | --- | +| Distribution scope | Per Gateway deployment. The image or mounted files must be duplicated across clusters | Shared. Any Gateway that can reach the bundle server uses the same bundle | +| Update workflow | Rebuild the image and roll out the Gateway, or update the mounted files and reload the Gateway | Publish a new bundle file, update the API definition, then trigger a Gateway reload | +| External dependencies | None beyond your own image registry or cluster storage | Requires an HTTP(S) server to host the bundle, and optionally signing keys | +| Adding or changing a single plugin file | Copy or mount the new file directly | Rebuild and republish the entire bundle, as all files in a bundle are fixed by its `manifest.json` at build time | +| Rollback | Roll the Gateway back to the previous image or mounted files | Point the API definition back to the previous bundle filename | +| Behavior if the plugin source is unavailable at startup | The Gateway fails to load the affected API | Tyk Gateway can keep using a previously cached copy of the same bundle if the download fails. A first-time download or a new bundle filename still fails to load the affected API | +| Best fit | Air-gapped or restricted-network environments, or teams with a mature image or configuration promotion workflow | Fleets that share plugins across many APIs or Gateways, or that need frequent plugin updates without restarting pods | + +Consider the following when deciding between the two: + +- Use **local plugins** if you operate in a restricted network, want to avoid a runtime dependency on an external server, or already have a strong image or configuration promotion workflow. +- Use **plugin bundles** if you need the same plugin shared across many APIs or Gateways, want to roll out plugin changes without restarting pods, or require [signed bundles](/api-management/plugins/overview#gateway-configuration) for supply-chain integrity. +- The two approaches are not mutually exclusive. Different APIs on the same Tyk Gateway fleet can use either approach, as long as each API definition clearly states where its plugin lives. +- If you use [Tyk Cloud](/tyk-cloud/using-plugins), plugin bundles are the only supported option for custom plugins. + +## Plugin Bundles + +For Tyk Gateway to execute local custom plugins during the processing of API requests and responses, the plugin source code must be loaded into the Gateway. The source is usually stored in files and the API definition is used to point the Gateway at the correct file for each [plugin type](/api-management/plugins/plugin-types#plugin-types). To simplify the management of plugins, you can group (or *bundle*) multiple plugin files together in a ZIP file that is referred to as a *plugin bundle*. + +### When To Use Plugin Bundles + +Plugin bundles are intended to simplify the process of attaching and loading custom middleware. Multiple API definitions can refer to the same plugin bundle (containing the source code and configuration) if required. Having this common, shared resource avoids you from having to duplicate plugin configuration for each of your APIs definitions. + +### How Plugin Bundles Work + +The source code and a [manifest file](#manifest) are bundled into a zip file and uploaded to an external remote web server. The manifest file references the source code file path and the function name within the code that should be invoked for each [plugin type](/api-management/plugins/plugin-types#plugin-types). Within the API definition, custom plugins are configured simply using the name of the bundle (zip file). Tyk Gateway downloads, caches, extracts and executes plugins from the downloaded bundle according to the configuration in the manifest file. + +plugin bundles architectural overview + +#### Caching plugin bundles + +Tyk downloads a plugin bundle on startup based on the configuration in the API definition, e.g. `http://my-bundle-server.com/bundles/bundle-latest.zip`. The bundle contents will be cached so that, when a Tyk reload event occurs, the Gateway does not have to retrieve the bundle from the server again each time. If you want to use a different bundle then you must update your API to retrieve a different bundle filename and then trigger a reload. It is not sufficient simply to replace the bundle file on your server with an updated version with the same name - the caching ensures this will not be retrieved during a reload event. + +As a suggestion, you may organize your plugin bundle files using a Git commit reference or version number, e.g. `bundle-e5e6044.zip`, `bundle-48714c8.zip`, `bundle-1.0.0.zip`, `bundle-1.0.1.zip`, etc. + +Alternatively, you may delete the cached bundle from Tyk manually and then trigger a hot reload to tell Tyk to fetch a new one. By default, Tyk will store downloaded bundles in this path: +`{ TYK_ROOT } / { CONFIG_MIDDLEWARE_PATH } / bundles` + +#### Gateway configuration + +To configure Tyk Gateway to load plugin bundles the following parameters must be specified in your `tyk.conf`: + +```yaml +"enable_bundle_downloader": true, +"bundle_base_url": "http://my-bundle-server.com/bundles/", +"public_key_path": "/path/to/my/pubkey", +``` + +- `enable_bundle_downloader`: Enables the bundle downloader. +- `bundle_base_url`: A base URL that will be used to download the bundle. For example if we have `bundle-latest.zip` specified in the API definition, Tyk will fetch the following file: `http://my-bundle-server.com/bundles/bundle-latest.zip` (see the next section for details). +- `public_key_path`: Sets a public key, used for verifying signed bundles. If unsigned bundles are used you may omit this. + + + + + Remember to set `"enable_coprocess": true` in your `tyk.conf` when using [rich plugins](/api-management/plugins/overview#plugin-bundles)! + + + +#### The manifest file + + +A plugin bundle must include a manifest file (called `manifest.json`). The manifest file contains important information like the configuration block and the list of source code files that will be included as part of the bundle file. If a file isn't specified in the list, it won't be included in the resulting file, even if it's present in the current directory. + +A sample manifest file looks like this: + +```json +{ + "file_list": [ + "middleware.py", + "mylib.py" + ], + "custom_middleware": { + "pre": [ + { + "name": "PreMiddleware" + } + ], + "post": [ + { + "name": "PostMiddleware" + } + ], + "driver": "python" + }, + "checksum": "", + "signature": "" +} +``` + +You may leave the `checksum` and `signature` fields empty, the bundler tool will fill these during the build process. + +The `custom_middleware` block follows the standard syntax we use for Tyk plugins. In Tyk Community Edition, where file-based API configuration is used by default, a `custom_middleware` block is located/added to the API configuration file. + +#### Creating plugin bundles + +Tyk provides the Bundle CLI tool as part of the `tyk` binary. For further details please visit the [Bundle CLI tool](/api-management/plugins/overview#bundler-cli-tool) page. + +### Tyk OAS API Configuration + +For API plugins that are deployed as [plugin bundles](/api-management/plugins/overview#plugin-bundles), the API should be configured with the name of the plugin bundle file to download from your remote web server. Furthermore, the Gateway should be [configured](/api-management/plugins/overview#gateway-configuration) to enable downloading plugin bundles. + +You can configure your API with the name of the plugin bundle file to download within the Tyk OAS API definition or API Designer. + +If you’re using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/plugins/overview#tyk-classic-apis) page. + +#### Using API Definition + +The configuration for a Tyk OAS API to fetch the download of a plugin bundle from a remote web server is encapsulated within the `pluginConfig` section within the `middleware.global` section of the `x-tyk-api-gateway` part of a Tyk OAS API Definition. + +The `pluginConfig` section is structured as follows: + +- `bundle`: A JSON entity that contains the following configuration parameters: + - `enabled`: When `true`, enables the plugin. + - `path`: The relative path of the zip file in relation to the base URL configured on the remote webserver that hosts plugin bundles. +- `driver`: Indicates the type of plugin, e.g. `golang`, `grpc`, `lua`, `otto` or `python`. + +An illustrative example is listed below: + +```json{hl_lines=["37-45"], linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-oas-plugin-configuration", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "put": { + "operationId": "anythingput", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-oas-plugin-configuration", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-oas-plugin-configuration/", + "strip": true + } + }, + "middleware": { + "global": { + "pluginConfig": { + "bundle": { + "enabled": true, + "path": "plugin.zip" + }, + "driver": "goplugin" + } + } + } + } +} +``` + +In this example we can see that bundle plugin has been configured within the `middleware.global.pluginConfig.bundle` object. The plugin is enabled and bundled within file `plugin.zip`. The plugin bundle is a Go plugin, i.e. `middleware.global.pluginConfig.driver` has been configured with value `goplugin`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out custom plugin bundles, assuming that you have provided a valid bundle file named `plugin.zip`. + +#### Using API Designer + +To configure plugin bundles for Tyk OAS APIs click on the APIs menu item in the *API Management* menu of Dashboard and select your API to display the editor screen. Subsequently, follow the steps below: + +1. **Access plugin options** + + Scroll down until the *Enable Plugin* section is displayed. + + Tyk OAS API Bundle section + +2. **Enable plugin bundle for you API** + + Enable a plugin bundle for your API by activating the toggle switch. + +3. **Enter relative path to plugin bundle file** + + Enter the relative path of the plugin bundle file in the *Plugin Bundle ID* field that Tyk Gateway should download from the web server that hosts your plugin bundles. + +4. **Save the API** + + Select **Save API** to apply the changes to your API. + +### Tyk Classic API Configuration + +For custom plugins that are deployed as [plugin bundles](/api-management/plugins/overview#plugin-bundles), the API should be configured with the name of the plugin bundle file to download from your remote web server. Furthermore, the Gateway should be [configured](/api-management/plugins/overview#gateway-configuration) to enable downloading plugin bundles. + +You can configure your API with the name of the plugin bundle file to download within the Tyk Classic API definition or API Designer. + +If you’re using the newer Tyk OAS APIs, then check out the [Tyk OAS](/api-management/plugins/overview#tyk-oas-api-configuration) page. + +#### Using API Definition + +The configuration for an API to fetch and download a plugin bundle from a remote server is encapsulated within the `custom_middleware_bundle` field of the Tyk Classic API Definition. An illustrative example is listed below: + +```json {hl_lines=["33"], linenos=true, linenostart=1} +{ + "name": "Tyk Classic Bundle API", + "api_id": "1", + "org_id": "default", + "definition": { + "location": "header", + "key": "version" + }, + "auth": { + "auth_header_name": "authorization" + }, + "use_keyless": true, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "expires": "3000-01-02 15:04", + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [] + } + } + } + }, + "proxy": { + "listen_path": "/quickstart/", + "target_url": "http://httpbin.org", + "strip_listen_path": true + }, + "custom_middleware_bundle": "bundle-latest.zip" +} +``` + +With the configuration given in the example above, calls to the API will invoke the custom plugins defined in the `manifest.json` file contained within `bundle-latest.zip` uploaded to your remote webserver, e.g. `http://your-example-plugin-server.com/plugins`. + +Tyk Gateway should be configured for downloading plugin bundles from a secured web server. Please consult the [plugin bundles](/api-management/plugins/overview#plugin-bundles) documentation for further details. + +#### Using API Designer + +To configure plugin bundles for Tyk Classic APIs click on the APIs menu item in the *API Management* menu of Dashboard and select your API to display the API editor screen. Subsequently, follow the steps below: + +1. **Access plugin options** + + Click on the *Advanced Options* tab and scroll down until the *Plugin Options* section is displayed. + + Tyk Classic Plugin Options section + +2. **Enter relative path to bundle file** + + Enter the relative path of the plugin bundle file in the *Plugin Bundle ID* field that Tyk Gateway should download from the web server hosting plugin bundles. + +3. **Save the API** + + Select the **save** or **update** button to apply the changes to your API. + +### Bundler CLI Tool + +The bundler tool is a CLI service, provided by _Tyk Gateway_ as part of its binary since v2.8. This lets you generate +[plugin bundles](/api-management/plugins/overview#plugin-bundles). + + + +Generated plugin bundles must be served using your own web server. + + + +Issue the following command to see more details on the `bundle` command: + +```bash +/opt/tyk-gateway/bin/tyk bundle -h +``` + +--- + +#### Prerequisites + +To create plugin bundles you will need the following: + +- **Manifest.json**: The [manifest.json](/api-management/plugins/overview#manifest) file + contains the paths to the plugin source files and the name of the function implementing each plugin. The + _manifest.json_ file is mandatory and must exist on the Tyk Gateway file system. By default the bundle CLI looks for + a file named _manifest.json_ in the current working directory where the bundle command is run from. The exact location + can be specified using the `--manifest` command option. +- **Plugin source code files**: The plugin source code files should be contained relative to the directory in which the + _manifest.json_ file is located. The _manifest.json_ should contain relative path references to source code files. + + + +Source code files are not required when creating a plugin bundle for gRPC plugins since the plugin + source code is located at the gRPC server. + + + +- **Certificate key**: Plugin bundles can optionally be signed with an RSA private key. The corresponding public key + should be located in the file configured in environmental variable `TYK_GW_PUBLICKEYPATH` or the `public_key_path` + parameter in `tyk.conf`: + +```json +{ + "enable_bundle_downloader": true, + "bundle_base_url": "http://my-bundle-server.com/bundles/", + "public_key_path": "/path/to/my/pubkey.pem" +} +``` + +--- + +#### Directory Structure + +A suggested directory structure is shown below for Golang, Javascript and Python bundles in the tabs below. + + + +Sub-directories (folders) are not supported inside the `bundle-directory` location. + + + + + +```bash +/bundle-directory +├── manifest.json # Manifest file with plugin references +└── plugin.so # Compiled Golang plugin +``` + + + +```bash +/bundle-directory +├── manifest.json # Manifest file with plugin references +├── plugin1.js # First JavaScript plugin source file +└── plugin2.js # Second JavaScript plugin source file +``` + + + + + +```bash +/bundle-directory +├── manifest.json # Manifest file with plugin references +├── plugin1.py # First Python plugin source file +└── plugin2.py # Second Python plugin source file +``` + + + + + +The `manifest.json` will reference the files located in the `bundle-directory`, ensure plugin source files are organized relative to the manifest. The Tyk Gateway will load and execute these plugins based on the paths defined in the `manifest.json` file. + +Sample `manifest.json` is shown below for Golang, Javascript and Python bundles in the tabs below. + + + +```json +{ + "file_list": [ + "plugin.so" + ], + "custom_middleware": { + "pre": [ + { + "name": "PreMiddleware", + "path": "./plugin.so" + } + ], + "post": [ + { + "name": "PostMiddleware", + "path": "./plugin.so" + } + ], + "driver": "goplugin" + }, + "checksum": "", + "signature": "" +} + +``` + + + +```json +{ + "file_list": [ + "plugin1.js", + "plugin2.js" + ], + "custom_middleware": { + "pre": [ + { + "name": "PreMiddleware", + "path": "./plugin1.js" + } + ], + "post": [ + { + "name": "PostMiddleware", + "path": "./plugin2.js" + } + ], + "driver": "otto" + }, + "checksum": "", + "signature": "" +} +``` + + + + + +```json +{ + "file_list": [ + "plugin1.py", + "plugin2.py" + ], + "custom_middleware": { + "pre": [ + { + "name": "PreMiddleware", + "path": "./plugin1.py" + } + ], + "post": [ + { + "name": "PostMiddleware", + "path": "./plugin2.py" + } + ], + "driver": "python" + }, + "checksum": "", + "signature": "" +} +``` + + + + + +--- + +#### Creating a plugin bundle + +Run the following command to create the bundle: + +```bash +$ tyk bundle build +``` + +The resulting file will contain all your specified files and a modified `manifest.json` with the checksum and signature +(if required) applied, in ZIP format. + +By default, Tyk will attempt to sign plugin bundles for improved security. If no private key is specified, the program +will prompt for a confirmation. Use `-y` to override this (see options below). + +--- + +#### Command Options + +Instructions on how to create plugin bundles is displayed by issuing the following command: + +```bash +/opt/tyk-gateway/bin/tyk bundle build -h +``` + +The following options are supported: + +- `--manifest`: Specifies the path to the manifest file. This defaults to `manifest.json` within the current working + directory. +- `--output`: Specifies the name of the bundle file e.g. `--output bundle-latest.zip`. If this flag is not specified, + `bundle.zip` will be used. +- `-y`: Force tool to create unsigned bundle without prompting e.g. `$ tyk bundle build --output bundle-latest.zip -y`. +- `--key`: Specifies the path to your private key which is used to generate signed bundle e.g. + `$ tyk bundle build --output bundle-latest.zip --key=mykey.pem`. + +--- + +#### Docker Example + +Since v5.5 Tyk Gateway uses distroless docker images. + +For Gateway version < v5.5 it is possible to use Docker to create plugin bundles as shown in the example below. + +```bash +docker run --rm -it \ + --name bundler \ + -v `pwd`:/plugin-source \ + -v `pwd`/../../../confs/keys:/keys \ + -w /plugin-source \ + --entrypoint /bin/bash \ + tykio/tyk-gateway:v5.4.0 \ + -c 'export PATH="/opt/tyk-gateway:$$PATH"; tyk bundle build -o bundle.zip -k /keys/key.pem' +``` + +This Docker command runs a container using the `tykio/tyk-gateway:v5.4.0` image to build a Tyk plugin bundle. It mounts +the current directory from the host as `/plugin-source` and a directory containing keys as `/keys` inside the container. +The working directory within the container is set to `/plugin-source`, and the default entrypoint is overridden to use +`/bin/bash`. The command executed in the container exports a modified `PATH` to include the Tyk Gateway binaries, then +runs `tyk bundle build` to generate a plugin bundle named `bundle.zip`, using the specified key for authentication. The +container is automatically removed after the command completes, and the operation is conducted interactively. + +## Supported Languages + +The following languages are supported for custom plugins: +* [Golang](/api-management/plugins/golang#): A plugin written in Golang is called a **Native Plugin**. Tyk recommends using Go plugins for performance, flexibility, and nativity reasons (all Tyk components are written in Go). +* [JavaScript](/api-management/plugins/javascript#): A plugin written in Javascript uses JavaScript Virtual Machine (JSVM) interpreter. +* [Rich Plugins](/api-management/plugins/rich-plugins#) includes Python, Lua, gRPC - With gRPC, you can write plugins in Java, .NET, C++ / C#, PHP, and all other [gRPC supported languages](https://grpc.io/docs/languages/). +Rich plugins give ultimate flexibility in the language of implementation, however, there are some performance and management overheads when compared with native GoLang plugin. + +**Common To All Plugin Languages:** + +* Make Layer 4 (TCP) or Layer 7 (HTTP/REST/SOAP) calls +* Open Persistent Connections +* Modify the request in-flight +* Used to stop the request and return a [custom response](/api-management/plugins/plugin-types#return-overrides-%2F-returnoverrides) +* Be served using [Bundles](/api-management/plugins/overview#plugin-deployment-types) or by files on the file system, except gRPC of course which by definition is served by some webserver in the language of your choosing + +### Plugin Hook Types + +Tyk provide 5 different phases, i.e. hooks to inject custom plugin throughout the [API execution lifecycle](/api-management/traffic-transformation#request-middleware-chain). + +Not all hooks are supported in every language. The following table shows you which plugin language support which phase/hook: + +| | Auth | Pre | Post-Auth | Post | Response +| :------------ | :-------- | :---------- | :----------- | :------ | :----------- | +| GoLang | ✅ |✅ |✅ |✅ |✅ +| JavaScript | ❌ |✅ |❌ |✅ |❌ +| gRPC | ✅ |✅ |✅ |✅ |✅ +| Python | ✅ |✅ |✅ |✅ |✅ +| Lua | ✅ |✅ |✅ |✅ |❌ + +More reading on the [hook types](/api-management/plugins/rich-plugins#coprocess-dispatcher-hooks) in rich plugins and explanation with common use case for each [hook type](/api-management/plugins/plugin-types#plugin-types) + + +### Plugin Driver Names + +We use the following Plugin driver names: + +| Plugin | Name | +| :---------- | :--------- | +| GoLang | goplugin | +| JavaScript | otto | +| gRPC | grpc | +| Python | python | +| Lua | lua | + +### Limitations + +What are the limitations to using this programming Language? + +| | GoLang | JavaScript | gRPC | Python | Lua +| :--- | :-------- | :------------------ | :----------- | :----------- | :----------- | +| Runs in Gateway process | ✅
Runs
natively | ✅
Built-In JSVM Interpreter | ❌
Standalone server | ✅
Tyk talks with Python interpreter |✅ +| Built-in SDK | ✅
All Gateway Functionality | ✅
[Yes](/api-management/plugins/javascript#javascript-api) | ❌ | ✅
[Yes](/api-management/plugins/rich-plugins#tyk-python-api-methods) | ❌ +| TCP Connections

(DBs, Redis, etc)

| ✅ | ❌
Very Limited | ✅ | ✅ | ✅ | + +### Custom Plugin Table + +We have put together a [GitHub repo with a table of custom plugins](https://github.com/TykTechnologies/custom-plugins#custom-gateway-plugins) in various languages that you can experiment with. If you would like to submit one that you have developed, feel free to open an issue in the repo. + +### Differences between Rich Plugins and JSVM middleware + +#### JavaScript +The JavaScript Virtual Machine provides pluggable middleware that can modify a request on the fly and are designed to augment a running Tyk process, are easy to implement and run inside the Tyk process in a sandboxed *ECMAScript* interpreter. This is good, but there are some drawbacks with the JSVM: + +* **Performance**: JSVM is performant, but is not easy to optimize and is dependent on the [otto interpreter](https://github.com/robertkrimen/otto) - this is not ideal. The JSVM also requires a copy of the interpreter object for each request to be made, which can increase memory footprint. +* **Extensibility**: JSVM is a limited interpreter, although it can use some NPM modules, it isn't NodeJS so writing interoperable code (especially with other DBs) is difficult. +* **TCP Access**: The JSVM has no socket access so working with DB drivers and directly with Redis is not possible. + +#### Rich Plugins +Rich Plugins can provide replacements for existing middleware functions (as opposed to augmentation) and are designed to be full-blown, optimized, highly capable services. They enable a full customized architecture to be built that integrates with a user's infrastructure. + +Rich Plugins bring about the following improvements: + +* **Performance**: Run on STDIN (unix pipes), which are extremely fast and run in their own memory space, and so can be optimized for performance way beyond what the JSVM could offer. +* **Extensibility**: By allowing any language to be used so long as GRPC is supported, the extensibility of a CPH is completely open. +* **TCP Access**: Because a plugin is a separate process, it can have it's own low-level TCP connections opens to databases and services. + +## Plugin Caveats + +- Tyk Gateway manages plugins for each API within the same process. +- For [gRPC plugins](/api-management/plugins/rich-plugins#overview-1), Tyk Gateway can only be configured to integrate with one gRPC server. +- Javascript plugins only allow Pre and Post Request hooks of the API Request Lifecycle. + + +## Plugins Hub + +{/* Want to try and get a design layout setup for this that uses stylesheets from home page to offer similar layout */} + +Welcome to the Tyk Plugins Hub, dedicated to providing you with a curated list of resources that showcase how to develop Tyk Plugins. + +[Tyk Plugins](/api-management/plugins/overview#) are a powerful tool that allows you to develop custom middleware that can intercept requests at different stages of the request lifecycle, modifying/transforming headers and body content. + +Tyk has extensive support for writing custom plugins using a wide range of languages, most notably: Go, Python, Javascript etc. In fact, plugins can be developed using most languages via *gRPC*. + +### Blogs + +Selected blogs for plugin development are included below. Further examples are available at the Tyk [website](https://tyk.io/?s=plugin). + +1. **[Decoupling micro-services using Message-based RPC](https://medium.com/@asoorm/decoupling-micro-services-using-message-based-rpc-fa1c12409d8f)** + + - **Summary**: Explains how to write a plugin that intercepts an API request and forwards it to a gRPC server. The gRPC server processes the request and dispatches work to an RabbitMQ message queue. The source code is available in the accompanying [GitHub repository](https://github.com/asoorm/tyk-rmq-middleware) + +2. **[How to configure a gRPC server using Tyk](https://tyk.io/blog/how-to-configure-a-grpc-server-using-tyk/)** + + - **Summary**: Explains how to configure a Python implementation of a gRPC server to add additional logic to API requests. During the request lifecycle, the Tyk-Gateway acts as a gRPC client that contacts the Python gRPC server, providing additional custom logic. + +3. **[How to deploy Python plugins in Tyk running On Kubernetes](https://tyk.io/blog/how-to-deploy-python-plugins-in-tyk-running-on-kubernetes/)** + + - **Summary**: Explains how to deploy a custom Python plugin into a Tyk installation running on a Kubernetes cluster. + +### GitHub Repositories + +Here are some carefully selected GitHub repositories that will help you learn how to integrate and utilize Tyk Plugins in your development projects: + +1. **[Tyk Awesome Plugins](https://github.com/TykTechnologies/tyk-awesome-plugins)** + + - **Description**: Index of plugins developed using a variety of languages. + - **Key Features Demonstrated**: A comprehensive index for a collection of plugins that can be used with the Tyk API Gateway in areas such as: rate limiting, authentication and request transformation. The examples are developed using a diverse array of languages, including but not limited to: Python, JavaScript and Go. This broad language support ensures that developers from different backgrounds and with various language preferences can seamlessly integrate these plugins with their Tyk API Gateway implementations. + +2. **[Custom Plugin Examples](https://github.com/TykTechnologies/custom-plugin-examples/tree/master)** + + - **Description**: Index of examples for a range of plugin hooks (Pre, Post, Post-Auth and Response) developed using a variety of languages. + - **Key Features Demonstrated**: Specific examples include invoking an AWS lambda, inserting a new claim into a JWT, inject a signed JWT into authorization header, request header modification. A range of examples are available including Python, Java, Ruby, Javascript, NodeJS and Go. + +3. **[Environment For Plugin Development](https://github.com/TykTechnologies/custom-go-plugin)** + + - **Description**: Provides a docker-compose environment for developing your own custom Go plugins. + - **Key Features Demonstrated**: Showcases support for bundling plugins, uploading plugins to AWS S3 storage, test coverage etc. + + diff --git a/api-management/plugins/plugin-types.mdx b/api-management/plugins/plugin-types.mdx new file mode 100644 index 0000000000..4af1153df3 --- /dev/null +++ b/api-management/plugins/plugin-types.mdx @@ -0,0 +1,724 @@ +--- +title: "Plugin Types" +description: "Understand the different types of custom plugins and execution hooks available in the Tyk middleware chain" +keywords: "Dashboard, User Management, RBAC, Role Based Access Control, User Groups, Teams, Permissions, API Ownership, SSO, Single Sing On, Multi Tenancy" +sidebarTitle: "Plugin Types" +--- + +## Introduction + +Custom Plugins enable users to execute custom code to complete tasks specific to their use case, allowing users to complete tasks that would not otherwise be possible using Tyk’s standard middleware options. + +Tyk has a [pre-defined execution order](/api-management/traffic-transformation#request-middleware-chain) for the middleware which also includes **seven hooks** for the custom plugins. As such, users can execute, or `hook`, their plugin in these phases of the API request/response lifecycle based on their specific use case. + +## Plugin and Hook Types +This table includes all the plugin types with the relevant hooks, their place in the execution chain, description and examples: + +| Hook Type (in their execution order) | Plugin Type | HTTP Request/Response phase | Executed before/after reverse proxy to the upstream API | Details | Common Use Cases | +|--------------------------|----|---|--------------|--------------------|--------- +| Pre (Request) | Request Plugin | HTTP request | Before | The first thing to be executed, before any middleware | IP Rate Limit plugins, API Request enrichment | +| Authentication| Authentication Plugin | HTTP request | Before | Replaces Tyk's authentication & authorization middleware with your own business logic | When you need your a custom flow, for example, interfacing with legacy Auth database | +| Post-Auth (Request)| Authentication Plugin | HTTP request | Before | Executed immediately after authentication middleware | Additional special custom authentication is needed | +| Post (Request)| Request Plugin | HTTP request| Before | The final middleware to be executed during the *HTTP request* phase (see **Note** below) | Update the request before it gets to the upstream, for example, adding a header that might override another header, so we add it at the end to ensure it doesn't get overridden | +| Response Plugin| Response Plugin | HTTP Response | After | Executed after the reverse proxy to the upstream API | Executed straight after the reverse proxy returns from the upstream API to Tyk | Change the response before the user gets it, for example, change `Location` header from internal to an external URL | +| Analytics Plugin (Request+Response)| Analytics Plugin | HTTP request | After | The final middleware to be executed during the *HTTP response* phase | Change analytics records, for example, obfuscating sensitive data such as the `Authorization` header | + + + +There are two different options for the Post Plugin that is executed at the end of the request processing chain. The API-level Post Plugin is applied to all requests, whilst the [endpoint-level](/api-management/plugins/plugin-types#per-endpoint-custom-plugins) custom Golang plugin is only applied to requests made to specific endpoints. If both are configured, the endpoint-level plugin will be executed first. + + + +## Plugin Types + +Tyk supports four types of plugins: + +1. **[Request Plugin](#request-plugins)** +2. **[Authentication Plugin](#authentication-plugins)** +3. **[Response Plugin](#response-plugins)** +4. **[Analytics Plugin](#analytics-plugins)** + +## Request Plugins + +There are 4 different phases in the [request lifecycle](/api-management/traffic-transformation#request-middleware-chain) you can inject custom plugins, including [Authentication plugins](/api-management/plugins/plugin-types#authentication-plugins). There are performance advantages to picking the correct phase, and of course that depends on your use case and what functionality you need. + +### Hook Capabilities +| Functionality | Pre | Auth | Post-Auth | Post | +| :------------------------- | :---------- | :------------- | :----------- | :----------- | +| Can modify the Header | ✅ | ✅ | ✅ | ✅ +| Can modify the Body | ✅ | ✅ | ✅ |✅ +| Can modify Query Params | ✅ | ✅ | ✅ |✅ +| Can view Session1 Details (metadata, quota, context-vars, tags, etc) | ❌ | ✅ |✅ |✅ +| Can modify Session1 2 | ❌ | ✅ | ❌ |❌ +| Can Add More Than One3 | ✅ | ❌ |✅ | ✅ + +1. A [Session](/api-management/access-control/sessions-and-keys/understanding-sessions) contains allowances and identity information that is unique to each requestor + +2. You can modify the session by using your programming language's SDK for Redis. Here is an [example](https://github.com/TykTechnologies/custom-plugins/blob/master/plugins/go-auth-multiple_hook_example/main.go#L135) of doing that in Golang. + +3. For select hook locations, you can add more than one plugin. For example, in the same API request, you can have 3 Pre, 1 auth, 5 post-auth, and 2 post plugins. + +### Return Overrides / ReturnOverrides +You can have your plugin finish the request lifecycle and return a response with custom payload & headers to the requestor. + +[Read more here](/api-management/plugins/rich-plugins#returnoverrides) + +##### Python Example + +```python +from tyk.decorators import * + +@Hook +def MyCustomMiddleware(request, session, spec): + print("my_middleware: MyCustomMiddleware") + request.object.return_overrides.headers['content-type'] = 'application/json' + request.object.return_overrides.response_code = 200 + request.object.return_overrides.response_error = "{\"key\": \"value\"}\n" + return request, session +``` + +##### JavaScript Example +```javascript +var testJSVMData = new TykJS.TykMiddleware.NewMiddleware({}); + +testJSVMData.NewProcessRequest(function(request, session, config) { + request.ReturnOverrides.ResponseError = "Foobarbaz" + request.ReturnOverrides.ResponseBody = "Foobar" + request.ReturnOverrides.ResponseCode = 200 + request.ReturnOverrides.ResponseHeaders = { + "X-Foo": "Bar", + "X-Baz": "Qux" + } + return testJSVMData.ReturnData(request, {}); +}); +``` + + +## Authentication Plugins + +If you have unique authentication requirements, you can write a custom authentication plugin. + +### Session Authentication and Authorization + +A very important thing to understand when using custom authentication plugins is that Tyk will continue to perform session authentication and authorization using the information returned by your plugin. Tyk will cache this Session information. **This is necessary in order to do things like rate limiting, access control, quotas, throttling, etc.** + +Tyk will try to be clever about what to cache, but we need to help it. There are two ways to do that, with and without the `ID Extractor`. + +#### The ID Extractor + +The ID Extractor is a caching mechanism that's used in combination with Tyk Plugins. It can be used specifically with plugins that implement custom authentication mechanisms. The ID Extractor works for all rich plugins: gRPC-based plugins, Python and Lua. + +See [ID Extractor](/api-management/plugins/plugin-types#plugin-caching-mechanism) for more details. + +#### Token Metadata + +Tyk creates an in-memory object to track the rate limit, quotas, and more for each session. + +This is why we set the `token` metadata when using custom authentication middleware, in order to give Tyk a unique ID with which to track each session. + +For backwards compatibility, even when using an ID Extractor, we need to continue to set the `token` metadata. For example, when building a session object in GoLang custom middleware: + +```{.copyWrapper} +object.Session = &coprocess.SessionState{ + LastUpdated: time.Now().String(), + Rate: 5, + Per: 10, + QuotaMax: int64(0), + QuotaRenews: time.Now().Unix(), + IdExtractorDeadline: extractorDeadline, + Metadata: map[string]string{ + "token": "my-unique-token", + }, + ApplyPolicies: ["5d8929d8f56e1a138f628269"], + } +``` +[source](https://github.com/TykTechnologies/tyk-grpc-go-basicauth-jwt/blob/master/main.go#L102) + +#### Without ID Extractor + +When not using ID Extractor, Tyk will continue to cache authenticated sessions returned by custom auth plugins. We must set a unique `token` field in the Metadata (see above) that Tyk will use to cache. + +### Supported Languages + +The following languages are supported for custom authentication plugins: + +- All Rich Plugins (gRPC, Python, Lua) +- GoLang + +See the [supported languages](/api-management/plugins/overview#supported-languages) section for custom authentication plugin examples in a language of your choosing. There's also a [blog that walks you through setting up gRPC custom auth in Java](https://tyk.io/blog/how-to-setup-custom-authentication-middleware-using-grpc-and-java/). + +### Tyk Operator + +Please consult the Tyk Operator supporting documentation for examples of how to configure a Tyk Operator API to use: + +- [Go custom authentication plugin](/tyk-stack/tyk-operator/create-an-api#custom-plugin-auth-go) +- [gRPC custom authentication plugin](/tyk-stack/tyk-operator/create-an-api#custom-plugin-auth-grpc) + +## Response Plugins + +Since Tyk 3.0 we have incorporated response hooks, this type of hook allows you to modify the response object returned by the upstream. The flow is follows: + +- Tyk receives the request. +- Tyk runs the full middleware chain, including any other plugins hooks like Pre, Post, Custom Authentication, etc. +- Tyk sends the request to your upstream API. +- The request is received by Tyk and the response hook is triggered. +- Your plugin modifies the response and sends it back to Tyk. +- Tyk takes the modified response and is received by the client. + +This snippet illustrates the hook function signature: + +```python +@Hook +def ResponseHook(request, response, session, metadata, spec): + tyk.log("ResponseHook is called", "info") + # In this hook we have access to the response object, to inspect it, uncomment the following line: + # print(response) + tyk.log("ResponseHook: upstream returned {0}".format(response.status_code), "info") + # Attach a new response header: + response.headers["injectedkey"] = "injectedvalue" + return response +``` + +If working with a Tyk Classic API, you would add this configuration to the API definition: + +``` +{ + "custom_middleware": { + "response": [ + { + "name": "ResponseHook", + "path": "middleware/middleware.py" + } + ], + "driver": "python" + } +} +``` + + - `driver`: set this to the appropriate value for the plugin type (e.g. `python`, `goplugin`) + - `response`: this is the hook name. You use middleware with the `response` hook type because you want this custom middleware to process the request on its return leg of a round trip. + - `response.name`: is your function name from the plugin file. + - `response.path`: is the full or relative (to the Tyk binary) path to the plugin source file. Ensure Tyk has read access to this file. + +Starting from versions 5.0.4 and 5.1.1+ for our Go, Python and Ruby users we have introduced the `multivalue_headers` field to facilitate more flexible and efficient management of headers, particularly for scenarios involving a single header key associated with multiple values. The `multivalue_headers` field, similar to its predecessor, the `headers` field, is a key-value store. However, it can accommodate an array or list of string values for each key, instead of a single string value. This feature empowers you to represent multiple values for a single header key. Here's an example of how you might use `multivalue_headers`, using the Set-Cookie header which often has multiple values: + +``` +multivalue_headers = { + "Set-Cookie": ["sessionToken=abc123; HttpOnly; Secure", "language=en-US; Secure"], +} +``` + +In this example, Set-Cookie header has two associated values: `"sessionToken=abc123; HttpOnly; Secure"` and `"language=en-US; Secure"`. To help you understand this further, let's see how `multivalue_headers` can be used in a Tyk response plugin written in Python: + +```python +from tyk.decorators import * +from gateway import TykGateway as tyk + +@Hook +def Del_ResponseHeader_Middleware(request, response, session, metadata, spec): + # inject a new header with 2 values + new_header = response.multivalue_headers.add() + new_header.key = "Set-Cookie" + new_header.values.extend("sessionToken=abc123; HttpOnly; Secure") + new_header.values.extend("language=en-US; Secure") + + tyk.log(f"Headers content :\n {response.headers}\n----------", "info") + tyk.log(f"Multivalue Headers updated :\n {response.multivalue_headers}\n----------", "info") + + return response +``` + +In this script, we add 2 values for the `Set-Cookie` header and then log both: the traditional `headers` and the new `multivalue_headers`. This is a great way to monitor your transition to `multivalue_headers` and ensure that everything is functioning as expected. + +Please note, while the `headers` field will continue to be available and maintained for backward compatibility, we highly encourage the adoption of `multivalue_headers` for the added flexibility in handling multiple header values. + +### Go response plugins + +[Go response plugins](/api-management/plugins/golang#creating-a-custom-response-plugin) have been available since Tyk v3.2. + +### Supported Response Plugin Languages + +See [Supported Plugins](/api-management/plugins/overview#supported-languages) for details on which languages the response plugin is supported in. + +## Analytics Plugins + +Since Tyk 4.1.0 we have incorporated analytic plugins which enables editing or removal of all parts of analytics records and raw request and responses recorded by Tyk at the gateway level. This feature leverages existing Go plugin infrastructure. + +- Tyk receives the request. +- Tyk runs the full middleware chain, including any other plugins hooks like Pre, Post, Custom Authentication, etc. +- Tyk sends the request to your upstream API. +- The response is received and analytics plugin function is triggered before recording the hit to redis. +- Your plugin modifies the analytics record and sends it back to Tyk. +- Tyk takes the modified analytics record and record the hit in redis. + +Example analytics Go plugins can be found [here](https://github.com/TykTechnologies/tyk/blob/master/test/goplugins/test_goplugin.go#L149) + +An analytics plugin is configured using the `analytics_plugin` configuration block within an API Definition. This contains the following configuration parameters: + +- `enable`: Set to `true` to enable the plugin +- `func_name`: The name of the function representing the plugin +- `plugin_path`: The path to the source code file containing the function that implements the plugin + + + + + +To enable the analytics rewriting functionality, adjust the following in API definition: + +```json +{ + "analytics_plugin": { + "enable": true, + "func_name": "", + "plugin_path": "/analytics_plugin.so" + } +} +``` + + + + + +The example API Definition resource listed below listens on path */httpbin* and forwards requests upstream to *http://httpbin.org*. A Go Analytics Plugin is enabled for function *MaskAnalyticsData*, located within the */opt/tyk-gateway/plugins/example-plugin.so* shared object file. + +```yaml {linenos=table,hl_lines=["15-18"],linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: analytics-plugin +spec: + name: httpbin-analytics-plugin + active: true + protocol: http + proxy: + listen_path: /httpbin + strip_listen_path: true + target_url: http://httpbin.org + use_keyless: true + enable_detailed_recording: true + analytics_plugin: + enable: true + func_name: MaskAnalyticsData # Replace it with function name of your plugin + plugin_path: /opt/tyk-gateway/plugins/example-plugin.so # Replace it with path of your plugin file +``` + + + + + +
+ +## Advance Configuration + +There are two advance configuratin with plugin types: + +1. **[Per Endpoint Custom Plugin](#per-endpoint-custom-plugins)** +2. **[Plugin Caching Mechanism for Authentication Plugin](#plugin-caching-mechanism)** + +## Per-Endpoint Custom Plugins + +Tyk's custom plugin architecture allows you to deploy custom logic that will be invoked at certain points in the [middleware chain](/api-management/traffic-transformation#request-middleware-chain) as Tyk processes requests to your APIs. + +At the API-level, there are several points in the processing flow where custom plugins can be "hooked", as explained [here](/api-management/plugins/plugin-types#plugin-types). Each of these will be invoked for calls to any endpoint on an API. If you want to perform custom logic only for specific endpoints, you must include selective processing logic within the plugin. + +At the endpoint-level, Tyk provides the facility to attach a custom Golang plugin at the end of the request processing chain (immediately before the API-level post-plugin is executed). + +### When to use the per-endpoint custom plugin + +##### Aggregating data from multiple services + +From a custom plugin, you can make calls out to other internal and upstream APIs. You can then aggregate and process the responses, returning a single response object to the originating client. This allows you to configure a single externally facing API to simplify interaction with multiple internal services, leaving the heavy lifting to Tyk rather than standing up an aggregation service within your stack. + +##### Enforcing custom policies + +Tyk provides a very flexible middleware chain where you can combine functions to implement the access controls you require to protect your upstream services. Of course, not all scenarios can be covered by Tyk’s standard middleware functions, but you can use a custom plugin to apply whatever custom logic you require to optimize your API experience. + +##### Dynamic Routing + +With a custom plugin you can implement complex dynamic routing of requests made to a single external endpoint on to different upstream services. The flexibility of the virtual endpoint gives access to data within the request (including the key session) and also the ability to make calls to other APIs to make decisions on the routing of the request. It can operate as a super-powered URL rewrite middleware. + +### How the per-endpoint custom plugin works + +Tyk Gateway is written using Golang. This has a flexible plugin architecture which allows for custom code to be compiled separately from the gateway and then invoked natively by the gateway. When registering a custom Go plugin in the API definition, you must provide the location of the compiled plugin and also the name of the function to be invoked within that package. + +Go plugins must therefore be [compiled](/api-management/plugins/golang#plugin-compiler) and [loaded](/api-management/plugins/golang#loading-custom-go-plugins-into-tyk) into the Gateway in order that the function named in the plugin configuration in the API definition can be located and executed at the appropriate stage in the request middleware processing chain. + +The custom code within the plugin has access to contextual data such as the session object and API definition. If required, it can [terminate the request](/api-management/plugins/golang#terminating-the-request) and hence can provide a [Virtual Endpoint](/api-management/traffic-transformation/virtual-endpoints) style capability using the Go language, rather than JavaScript (as supported by the virtual endpoint middleware). This can then act as a high-performance replacement for the JavaScript virtual endpoints or for cases when you want to make use of external libraries. + +{/* proposed "summary box" to be shown graphically on each middleware page + ## Ignore Authentication middleware summary + - The Per-Endpoint Custom Plugin is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Per-Endpoint Custom Plugin can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + +### Using the Per-Endpoint Plugin with Tyk OAS APIs + +The [per-endpoint custom plugin](/api-management/plugins/plugin-types#per-endpoint-custom-plugins) provides the facility to attach a custom Go plugin at the end of the request processing chain. +This plugin allows you to add custom logic to the processing flow for the specific endpoint without adding to the processing complexity of other endpoints. +It can [terminate the request](/api-management/plugins/golang#terminating-the-request) if required, +and provides a [Virtual Endpoint](/api-management/traffic-transformation/virtual-endpoints) style capability using the Go language, rather than JavaScript (as supported by the virtual endpoint middleware). + +The middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/plugins/plugin-types#using-the-per-endpoint-plugin-with-tyk-classic-apis) page. + +#### Using Tyk OAS API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. The `path` can contain wildcards in the form of any string bracketed by curly braces, for example `{user_id}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The endpoint plugin middleware (`postPlugins`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `postPlugins` object has the following configuration: + +- `enabled`: enable the middleware for the endpoint +- `functionName`: this is the name of the Go function that will be executed when the middleware is triggered +- `path`: the relative path to the source file containing the compiled Go code + +You can chain multiple plugin functions in an array. Tyk will process them in the order they appear in the API definition. + +For example: + +```json {hl_lines=["39-45"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-endpoint-plugin", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-endpoint-plugin", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-endpoint-plugin/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingget": { + "postPlugins": [ + { + "enabled": true, + "functionName": "myUniqueFunctionName", + "path": "/middleware/myPlugin.so" + } + ] + } + } + } + } +} +``` + +In this example the per-endpoint custom plugin middleware has been configured for HTTP `GET` requests to the `/anything` endpoint. For any call made to this endpoint, Tyk will invoke the function `myUniqueFunctionName` in the file located at `/middleware/myPlugin.so`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the per-endpoint custom plugin middleware. + +#### Using API Designer + +Adding a per-endpoint custom plugin to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Go Post-Plugin middleware** + + Select **ADD MIDDLEWARE** and choose **Go Post-Plugin** from the *Add Middleware* screen. + + Adding the Go Post-Plugin middleware + +3. **Configure the middleware** + + You must provide the path to the compiled plugin and the name of the Go function that should be invoked by Tyk Gateway when the middleware is triggered. + + Configuring the per-endpoint custom plugin + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes. + + + + + You are only able to add one custom plugin to each endpoint when using the API Designer, however you can add more by editing the API definition directly in the Raw Definition editor. + + + +### Using the Per-Endpoint Plugin with Tyk Classic APIs + +The [per-endpoint custom plugin](/api-management/plugins/plugin-types#per-endpoint-custom-plugins) provides the facility to attach a custom Golang plugin at the end of the request processing chain. +This plugin allows you to add custom logic to the processing flow for the specific endpoint without adding to the processing complexity of other endpoints. +It can [terminate the request](/api-management/plugins/golang#terminating-the-request), if required, +and hence can provide a [Virtual Endpoint](/api-management/traffic-transformation/virtual-endpoints) style capability using the Go language, rather than JavaScript (as supported by the virtual endpoint middleware). + +This middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](/api-management/plugins/plugin-types#using-the-per-endpoint-plugin-with-tyk-oas-apis) page. + +#### Using Tyk Classic API Definition + +To enable the middleware you must add a new `go_plugin` object to the `extended_paths` section of your API definition. + +The `go_plugin` object has the following configuration: + +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `func_name`: this is the "symbol" or function name you are calling in your Go plugin once loaded - a function can be called by one or more APIs +- `plugin_path`: the relative path of the shared object containing the function you wish to call, one or many `.so` files can be called + +You can register multiple plugin functions for a single endpoint. Tyk will process them in the order they appear in the API definition. + +For example: +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "go_plugin": [ + { + "disabled": false, + "path": "/anything", + "method": "GET", + "plugin_path": "/middleware/myPlugin.so", + "func_name": "myUniqueFunctionName" + } + ] + } +} +``` + +In this example the per-endpoint custom plugin middleware has been configured for HTTP `GET` requests to the `/anything` endpoint. For any call made to this endpoint, Tyk will invoke the function `myUniqueFunctionName` in the file located at `/middleware/myPlugin.so`. + +#### Using API Designer + +You can use the API Designer in the Tyk Dashboard to add the per-endpoint custom plugin middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to trigger the custom plugin function. Select the **Go Plugin** plugin. + + Selecting the middleware + +2. **Locate the middleware in the raw API definition** + + Once you have selected the middleware for the endpoint, you need to switch to the *Raw Definition* view and then locate the `go_plugin` section (you can search within the text editor window). + + Locating the middleware configuration + +3. **Configure the middleware** + + Now you can directly edit the `plugin_path` and `func_name` to locate your compiled plugin function. + + Configuring the middleware + +4. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +## Plugin Caching Mechanism + +The **ID extractor** is a caching mechanism that's used in combination with Tyk Plugins. It is used specifically with plugins that implement **custom authentication mechanisms**. + +We use the term `ID` to describe any key that's used for authentication purposes. + +When a custom authentication mechanism is used, every API call triggers a call to the associated middleware function, if you're using a gRPC-based plugin this translates into a gRPC call. If you're using a native plugin -like a Python plugin-, this involves a Python interpreter call. + +The ID extractor works the following rich plugins: gRPC-based plugins, Python and Lua. + +### When to use the ID Extractor? + +The main idea of the ID extractor is to reduce the number of calls made to your plugin and cache the API keys that have been already authorized by your authentication mechanism. This means that after a successful authentication event, subsequent calls will be handled by the Tyk Gateway and its Redis cache, resulting in a performance similar to the built-in authentication mechanisms that Tyk provides. + +### When does the ID Extractor Run? + +When enabled, the ID extractor runs right before the authentication step, allowing it to take control of the flow and decide whether to call your authentication mechanism or not. + +If my ID is cached by this mechanism and my plugin isn't longer called, how do I expire it? +When you implement your own authentication mechanism using plugins, you initialise the session object from your own code. The session object has a field that's used to configure the lifetime of a cached ID, this field is called `id_extractor_deadline`. See [Plugin Data Structures](/api-management/plugins/rich-plugins#rich-plugins-data-structures) for more details. +The value of this field should be a UNIX timestamp on which the cached ID will expire, like `1507268142958`. It's an integer. + +For example, this snippet is used in a NodeJS plugin, inside a custom authentication function: + +``` +// Initialize a session state object + var session = new tyk.SessionState() + // Get the current UNIX timestamp + var timestamp = Math.floor( new Date() / 1000 ) + // Based on the current timestamp, add 60 seconds: + session.id_extractor_deadline = timestamp + 60 + // Finally inject our session object into the request object: + Obj.session = session +``` + +If you already have a plugin that implements a custom authentication mechanism, appending the `id_extractor_deadline` and setting its value is enough to activate this feature. +In the above sample, Tyk will cache the key for 60 seconds. During that time any requests that use the cached ID won't call your plugin. + +### How to enable the ID Extractor + +The ID extractor is configured on a per API basis. +The API should be a protected one and have the `enable_coprocess_auth` flag set to true, like the following definition: + +```json +{ + "name": "Test API", + "api_id": "my-api", + "org_id": "my-org", + "use_keyless": false, + "auth": { + "auth_header_name": "Authorization" + }, + "proxy": { + "listen_path": "/test-api/", + "target_url": "http://httpbin.org/", + "strip_listen_path": true + }, + "enable_coprocess_auth": true, + "custom_middleware_bundle": "bundle.zip" +} +``` + +If you're not using the Community Edition, check the API settings in the dashboard and make sure that "Custom Auth" is selected. + +The second requirement is to append an additional configuration block to your plugin manifest file, using the `id_extractor` key: + +```json +{ + "custom_middleware": { + "auth_check": { "name": "MyAuthCheck" }, + "id_extractor": { + "extract_from": "header", + "extract_with": "value", + "extractor_config": { + "header_name": "Authorization" + } + }, + "driver": "grpc" + } +} +``` + +* `extract_from` specifies the source of the ID to extract. +* `extract_with` specifies how to extract and parse the extracted ID. +* `extractor_config` specifies additional parameters like the header name or the regular expression to use, this is different for every choice, see below for more details. + + +### Available ID Extractor Sources + +#### Header Source + +Use this source to extract the key from a HTTP header. Only the name of the header is required: + +```json +{ + "id_extractor": { + "extract_from": "header", + "extract_with": "value", + "extractor_config": { + "header_name": "Authorization" + } + } +} +``` + +#### Form source + +Use this source to extract the key from a submitted form, where `param_name` represents the key of the submitted parameter: + + +```json +{ + "id_extractor": { + "extract_from": "form", + "extract_with": "value", + "extractor_config": { + "param_name": "my_param" + } + } +} +``` + + +### Available ID Extractor Modes + +#### Value Extractor + +Use this to take the value as its present. This is commonly used in combination with the header source: + +```json +{ + "id_extractor": { + "extract_from": "header", + "extract_with": "value", + "extractor_config": { + "header_name": "Authorization" + } + } +} +``` + +#### Regular Expression Extractor + +Use this to match the ID with a regular expression. This requires additional parameters like `regex_expression`, which represents the regular expression itself and `regex_match_index` which is the item index: + +```json +{ + "id_extractor": { + "extract_from": "header", + "extract_with": "regex", + "extractor_config": { + "header_name": "Authorization", + "regex_expression": "[^-]+$", + "regex_match_index": 0 + } + } +} +``` + +Using the example above, if we send a header like `prefix-d28e17f7`, given the regular expression we're using, the extracted ID value will be `d28e17f7`. + +### Example Session +Here's an example of a Session being built in GoLang custom middleware: +```{.copyWrapper} +extractorDeadline := time.Now().Add(time.Second * 5).Unix() +object.Session = &coprocess.SessionState{ + + LastUpdated: time.Now().String(), + Rate: 5, + Per: 10, + QuotaMax: int64(0), + QuotaRenews: time.Now().Unix(), + Metadata: map[string]string{ + "token": "my-unique-token", + }, + ApplyPolicies: ["5d8929d8f56e1a138f628269"], + } +``` +[source](https://github.com/TykTechnologies/tyk-grpc-go-basicauth-jwt/blob/master/main.go#L102) + +Note: When using an ID Extractor, you must set a `LastUpdated` or else token updates will not be applied. If you don't set an ID Extractor, Tyk will store session information in the cache based off the `token` field that is set in the metadata. + diff --git a/api-management/plugins/rich-plugins.mdx b/api-management/plugins/rich-plugins.mdx new file mode 100644 index 0000000000..d2956f5323 --- /dev/null +++ b/api-management/plugins/rich-plugins.mdx @@ -0,0 +1,3533 @@ +--- +title: "Rich Plugins" +description: "Learn how to write powerful custom middleware for Tyk using Python, Lua, or gRPC-supported languages" +sidebarTitle: "Rich Plugins" +--- + +import GrpcInclude from '/snippets/grpc-include.mdx'; +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + +## Introduction + +Rich plugins make it possible to write powerful middleware for Tyk. Tyk supports: + +* [Python](/api-management/plugins/rich-plugins#overview) +* [gRPC](/api-management/plugins/rich-plugins#overview-1) +* [Lua](/api-management/plugins/rich-plugins#using-lua) + +gRPC provides the ability to write plugins using many languages including C++, Java, Ruby and C#. + +The dynamically built Tyk binaries can expose and call Foreign Function Interfaces in guest languages that extend the functionality of a gateway process. + +The plugins are able to directly call some Tyk API functions from within their guest language. They can also be configured so that they hook into various points along the standard middleware chain. + + + +When using Python plugins, the middleware function names are set globally. So, if you include two or more plugins that implement the same function, the last declared plugin implementation of the function will be returned. We plan to add namespaces in the future. + + + +## How do rich plugins work ? + +### ID Extractor & Auth Plugins + +The ID Extractor is a caching mechanism that's used in combination with Tyk Plugins. It can be used specifically with plugins that implement custom authentication mechanisms. The ID Extractor works for all rich plugins: gRPC-based plugins, Python and Lua. + +See [ID Extractor](/api-management/plugins/plugin-types#plugin-caching-mechanism) for more details. + +### Interoperability + +This feature implements an in-process message passing mechanism, based on [Protocol Buffers](https://developers.google.com/protocol-buffers/), any supported languages should provide a function to receive, unmarshal and process this kind of messages. + +The main interoperability task is achieved by using [cgo](https://golang.org/cmd/cgo/) as a bridge between a supported language -like Python- and the Go codebase. + +Your C bridge function must accept and return a `CoProcessMessage` data structure like the one described in [`api.h`](https://github.com/TykTechnologies/tyk/blob/master/coprocess/api.h), where `p_data` is a pointer to the serialised data and `length` indicates the length of it. + +```{.copyWrapper} +struct CoProcessMessage { + void* p_data; + int length; +}; +``` + +The unpacked data will hold the actual `CoProcessObject` data structure. + +- `HookType` - the hook type (see below) +- `Request` - the HTTP request +- `Session` - the client's [Session](/api-management/access-control/sessions-and-keys/understanding-sessions). +- `Metadata` - the metadata from the session data above (key/value string map). +- `Spec` - the API specification data. Currently organization ID, API ID and config_data. + +```{.copyWrapper} +type CoProcessObject struct { + HookType string + Request CoProcessMiniRequestObject + Session SessionState + Metadata map[string]string + Spec map[string]string +} +``` + +### Coprocess Dispatcher + +`Coprocess.Dispatcher` describes a very simple interface for implementing the dispatcher logic, the required methods are: `Dispatch`, `DispatchEvent` and `Reload`. + +`Dispatch` accepts a pointer to a struct `CoProcessObject` (as described above) and must return an object of the same type. This method is called for every configured hook on every request. +Typically, it performs a single function call in the target language (such as `Python_DispatchHook` in `coprocess_python`), where the corresponding logic is handled—mainly because different languages have different ways of loading, referencing, or calling middleware. + +`DispatchEvent` provides a way of dispatching Tyk events to a target language. This method doesn't return any variables but does receive a JSON-encoded object containing the event data. For extensibility purposes, this method doesn't use Protocol Buffers, the input is a `[]byte`, the target language will take this (as a `char`) and perform the JSON decoding operation. + +`Reload` is called when triggering a hot reload, this method could be useful for reloading scripts or modules in the target language. + +### Coprocess Dispatcher - Hooks + +This component is in charge of dispatching your HTTP requests to the custom middleware. The list, from top to bottom, shows the order of execution. The dispatcher follows the standard middleware chain logic and provides a simple mechanism for "hooking" your custom middleware behavior, the supported hooks are: + +* **Pre**: gets executed before the request is sent to your upstream target and before any authentication information is extracted from the header or parameter list of the request. When enabled, this applies to both keyless and protected APIs. +* **AuthCheck**: gets executed as a custom authentication middleware, instead of the standard ones provided by Tyk. Use this to provide your own authentication mechanism. +* **PostKeyAuth**: gets executed right after the authentication process. +* **Post**: gets executed after the authentication, validation, throttling, and quota-limiting middleware has been executed, just before the request is proxied upstream. Use this to post-process a request before sending it to your upstream API. This is only called when using protected APIs. If you want to call a hook after the authentication but before the validation, throttling and other middleware, see **PostKeyAuth**. +* **Response**: gets executed after the upstream API replies. The arguments passed to this hook include both the request and response data. Use this to modify the HTTP response before it's sent to the client. This hook also receives the request object, the session object, the metadata and API definition associated with the request. + + + + + Response hooks are not available for native Go plugins. Python and gRPC plugins are supported. + + + + +### Coprocess Gateway API + +[`coprocess_api.go`](https://github.com/TykTechnologies/tyk/tree/master/coprocess) provides a bridge between the Gateway API and C. Any function that needs to be exported should have the `export` keyword: + +```{.copyWrapper} +//export TykTriggerEvent +func TykTriggerEvent( CEventName *C.char, CPayload *C.char ) { + eventName := C.GoString(CEventName) + payload := C.GoString(CPayload) + + FireSystemEvent(tykcommon.TykEvent(eventName), EventMetaDefault{ + Message: payload, + }) +} +``` + +You should also expect a header file declaration of this function in [`api.h`](https://github.com/TykTechnologies/tyk/blob/master/coprocess/api.h), like this: + +```{.copyWrapper} +#ifndef TYK_COPROCESS_API +#define TYK_COPROCESS_API +extern void TykTriggerEvent(char* event_name, char* payload); +#endif +``` + +The language binding will include this header file (or declare the function inline) and perform the necessary steps to call it with the appropriate arguments (like an FFI mechanism could do). As a reference, this is how this could be achieved if you're building a [Cython](http://cython.org/) module: + +```{.copyWrapper} +cdef extern: + void TykTriggerEvent(char* event_name, char* payload); + +def call(): + event_name = 'my event'.encode('utf-8') + payload = 'my payload'.encode('utf-8') + TykTriggerEvent( event_name, payload ) +``` + +### Basic usage + +The intended way of using a Coprocess middleware is to specify it as part of an API Definition: + +```{.json} +"custom_middleware": { + "pre": [ + { + "name": "MyPreMiddleware", + "require_session": false + }, + { + "name": "AnotherPreMiddleware", + "require_session": false + } + ], + "post": [ + { + "name": "MyPostMiddleware", + "require_session": false + } + ], + "post_key_auth": [ + { + "name": "MyPostKeyAuthMiddleware", + "require_session": true + } + ], + "auth_check": { + "name": "MyAuthCheck" + }, + "driver": "python" +} +``` + + +All hook types support chaining except the custom auth check (`auth_check`). + + + +--- + +## Rich Plugins Data Structures + +This section describes the data structures used by the Tyk rich plugins. + +The coprocess object is a message dispatched by Tyk to the gRPC server handling the custom plugins. + +The Tyk [Protocol Buffer definitions](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto) are intended for users to generate their own bindings using the appropriate gRPC tools for the required target language. +The remainder of this document illustrates a class diagram and explins the attributes of the protobuf messages. + +### Coprocess Object + +The `Coprocess.Object` data structure wraps a `Coprocess.MiniRequestObject` and `Coprocess.ResponseObject` It contains additional fields that are useful for users that implement their own request dispatchers, like the middleware hook type and name. +It also includes the session state object (`SessionState`), which holds information about the current key/user that's used for authentication. + +```protobuf +message Object { + HookType hook_type = 1; + string hook_name = 2; + MiniRequestObject request = 3; + SessionState session = 4; + map metadata = 5; + map spec = 6; + ResponseObject response = 7; +} +``` + +This class diagram presents the structure of the object: + + + + +#### Field Descriptions + +`hook_type` +Contains the middleware hook type: pre, post, custom auth. + +`hook_name` +Contains the hook name. + +`request` +Contains the request object, see `MiniRequestObject` for more details. + +`session` +Contains the session object, see `SessionState` for more details. + +`metadata` +Contains the metadata. This is a dynamic field. + +`spec` +Contains information about API definition, including `APIID`, `OrgID` and `config_data`. + +`response` +Contains information populated from the upstream HTTP response data, for response hooks. See [ResponseObject](#responseobject) for more details. All the field contents can be modified. + +### MiniRequestObject + +The `Coprocess.MiniRequestObject` is the main request data structure used by rich plugins. It's used for middleware calls and contains important fields like headers, parameters, body and URL. A `MiniRequestObject` is part of a `Coprocess.Object`. + +```protobuf +message MiniRequestObject { + map headers = 1; + map set_headers = 2; + repeated string delete_headers = 3; + string body = 4; + string url = 5; + map params = 6; + map add_params = 7; + map extended_params = 8; + repeated string delete_params = 9; + ReturnOverrides return_overrides = 10; + string method = 11; + string request_uri = 12; + string scheme = 13; + bytes raw_body = 14; +} +``` + +#### Field Descriptions + +`headers` +A read-only field for reading headers injected by previous middleware. Modifying this field won't alter the request headers See `set_headers` and `delete_headers` for this. + +`set_headers` +This field appends the given headers (keys and values) to the request. + +`delete_headers` +This field contains an array of header names to be removed from the request. + +`body` +Contains the request body. See `ReturnOverrides` for response body modifications. + +`raw_body` +Contains the raw request body (bytes). + +`url` +The request URL. + +`params` +A read-only field that contains the request params. Modifying this value won't affect the request params. + +`add_params` +Add paramaters to the request. + +`delete_params` +This field contains an array of parameter keys to be removed from the request. + +`return_overrides` +See `ReturnOverrides` for more information. + +`method` +The request method, e.g. GET, POST, etc. + +`request_uri` +Raw unprocessed URL which includes query string and fragments. + +`scheme` +Contains the URL scheme, e.g. `http`, `https`. + +--- + +### ResponseObject + +The `ResponseObject` exists within an [object](#coprocess-object) for response hooks. The fields are populated with the upstream HTTP response data. All the field contents can be modified. + +```protobuf +syntax = "proto3"; + +package coprocess; + +message ResponseObject { + int32 status_code = 1; + bytes raw_body = 2; + string body = 3; + map headers = 4; + repeated Header multivalue_headers = 5; +} + +message Header { + string key = 1; + repeated string values = 2; +} +``` + +#### Field Descriptions + +`status_code` +This field indicates the HTTP status code that was sent by the upstream. + +`raw_body` +This field contains the HTTP response body (bytes). It's always populated. + +`body` +This field contains the HTTP response body in string format. It's not populated if the `raw_body` contains invalid UTF-8 characters. + +`headers` +A map that contains the headers sent by the upstream. + +`multivalue_headers` +A list of headers, each header in this list is a structure that consists of two parts: a key and its corresponding values. +The key is a string that denotes the name of the header, the values are a list of strings that hold the content of the header, this is useful when the header has multiple associated values. +This field is available for Go, Python and Ruby since tyk v5.0.4 and 5.1.1+. + +--- + +### ReturnOverrides + +The `ReturnOverrides` object, when returned as part of a `Coprocess.Object`, overrides the response of a given HTTP request. It also stops the request flow and the HTTP request isn't passed upstream. The fields specified in the `ReturnOverrides` object are used as the HTTP response. +A sample usage for `ReturnOverrides` is when a rich plugin needs to return a custom error to the user. + +```protobuf +syntax = "proto3"; + +package coprocess; + +message ReturnOverrides { + int32 response_code = 1; + string response_error = 2; + map headers = 3; + bool override_error = 4; + string response_body = 5; +} +``` + +#### Field Descriptions + +`response_code` +This field overrides the HTTP response code and can be used for error codes (403, 500, etc.) or for overriding the response. + +`response_error` +This field overrides the HTTP response body. + +`headers` +This field overrides response HTTP headers. + +`override_error` +This setting provides enhanced customization for returning custom errors. It should be utilized alongside `response_body` for optimal effect. + +`response_body` +This field serves as an alias for `response_erro`r and holds the HTTP response body. + +--- + +### SessionState + + +A `SessionState` data structure is created for every authenticated request and stored in Redis. It's used to track the activity of a given key in different ways, mainly by the built-in Tyk middleware like the quota middleware or the rate limiter. +A rich plugin can create a `SessionState` object and store it in the same way built-in authentication mechanisms do. This is what a custom authentication middleware does. This is also part of a `Coprocess.Object`. +Returning a null session object from a custom authentication middleware is considered a failed authentication and the appropriate HTTP 403 error is returned by the gateway (this is the default behavior) and can be overridden by using `ReturnOverrides`. + +#### Field Descriptions + +`last_check` +No longer used. + +`allowance` +No longer in use, should be the same as `rate`. + +`rate` +The number of requests that are allowed in the specified rate limiting window. + +`per` +The number of seconds that the rate window should encompass. + +`expires` +An epoch that defines when the key should expire. + +`quota_max` +The maximum number of requests allowed during the quota period. + +`quota_renews` +An epoch that defines when the quota renews. + +`quota_remaining` +Indicates the remaining number of requests within the user's quota, which is independent of the rate limit. + +`quota_renewal_rate` +The time in seconds during which the quota is valid. So for 1000 requests per hour, this value would be 3600 while `quota_max` and `quota_remaining` would be 1000. + +`access_rights` +Defined as a `map` instance, that maps the session's API ID to an [AccessDefinition](#access-definition). The AccessDefinition defines the [access rights](/api-management/access-control/sessions-and-keys/access-rights) for the API in terms of allowed: versions and URLs(endpoints). Each URL (endpoint) has a list of allowed methods. For further details consult the tutorials for how to create a [security policy](/api-management/gateway-config-managing-classic#secure-an-api) for Tyk Cloud, Tyk Self Managed and Tyk OSS platforms. + +`org_id` +The organization this user belongs to. This can be used in conjunction with the org_id setting in the API Definition object to have tokens "owned" by organizations. + +`oauth_client_id` +This is set by Tyk if the token is generated by an OAuth client during an OAuth authorization flow. + +`basic_auth_data` +This section contains a hashed representation of the basic auth password and the hashing method used. +For further details see [BasicAuthData](#basicauthdata). + +`jwt_data` +Added to sessions where a Tyk key (embedding a shared secret) is used as the public key for signing the JWT. The JWT token's KID header value references the ID of a Tyk key. See [JWTData](#jwtdata) for an example. + +`hmac_enabled` +When set to `true` this indicates generation of a [HMAC signature](/basic-config-and-security/security/authentication-authorization/hmac-signatures) using the secret provided in `hmac_secret`. If the generated signature matches the signature provided in the *Authorization* header then authentication of the request has passed. + +`hmac_secret` +The value of the HMAC shared secret. + +`is_inactive` +Set this value to true to deny access. + +`apply_policy_id` +The policy ID that is bound to this token. + + + +Although `apply_policy_id` is still supported, it is now deprecated. `apply_policies` is now used to list your policy IDs as an array. See [here](/api-management/access-control/policies/applying-policies#policy-ids) for more details. + + + +`data_expires` +A value, in seconds, that defines when data generated by this token expires in the analytics DB (must be using Pro edition and MongoDB). + +`monitor` +Defines a [quota monitor](/api-management/gateway-events#monitoring-quota-consumption) containing a list of percentage threshold limits in descending order. These limits determine when webhook notifications are triggered for API users or an organization. Each threshold represents a percentage of the quota that, when reached, triggers a notification. See [Monitor](#monitor) for further details and an example. + +`enable_detailed_recording` +Set this value to true to have Tyk store the inbound request and outbound response data in HTTP Wire format as part of the analytics data. + +`metadata` +Metadata to be included as part of the session. This is a key/value string map that can be used in other middleware such as transforms and header injection to embed user-specific data into a request, or alternatively to query the providence of a key. + +`tags` +Tags are embedded into analytics data when the request completes. If a policy has tags, those tags will supersede the ones carried by the token (they will be overwritten). + +`alias` +As of v2.1, an Alias offers a way to identify a token in a more human-readable manner, add an Alias to a token in order to have the data transferred into Analytics later on so you can track both hashed and un-hashed tokens to a meaningful identifier that doesn't expose the security of the underlying token. + +`last_updated` +A UNIX timestamp that represents the time the session was last updated. Applicable to *Post*, *PostAuth* and *Response* plugins. When developing *CustomAuth* plugins developers should add this to the SessionState instance. + +`id_extractor_deadline` +This is a UNIX timestamp that signifies when a cached key or ID will expire. This relates to custom authentication, where authenticated keys can be cached to save repeated requests to the gRPC server. See [id_extractor](/api-management/plugins/plugin-types#plugin-caching-mechanism) and [Auth Plugins](/api-management/plugins/plugin-types#authentication-plugins) for additional information. + +`session_lifetime` +UNIX timestamp that denotes when the key will automatically expire. Any·subsequent API request made using the key will be rejected. Overrides the global session lifetime. See [Key Expiry and Deletion](/api-management/access-control/sessions-and-keys/session-lifecycle) for more information. + +`key_id` +This is the unique identifier for the access token used to authenticate the request, introduced in v5.9.0. + +--- + +### AccessDefinition + + +```protobuf +message AccessDefinition { + string api_name = 1; + string api_id = 2; + repeated string versions = 3; + repeated AccessSpec allowed_urls = 4; +} +``` + +Defined as an attribute within a [SessionState](#session-state) instance. Contains the allowed versions and URLs (endpoints) for the API that the session request relates to. Each URL (endpoint) specifies an associated list of allowed methods. See also [AccessSpec](#access-spec). + +#### Field Descriptions + +`api_name` +The name of the API that the session request relates to. + +`api_id` +The ID of the API that the session request relates to. + +`versions` +List of allowed API versions, e.g. `"versions": [ "Default" ]`. + +`allowed_urls` List of [AccessSpec](#access-spec) instances. Each instance defines a URL (endpoint) with an associated allowed list of methods. If all URLs (endpoints) are allowed then the attribute is not set. + +--- + +### AccessSpec + + +Defines an API's URL (endpoint) and associated list of allowed methods + +```protobuf +message AccessSpec { + string url = 1; + repeated string methods = 2; +} +``` + +#### Field Descriptions + +`url` +A URL (endpoint) belonging to the API associated with the request session. + +`methods` +List of allowed methods for the URL (endpoint), e.g. `"methods": [ "GET". "POST", "PUT", "PATCH" ]`. + +--- + +### BasicAuthData + +The `BasicAuthData` contains a hashed password and the name of the hashing algorithm used. This is represented by the `basic_auth_data` attribute in [SessionState](#session-state) message. + +```yaml +"basicAuthData": { + "password": , + "hash": +} +``` + +#### Field Descriptions + +`password` +A hashed password. + +`hash` +Name of the [hashing algorithm](/api-management/access-control/sessions-and-keys/key-hashing#hashing-algorithms) used to hash the password. + +--- + +### JWTData + +Added to [sessions](#session-state) where a Tyk key (embedding a shared secret) is used as the public key for signing the JWT. This message contains the shared secret. + +```yaml +"jwtData": { + "secret": "the_secret" +} +``` + +#### Field Descriptions + +`secret` +The shared secret. + +--- + +### Monitor + + +Added to a [session](#session-state) when [monitor quota thresholds](/api-management/gateway-events#monitoring-quota-consumption) are defined within the Tyk key. This message contains the quota percentage threshold limits, defined in descending order, that trigger webhook notification. + +```yaml +message Monitor { + repeated double trigger_limits = 1; +} +``` + +#### Field Descriptions + +`trigger_limits` +List of trigger limits defined in descending order. Each limit represents the percentage of the quota that must be reached in order for the webhook notification to be triggered. + +```yaml +"monitor": { + "trigger_limits": [80.0, 60.0, 50.0] +} +``` + +--- + +
+ + + +## Using Python + +### Overview + +#### Requirements + +Since v2.9, Tyk supports any currently stable [Python 3.x version](https://www.python.org/downloads/). The main requirement is to have the Python shared libraries installed. These are available as `libpython3.x` in most Linux distributions. + +- Python3-dev +- [Protobuf](https://pypi.org/project/protobuf/): provides [Protocol Buffers](https://developers.google.com/protocol-buffers/) support +- [gRPC](https://pypi.org/project/grpcio/): provides [gRPC](http://www.grpc.io/) support + +#### Important Note Regarding Performance + +Python plugins are [embedded](https://docs.python.org/3/extending/embedding.html) within the Tyk Gateway process. Tyk Gateway integrates with Python custom plugins via a [cgo](https://golang.org/cmd/cgo) bridge. + +`Tyk Gateway` <-> CGO <-> `Python Custom Plugin` + +In order to integrate with Python custom plugins, the *libpython3.x.so* shared object library is used to embed a Python interpreter directly in the Tyk Gateway. Further details can be found [here](/api-management/plugins/rich-plugins#coprocess-gateway-api) + +This allows combining the strengths of both Python and Go in a single application. However, it's essential to be aware of the potential complexities and performance implications of mixing languages, as well as the need for careful memory management when working with Python objects from Go. + +The Tyk Gateway process initialises the Python interpreter using [Py_initialize](https://docs.python.org/3/c-api/init.html#c.Py_Initialize). The Python [Global Interpreter Lock (GIL)](https://docs.python.org/3/glossary.html#term-global-interpreter-lock) allows only one thread to execute Python bytecode at a time, ensuring thread safety and simplifying memory management. While the GIL simplifies these aspects, it can limit the scalability of multi-threaded applications, particularly those with CPU-bound tasks, as it restricts parallel execution of Python code. + +In the context of custom Python plugins, API calls are queued and the Python interpreter handles requests sequentially, processing them one at a time. Subsequently, this would consume large amounts of memory, and network sockets would remain open and blocked until the API request is processed. + +#### Install the Python development packages + + + + + + +Starting from Tyk Gateway version `v5.3.0`, Python is no longer bundled with the official Tyk Gateway Docker image by default, to address security vulnerabilities in the Python libraries highlighted by [Docker Scout](https://docs.docker.com/scout/). +
+Whilst Python plugins are still supported by Tyk Gateway, if you want to use them you must extend the image to add support for Python. For further details, please refer to the [release notes](/developer-support/release-notes/gateway) for Tyk Gateway `v5.3.0`. +
+ + +If you wish to use Python plugins using Docker, you can extend the official Tyk Gateway Docker image by adding Python to it. + +This example Dockerfile extends the official Tyk Gateway image to support Python plugins by installing python and the required modules: + +```dockerfile +ARG BASE_IMAGE +FROM ${BASE_IMAGE} AS base + +FROM python:3.11-bookworm +COPY --from=base /opt/tyk-gateway/ /opt/tyk-gateway/ +RUN pip install setuptools && pip install google && pip install 'protobuf==4.24.4' + +EXPOSE 8080 80 443 + +ENV PYTHON_VERSION=3.11 +ENV PORT=8080 + +WORKDIR /opt/tyk-gateway/ + +ENTRYPOINT ["/opt/tyk-gateway/tyk" ] +CMD [ "--conf=/opt/tyk-gateway/tyk.conf" ] +``` + +To use this, you simply run `docker build` with this Dockerfile, providing the Tyk Gateway image that you would like to extend as build argument `BASE_IMAGE`. +As an example, this command will extend Tyk Gateway `v5.3.0` to support Python plugins, generating the image `tyk-gateway-python:v5.3.0`: + +```bash +docker build --build-arg BASE_IMAGE=tykio/tyk-gateway:v5.3.0 -t tyk-gateway-python:v5.3.0 . +``` + +
+ + + +```apt +apt install python3 python3-dev python3-pip build-essential +``` + +#### Install the Required Python Modules + +Make sure that "pip" is available in your system, it should be typically available as "pip", "pip3" or "pipX.X" (where X.X represents the Python version): + +```pip3 +pip3 install protobuf grpcio +``` + + + + + +```yum +yum install python3-devel python3-setuptools +python3 -m ensurepip +``` + +#### Install the Required Python Modules + +Make sure that "pip" is now available in your system, it should be typically available as "pip", "pip3" or "pipX.X" (where X.X represents the Python version): + +```pip3 +pip3 install protobuf grpcio +``` + + + +
+ +#### Python versions + +Newer Tyk versions provide more flexibility when using Python plugins, allowing the users to set which Python version to use. By default, Tyk will try to use the latest version available. + +To see the Python initialisation log, run the Tyk gateway in debug mode. + +To use a specific Python version, set the `python_version` flag under `coprocess_options` in the Tyk Gateway configuration file (tyk.conf). + + + +Tyk doesn't support Python 2.x. + + + +#### Troubleshooting + +To verify that the required Python Protocol Buffers module is available: + +```python3 +python3 -c 'from google import protobuf' +``` + +No output is expected from this command on successful setups. + +#### How do I write Python Plugins? + +We have created [a demo Python plugin repository](https://github.com/TykTechnologies/tyk-plugin-demo-python). + +The project implements a simple middleware for header injection, using a Pre hook (see [Tyk custom middleware hooks](/api-management/plugins/rich-plugins#coprocess-dispatcher-hooks). A single Python script contains the code for it, see [middleware.py](https://github.com/TykTechnologies/tyk-plugin-demo-python/blob/master/middleware.py). + + +### Custom Authentication Plugin Tutorial + +#### Introduction +This tutorial will guide you through the creation of a custom authentication plugin, written in Python. +A custom authentication plugin allows you to implement your own authentication logic and override the default Tyk authentication mechanism. The sample code implements a very simple key check; currently it supports a single, hard-coded key. It could serve as a starting point for your own authentication logic. We have tested this plugin with Ubuntu 14. + +The code used in this tutorial is also available in [this GitHub repository](https://github.com/TykTechnologies/tyk-plugin-demo-python). + +#### Requirements + +* Tyk API Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See [here](/tyk-self-managed/install) for more installation options. + +##### Dependencies + +* The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from [https://github.com/TykTechnologies/tyk-cli](https://github.com/TykTechnologies/tyk-cli) +* In Tyk 2.8 the Tyk CLI is part of the gateway binary, you can find more information by running "tyk help bundle". +* Python 3.4 + +#### Create the Plugin +The first step is to create a new directory for your plugin file: + +```bash +mkdir ~/my-tyk-plugin +cd ~/my-tyk-plugin +``` + +Next you need to create a manifest file. This file contains information about our plugin file structure and how you expect it to interact with the API that will load it. +This file should be named `manifest.json` and needs to contain the following content: + +```json +{ + "file_list": [ + "middleware.py" + ], + "custom_middleware": { + "driver": "python", + "auth_check": { + "name": "MyAuthMiddleware" + } + } +} +``` + +* The `file_list` block contains the list of files to be included in the bundle, the CLI tool expects to find these files in the current working directory. +* The `custom_middleware` block contains the middleware settings like the plugin driver we want to use (`driver`) and the hooks that our plugin will expose. You use the `auth_check` for this tutorial. For other hooks see [here](/api-management/plugins/rich-plugins#coprocess-dispatcher-hooks). +* The `name` field references the name of the function that you implement in your plugin code: `MyAuthMiddleware`. +* You add an additional file called `middleware.py`, this will contain the main implementation of our middleware. + + + + + Your bundle should always contain a file named `middleware.py` as this is the entry point file. + + + +##### Contents of middleware.py + +You import decorators from the Tyk module as this gives you the `Hook` decorator, and you import [Tyk Python API helpers](/api-management/plugins/rich-plugins#tyk-python-api-methods) + +You implement a middleware function and register it as a hook, the input includes the request object, the session object, the API meta data and its specification: + +```python +from tyk.decorators import * +from gateway import TykGateway as tyk + +@Hook +def MyAuthMiddleware(request, session, metadata, spec): + auth_header = request.get_header('Authorization') + if auth_header == '47a0c79c427728b3df4af62b9228c8ae': + tyk.log("I'm logged!", "info") + tyk.log("Request body" + request.object.body, "info") + tyk.log("API config_data" + spec['config_data'], "info") + session.rate = 1000.0 + session.per = 1.0 + metadata["token"] = "47a0c79c427728b3df4af62b9228c8ae" + return request, session, metadata +``` + + +You can modify the `manifest.json` to add as many files as you want. Files that aren't listed in the `manifest.json` file will be ignored when building the plugin bundle. + +#### Building the Plugin + +A plugin bundle is a packaged version of the plugin, it may also contain a cryptographic signature of its contents. The `-y` flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial. For more information on the Tyk CLI tool, see [here](/api-management/plugins/overview#plugin-bundles). + +You will use the Dockerised version of the Tyk CLI tool to bundle our package. + +First, export your Tyk Gateway version to a variable. +```bash +##### THIS MUST MATCH YOUR TYK GATEWAY VERSION +$ IMAGETAG=v3.1.2 +``` + +Then run the following commands to generate a `bundle.zip` in your current directory: +```docker +$ docker run \ + --rm -w "/tmp" -v $(pwd):/tmp \ + --entrypoint "/bin/sh" -it \ + tykio/tyk-gateway:$IMAGETAG \ + -c '/opt/tyk-gateway/tyk bundle build -y' +``` + +**Success!** + +You should now have a `bundle.zip` file in the plugin directory. + +#### Publishing the Plugin + +To allow Tyk access to the plugin bundle, you need to serve this file using a web server. For this tutorial we'll use the Python built-in HTTP server (check the official docs for additional information). This server listens on port 8000 by default. To start it use: + +`python3 -m http.server` + +When the server is started our current working directory is used as the web root path, this means that our `bundle.zip` file should be accessible from the following URL: + +`http://:8000/bundle.zip` + +The Tyk Gateway fetches and loads a plugin bundle during startup time and subsequent reloads. For updating plugins using the hot reload feature, you should use different plugin bundle names as you expect them to be used for versioning purposes, e.g. bundle-1, bundle-2, etc. +If a bundle already exists, Tyk will skip the download process and load the version that's already present. + +#### Configure Tyk + +You will need to modify the Tyk global configuration file (`tyk.conf`) to use Python plugins. The following block should be present in this file: + +```json +"coprocess_options": { + "enable_coprocess": true, + "python_path_prefix": "/opt/tyk-gateway" +}, +"enable_bundle_downloader": true, +"bundle_base_url": "http://dummy-bundle-server.com/bundles/", +"public_key_path": "/path/to/my/pubkey" +``` + +##### Options + +* `enable_coprocess`: This enables the plugin +* `python_path_prefix`: Sets the path to built-in Tyk modules, this will be part of the Python module lookup path. The value used here is the default one for most installations. +* `enable_bundle_downloader`: This enables the bundle downloader +* `bundle_base_url`: This is a base URL that will be used to download the bundle. You should replace the `bundle_base_url` with the appropriate URL of the web server that's serving your plugin bundles. For now HTTP and HTTPS are supported but we plan to add more options in the future (like pulling directly from S3 buckets). You use the URL that's exposed by the test HTTP server in the previous step. +* `public_key_path`: Modify `public_key_path` in case you want to enforce the cryptographic check of the plugin bundle signatures. If the `public_key_path` isn't set, the verification process will be skipped and unsigned plugin bundles will be loaded normally. + +#### Configure an API Definition + +There are two important parameters that you need to add or modify in the API definition. +The first one is `custom_middleware_bundle` which must match the name of the plugin bundle file. If we keep this with the default name that the Tyk CLI tool uses, it will be `bundle.zip`. + +`"custom_middleware_bundle": "bundle.zip"` + +The second parameter is specific to this tutorial, and should be used in combination with `use_keyless` to allow an API to authenticate against our plugin: + +`"use_keyless": false` +`"enable_coprocess_auth": true` + +`"enable_coprocess_auth"` will instruct the Tyk gateway to authenticate this API using the associated custom authentication function that's implemented by the plugin. + +#### Configuration via the Tyk Dashboard + +To attach the plugin to an API, From the **Advanced Options** tab in the **API Designer** enter **bundle.zip** in the **Plugin Bundle ID** field. + +Plugin Options + +You also need to modify the authentication mechanism that's used by the API. +From the **Core Settings** tab in the **API Designer** select **Use Custom Authentication (Python, CoProcess, and JSVM plugins)** from the **Authentication - Authentication Mode** drop-down list. + +Advanced Options + +#### Testing the Plugin + +Now you can simply make an API call against the API for which we've loaded the Python plugin. + + +##### If Running Tyk Gateway from Source + +At this point you have your test HTTP server ready to serve the plugin bundle and the configuration with all the required parameters. +The final step is to start or restart the **Tyk Gateway** (this may vary depending on how you setup Tyk). +A separate service is used to load the Tyk version that supports Python (`tyk-gateway-python`), so we need to stop the standard one first (`tyk-gateway`): + +```service +service tyk-gateway stop +service tyk-gateway-python start +``` + +From now on you should use the following command to restart the service: + +```service +service tyk-gateway-python restart +``` + +A cURL request will be enough for testing our custom authentication middleware. + +This request will trigger a bad authentication: + +```curl +curl http://:8080/my-api/my-path -H 'Authorization: badtoken' +``` + +This request will trigger a successful authentication. You are using the token that's set by your Python plugin: + +```curl +curl http://:8080/my-api/my-path -H 'Authorization: 47a0c79c427728b3df4af62b9228c8ae' +``` + +#### What's Next? + +In this tutorial you learned how Tyk plugins work. For a production-level setup we suggest the following steps: + +* Configure Tyk to use your own key so that you can enforce cryptographic signature checks when loading plugin bundles, and sign your plugin bundles! +* Configure an appropriate web server and path to serve your plugin bundles. + + +### Add Python Plugin To Your Gateway + +#### API settings + +To add a Python plugin to your API, you must specify the bundle name using the `custom_middleware_bundle` field: + +```{.json} +{ + "name": "Tyk Test API", + "api_id": "1", + "org_id": "default", + "definition": { + "location": "header", + "key": "version" + }, + "auth": { + "auth_header_name": "authorization" + }, + "use_keyless": true, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "expires": "3000-01-02 15:04", + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [] + } + } + } + }, + "proxy": { + "listen_path": "/quickstart/", + "target_url": "http://httpbin.org", + "strip_listen_path": true + }, + "custom_middleware_bundle": "test-bundle" +} +``` + +#### Global settings + +To enable Python plugins you need to add the following block to `tyk.conf`: + +```{.copyWrapper} +"coprocess_options": { + "enable_coprocess": true, + "python_path_prefix": "/opt/tyk-gateway" +}, +"enable_bundle_downloader": true, +"bundle_base_url": "http://dummy-bundle-server.com/bundles/", +"public_key_path": "/path/to/my/pubkey", +``` + +`enable_coprocess`: enables the rich plugins feature. + +`python_path_prefix`: Sets the path to built-in Tyk modules, this will be part of the Python module lookup path. The value used here is the default one for most installations. + +`enable_bundle_downloader`: enables the bundle downloader. + +`bundle_base_url`: is a base URL that will be used to download the bundle, in this example we have `test-bundle` specified in the API settings, Tyk will fetch the URL for your specified bundle server (in the above example): `dummy-bundle-server.com/bundles/test-bundle`. You need to create and then specify your own bundle server URL. + +`public_key_path`: sets a public key, this is used for verifying signed bundles, you may omit this if unsigned bundles are used. + +### Tyk Python API methods + +Python plugins may call these Tyk API methods: + +#### store_data(key, value, ttl) + +`store_data` sets a Redis `key` with the specified `value` and `ttl`. + +#### get_data(key) + +`get_data` retrieves a Redis `key`. + +#### trigger_event(event_name, payload) + +`trigger_event` triggers an internal Tyk event, the `payload` must be a JSON object. + +#### log(msg, level) + +`log` will log a message (`msg`) using the specified `level`. + +#### log_error(*args) + +`log_error` is a shortcut for `log`, it uses the error log level. + +### Python Performance + +These are some benchmarks performed on Python plugins. Python plugins run in a standard Python interpreter, embedded inside Tyk. + +Python Performance + +Python Performance + +--- + +## Using gRPC + +### Overview + +gRPC is a very powerful framework for RPC communication across different [languages](https://www.grpc.io/docs). It was created by Google and makes heavy use of HTTP2 capabilities and the [Protocol Buffers](https://developers.google.com/protocol-buffers/) serialisation mechanism to dispatch and exchange requests between Tyk and your gRPC plugins. + +When it comes to built-in plugins, we have been able to integrate several languages like Python, Javascript & Lua in a native way: this means the middleware you write using any of these languages runs in the same process. At the time of writing, the following languages are supported: C++, Java, Objective-C, Python, Ruby, Go, C# and Node.JS. + +For supporting additional languages we have decided to integrate gRPC connections and perform the middleware operations within a gRPC server that is external to the Tyk process. Please contact us to learn more: + +
+ + + +Tyk has built-in support for gRPC backends, enabling you to build rich plugins using any of the gRPC supported languages. See [gRPC by language](http://www.grpc.io/docs/) for further details. + +#### Use Cases + +Deploying an external gRPC server to handle plugins provides numerous technical advantages: + +- Allows for independent scalability of the service from the Tyk Gateway. +- Utilizes a custom-designed server tailored to address specific security concerns, effectively mitigating various security risks associated with native plugins. + +#### gRPC Plugin Architectural Overview + +An example architecture is illustrated below. + +Using gRPC for plugins + +Here we can see that Tyk Gateway sends requests to an external Java gRPC server to handle authentication, via a CustomAuth plugin. The flow is as follows: + +- Tyk receives a HTTP request. +- Tyk serialises the request and session into a protobuf message that is dispatched to your gRPC server. +- The gRPC server performs custom middleware operations (for example, any modification of the request object). Each plugin (Pre, PostAuthKey, Post, Response etc.) is handled as separate gRPC request. +- The gRPC server sends the request back to Tyk. +- Tyk proxies the request to your upstream API. + +#### Limitations of gRPC plugins + +At the time of writing the following features are currently unsupported and unavailable in the serialised request: +- Client certificiates +- OAuth keys +- For graphQL APIs details concerning the *max_query_depth* is unavailable +- A request query parameter cannot be associated with multiple values + +#### gRPC Developer Resources + +The [Protocol Buffers](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto ) and [bindings](https://github.com/TykTechnologies/tyk/tree/master/coprocess/bindings) provided by Tyk should be used in order for successful ommunication between Tyk Gateway and your gRPC plugin server. Documentation for the protobuf messages is available in the [Rich Plugins Data Structures](/api-management/plugins/rich-plugins#rich-plugins-data-structures) page. + +You can generate supporting HTML documentation using the *docs* task in the [Taskfile](https://github.com/TykTechnologies/tyk/blob/master/coprocess/proto/Taskfile.yml) file of the [Tyk repository](https://github.com/TykTechnologies/tyk). This documentation explains the protobuf messages and services that allow gRPC plugins to handle a request made to the Gateway. Please refer to the README file within the proto folder of the tyk repository for further details. + +You may re-use the bindings that were generated for our samples or generate the bindings youself for Go, Python and Ruby, as implemented by the *generate* task in the [Taskfile](https://github.com/TykTechnologies/tyk/blob/master/coprocess/proto/Taskfile.yml) file of the [Tyk repository](https://github.com/TykTechnologies/tyk). + +If you wish to generate bindings for another target language you may generate the bindings yourself. The [Protocol Buffers](https://developers.google.com/protocol-buffers/) and [gRPC documentation](http://www.grpc.io/docs) provide specific requirements and instructions for each language. + +#### Load Balancing Between gRPC Servers + +Since Tyk 5.8.3 Tyk Gateway has had the ability to load balance between multiple gRPC servers. + +To implement this you must first specify the address of the load balanced service using the `dns:///` (note: triple slash) [protocol](https://github.com/grpc/grpc/blob/master/doc/naming.md) in Tyk Gateway's [gRPC server address](/tyk-oss-gateway/configuration#coprocess_options-coprocess_grpc_server) configuration (`TYK_GW_COPROCESSOPTIONS_COPROCESSGRPCSERVER`). Tyk will retrieve the list of addresses for each gRPC server from that service. + +You can control whether Tyk will implement load balancing using the [gRPC round robin load balancing](/tyk-oss-gateway/configuration#coprocess_options-grpc_round_robin_load_balancing)) config (`TYK_GW_COPROCESSOPTIONS_GRPCROUNDROBINLOADBALANCING`): + +- If set to `true`, Tyk will balance load between the server addresses retrieved using a round robin approach. +- If set to `false`, Tyk will implement a sticky session approach without load balancing. + +Note that Tyk will only query the DNS on start-up, so if you need to update the list of gRPC servers that you want Tyk to target, you must restart Tyk Gateway. + +If you are not load balancing, you can alternatively provide the `tcp://` address of the gRPC server in Tyk Gateway's [gRPC server address](/tyk-oss-gateway/configuration#coprocess_options-coprocess_grpc_server) configuration (`TYK_GW_COPROCESSOPTIONS_COPROCESSGRPCSERVER`) and set [gRPC round robin load balancing](/tyk-oss-gateway/configuration#coprocess_options-grpc_round_robin_load_balancing) (`TYK_GW_COPROCESSOPTIONS_GRPCROUNDROBINLOADBALANCING`) to `false`. + +--- + +### Getting Started: Key Concepts + +This document serves as a developer's guide for understanding the key concepts and practical steps for writing and configuring gRPC plugins for Tyk Gateway. It provides technical insights and practical guidance to seamlessly integrate Tyk plugins into your infrastructure through gRPC. The goal is to equip developers with the knowledge and tools needed to effectively utilize gRPC for enhancing Tyk Gateway functionalities. + +This comprehensive guide covers essential tasks, including: + +1. **Developing a gRPC Server:** Learn how to develop a gRPC server using [Tyk protocol buffers](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto). The gRPC server facilitates the execution of Tyk plugins, which offer custom middleware for various phases of the API request lifecycle. By integrating these plugins, developers can enable Tyk Gateway with enhanced control and flexibility in managing API requests, allowing for fine-grained customization and tailored processing at each stage of the request lifecycle. + +2. **Configuring Tyk Gateway:** Set up Tyk Gateway to communicate with your gRPC Server and, optionally, an external secured web server hosting the gRPC plugin bundle for API configurations. Configure Tyk Gateway to fetch the bundle configured for an API from the web server, enabling seamless integration with gRPC plugins. Specify connection settings for streamlined integration. + +3. **API Configuration:** Customize API settings within Tyk Gateway to configure gRPC plugin utilization. Define plugin hooks directly within the API Definition or remotely via an external web server for seamless request orchestration. Tyk plugins provide custom middleware for different phases of the API request lifecycle, enhancing control and flexibility. + +4. **API Testing:** Test that Tyk Gateway integrates with your gRPC server for the plugins configured for your API. + +--- + +#### Develop gRPC server + +Develop your gRPC server, using your preferred language, to handle requests from Tyk Gateway for each of the required plugin hooks. These hooks allow Tyk Gateway to communicate with your gRPC server to execute custom middleware at various stages of the API request lifecycle. + +##### Prerequisites + +The following prerequisites are necessary for developing a gRPC server that integrates with Tyk Gateway. + +####### Tyk gRPC Protocol Buffers + +A collection of [Protocol Buffer](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto) messages are available in the Tyk Gateway repository to allow Tyk Gateway to integrate with your gRPC server, requesting execution of plugin code. These messages establish a standard set of data structures that are serialised between Tyk Gateway and your gRPC Server. Developers should consult the [Rich Plugins Data Structures](/api-management/plugins/rich-plugins#rich-plugins-data-structures) page for further details. + +####### Protocol Buffer Compiler + +The protocol buffer compiler, `protoc`, should be installed to generate the service and data structures in your preferred language(s) from the [Tyk gRPC Protocol Buffer](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto) files. Developers should consult the [installation](https://grpc.io/docs/protoc-installation/) documentation at [grpc.io](https://grpc.io/) for an explanation of how to install `protoc`. + +##### Generate Bindings + +Generate the bindings (service and data structures) for your target language using the `protoc` compiler. Tutorials are available at [protobuf.dev](https://protobuf.dev/getting-started/) for your target language. + +##### Implement service + +Your gRPC server should implement the *Dispatcher* service to enable Tyk Gateway to integrate with your gRPC server. The Protocol Buffer definition for the *Dispatcher* service is listed below: + +```protobuf +service Dispatcher { + rpc Dispatch (Object) returns (Object) {} + rpc DispatchEvent (Event) returns (EventReply) {} +} +``` + +The *Dispatcher* service contains two RPC methods, *Dispatch* and *DispatchEvent*. Dispatch handles a requests made by Tyk Gateway for each plugin configured in your API. DispatchEvent receives notification of an event. + +Your *Dispatch* RPC should handle the request made by Tyk Gateway, implementing custom middleware for the intended plugin hooks. Each plugin hook allows Tyk Gateway to communicate with your gRPC server to execute custom middleware at various stages of the API request lifecycle, such as Pre, PostAuth, Post, Response etc. The Tyk Protocol Buffers define the [HookType](https://github.com/TykTechnologies/tyk/blob/master/coprocess/proto/coprocess_common.proto) enumeration to inspect the type of the intended gRPC plugin associated with the request. This is accessible as an attribute on the *Object* message, e.g. *object_message_instance.hook_type*. + +##### Developer resources + +Consult the [Tyk protocol buffers](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto) for the definition of the service and data structures that enable integration of Tyk gateway with your gRPC server. Tyk provides pre-generated [bindings](https://github.com/TykTechnologies/tyk/tree/master/coprocess/bindings) for C++, Java, Python and Ruby. + +Example tutorials are available that explain how to generate the protobuf bindings and implement a server for [Java](/api-management/plugins/rich-plugins#create-a-request-transformation-plugin-with-java), [.NET](/api-management/plugins/rich-plugins#create-custom-auth-plugin-with-dotnet) and [NodeJS](/api-management/plugins/rich-plugins#create-custom-auth-plugin-with-dotnet). + +Tyk Github repositories are also available with examples for [Ruby](https://github.com/TykTechnologies/tyk-plugin-demo-ruby) and [C#/.NET](https://github.com/TykTechnologies/tyk-plugin-demo-dotnet) + +--- + +#### Configure Tyk Gateway + +Configure Tyk Gateway to issue requests to your gRPC server and optionally, specify the URL of the web server that will serve plugin bundles. + +##### Configure gRPC server + +Modify the root of your `tyk.conf` file to include the *coprocess_options* section, similar to that listed below: + +```yaml +"coprocess_options": { + "enable_coprocess": true, + "coprocess_grpc_server": "tcp://127.0.0.1:5555", + "grpc_authority": "localhost", + "grpc_recv_max_size": 100000000, + "grpc_send_max_size": 100000000 +}, +``` + +A gRPC server can configured under the `coprocess_options` section as follows: + +- `enable_coprocess`: Enables the rich plugins feature. +- `coprocess_grpc_server`: Specifies the gRPC server URL, in this example we're using TCP. Tyk will attempt a connection on startup and keep reconnecting in case of failure. +- `grpc_recv_max_size`: Specifies the message size supported by the gateway gRPC client, for receiving gRPC responses. +- `grpc_send_max_size`: Specifies the message size supported by the gateway gRPC client for sending gRPC requests. +- `grpc_authority`: The `authority` header value, defaults to `localhost` if omitted. Allows configuration according to [RFC 7540](https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3). + +When using gRPC plugins, Tyk acts as a gRPC client and dispatches requests to your gRPC server. gRPC libraries usually set a default maximum size, for example, the official gRPC Java library establishes a 4 +MB message size [https://jbrandhorst.com/post/grpc-binary-blob-stream/](https://jbrandhorst.com/post/grpc-binary-blob-stream/). + +Configuration parameters are available for establishing a message size in both directions (send and receive). For most use cases and especially if you're dealing with multiple hooks, where the same request object is dispatched, it is recommended to set both values to the same size. + +##### Configure Web server (optional) + +Tyk Gateway can be configured to download the gRPC plugin configuration for an API from a web server. For further details related to the concept of bundling plugins please refer to [plugin bundles](/api-management/plugins/overview#plugin-bundles). + +```yaml +"enable_bundle_downloader": true, +"bundle_base_url": "https://my-bundle-server.com/bundles/", +"public_key_path": "/path/to/my/pubkey", +``` + +The following parameters can be configured: +- `enable_bundle_downloader`: Enables the bundle downloader to download bundles from a webserver. +- `bundle_base_url`: Base URL from which to serve bundled plugins. +- `public_key_path`: Public key for bundle verification (optional) + +The `public_key_path` value is used for verifying signed bundles, you may omit this if unsigned bundles are used. + +--- + +#### Configure API + +Plugin hooks for your APIs in Tyk can be configured either by directly specifying them in a configuration file on the Gateway server or by hosting the configuration externally on a web server. This section explains how to configure gRPC plugins for your API endpoints on the local Gateway or remotely from an external secured web server. + +##### Local + +This section provides examples for how to configure gRPC plugin hooks, locally within an API Definition. Examples are provided for Tyk Gateway and Tyk Operator. + +###### Tyk Gateway + +For configurations directly embedded within the Tyk Gateway, plugin hooks can be defined within your API Definition. An example snippet from a Tyk Classic API Definition is provided below: + +```yaml +"custom_middleware": { + "pre": [ + {"name": "MyPreMiddleware"} + ], + "post": [ + {"name": "MyPostMiddleware"} + ], + "auth_check": { + "name": "MyAuthCheck" + }, + "driver": "grpc" +} +``` + +For example, a Post request plugin hook has been configured with name `MyPostMiddleware`. Before the request is sent upstream Tyk Gateway will serialize the request into a [Object protobuf message](/api-management/plugins/rich-plugins#coprocess-object) with the `hook_name` property set to `MyPostMiddleware` and the `hook_type` property set to `Post`. This message will then then be dispatched to the gRPC server for processing before the request is sent upstream. + +
+ + +Ensure the plugin driver is configured as type *grpc*. Tyk will issue a request to your gRPC server for each plugin hook that you have configured. + + + +###### Tyk Operator + +The examples below illustrate how to configure plugin hooks for an API Definition within Tyk Operator. + +Setting the `driver` configuring parameter to `gRPC` instructs Tyk Gateway to issue a request to your gRPC server for each plugin hook that you have configured. + +**Pre plugin hook example** + +In this example we can see that a `custom_middleware` configuration block has been used to configure a gRPC Pre request plugin hook with name `HelloFromPre`. Before any middleware is executed Tyk Gateway will serialize the request into a [Object protobuf message](/api-management/plugins/rich-plugins#coprocess-object) with the `hook_name` property set to `HelloFromPre` and the `hook_type` property set to `Pre`. This message will then then be dispatched to the gRPC server. + +```yaml {linenos=table,hl_lines=["14-18"],linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-grpc-pre +spec: + name: httpbin-grpc-pre + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.default.svc:8000 + listen_path: /httpbin-grpc-pre + strip_listen_path: true + custom_middleware: + driver: grpc + pre: + - name: HelloFromPre + path: "" +``` + +**Post plugin hook example** + +In the example we can see that a `custom_middleware` configuration block has been used to configure a gRPC Post plugin with name `HelloFromPost`. + +Before the request is sent upstream Tyk Gateway will serialize the request and session details into a [Object protobuf message](/api-management/plugins/rich-plugins#coprocess-object) with the `hook_name` property set to `HelloFromPost` and the `hook_type` property set to `Post`. This message will then then be dispatched to the gRPC server for processing before the request is sent upstream. + +```yaml {linenos=table,hl_lines=["14-18"],linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-grpc-post +spec: + name: httpbin-grpc-post + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.default.svc:8000 + listen_path: /httpbin-grpc-post + strip_listen_path: true + custom_middleware: + driver: grpc + post: + - name: HelloFromPost + path: "" +``` + +##### Remote + +It is possible to configure your API so that it downloads a bundled configuration of your plugins from an external webserver. The bundled plugin configuration is contained within a zip file. + +A gRPC plugin bundle is similar to the [standard bundling mechanism](/api-management/plugins/overview#plugin-bundles). The standard bundling mechanism zips the configuration and plugin source code, which will be executed by Tyk. Conversely, a gRPC plugin bundle contains only the configuration (`manifest.json`), with plugin code execution being handled independently by the gRPC server. + +Bundling a gRPC plugin requires the following steps: +- Create a `manifest.json` that contains the configuration of your plugins +- Build a zip file that bundles your plugin +- Upload the zip file to an external secured webserver +- Configure your API to download your plugin bundle + +###### Create the manifest file + +The `manifest.json` file specifies the configuration for your gRPC plugins. An example `manifest.json` is listed below: + +```yaml +{ + "file_list": [], + "custom_middleware": { + "pre": [{"name": "MyPreMiddleware"}], + "post": [{"name": "MyPostMiddleware"}], + "auth_check": {"name": "MyAuthCheck"}, + "driver": "grpc" + }, + "checksum": "", + "signature": "" +} +``` + + + +The source code files, *file_list*, are empty for gRPC plugins. Your gRPC server contains the source code for handling plugins. + + + +###### Build plugin bundle + +A plugin bundle can be built using the Tyk Gateway binary and should only contain the `manifest.json` file: + +```bash +tyk bundle build -output mybundle.zip -key mykey.pem +``` + +The example above generates a zip file, name `mybundle.zip`. The zip file is signed with key `mykey.pem`. + +The resulting bundle file should then be uploaded to the webserver that hosts your plugin bundles. + +###### Configure API + +####### Tyk Gateway + +To add a gRPC plugin to your API definition, you must specify the bundle file name within the `custom_middleware_bundle` field: + +```yaml +{ + "name": "Tyk Test API", + ... ++ "custom_middleware_bundle": "mybundle.zip" +} +``` + +The value of the `custom_middleware_bundle` field will be used in combination with the gateway settings to construct a bundle URL. For example, if Tyk Gateway is configured with a webserver base URL of `https://my-bundle-server.com/bundles/` then an attempt would be made to download the bundle from `https://my-bundle-server.com/bundles/mybundle.zip`. + +####### Tyk Operator + + Currently this feature is not yet documented with a Tyk Operator example for configuring an API to use plugin bundles. For further details please reach out and contact us on the [community support forum](https://community.tyk.io). + +--- + +#### Test your API Endpoint + +It is crucial to ensure the security and reliability of your gRPC server. As the developer, it is your responsibility to verify that your gRPC server is secured and thoroughly tested with appropriate test coverage. Consider implementing unit tests, integration tests and other testing methodologies to ensure the robustness of your server's functionality and security measures. This step ensures that the Tyk Gateway properly communicates with your gRPC server and executes the custom logic defined by the plugin hooks. + +Test the API endpoint using tools like *Curl* or *Postman*. Ensure that your gRPC server is running and the gRPC plugin(s) are functioning. An example using *Curl* is listed below: + +```bash +curl -X GET https://www.your-gateway-server.com:8080/api/path +``` + +Replace `https://www.your-gateway-server.com:8080/api/path` with the actual endpoint of your API. + +--- + +#### Summary + +This guide has explained the key concepts and processes for writing gRPC plugins that integrate with Tyk Gateway. The following explanations have been given: + +- Prerequisites for developing a gRPC server for your target language. +- The *Dispatcher* service interface. +- How to configure Tyk Gateway to integrate with your gRPC server. +- How to configure Tyk Gateway with an optional external web server for fetching plugin configuration. +- How to configure gRPC plugins for your APIs. +- How to test your API integration with your gRPC server using curl. + +--- + +#### What's Next? + +- Consult the [Protocol Buffer messages](/api-management/plugins/rich-plugins#rich-plugins-data-structures) that Tyk Gateway uses when making a request to a gRPC server. +- Visit tutorial guides that explain how to implement a [Java](/api-management/plugins/rich-plugins#create-a-request-transformation-plugin-with-java), [.NET](/api-management/plugins/rich-plugins#create-custom-auth-plugin-with-dotnet) and [NodeJS](/api-management/plugins/rich-plugins#create-custom-auth-plugin-with-dotnet) gRPC server. +- Visit our [plugins hub](/api-management/plugins/overview#plugins-hub) to explore further gRPC development examples and resources. + +--- + + + + +### Getting Started: Creating A Python gRPC Server + +In the realm of API integration, establishing seamless connections between services is paramount. + +Understanding the fundamentals of gRPC server implementation is crucial, especially when integrating with a Gateway solution like Tyk. This guide aims to provide practical insights into this process, starting with the basic principles of how to implement a Python gRPC server that integrates with Tyk Gateway. + +#### Objectives + +By the end of this guide, you will be able to implement a gRPC server that will integrate with Tyk Gateway, setting the stage for further exploration in subsequent parts: + +- Establishing the necessary tools, Python libraries and gRPC service definition for implementing a gRPC server that integrates with Tyk Gateway. +- Developing a basic gRPC server that echoes the request payload to the console, showcasing the core principles of integration. +- Configuring Tyk Gateway to interact with our gRPC server, enabling seamless communication between the two services. + +Before implementing our first gRPC server it is first necessary to understand the service interface that defines how Tyk Gateway integrates with a gRPC server. + + +#### Tyk Dispatcher Service + +The *Dispatcher* service, defined in the [coprocess_object.proto](https://github.com/TykTechnologies/tyk/blob/master/coprocess/proto/coprocess_object.proto) file, contains the *Dispatch* RPC method, invoked by Tyk Gateway to request remote execution of gRPC plugins. Tyk Gateway dispatches accompanying data relating to the original client request and session. The service definition is listed below: + +```protobuf +service Dispatcher { + rpc Dispatch (Object) returns (Object) {} + rpc DispatchEvent (Event) returns (EventReply) {} +} +``` + +On the server side, we will implement the *Dispatcher* service methods and a gRPC server to handle requests from Tyk Gateway. The gRPC infrastructure decodes incoming requests, executes service methods and encodes service responses. + +Before we start developing our gRPC server we need to setup our development environment with the supporting libraries and tools. + + +#### Prerequisites + +Firstly, we need to download the [Tyk Protocol Buffers](https://github.com/TykTechnologies/tyk/tree/master/coprocess/proto) and install the Python protoc compiler. + +We are going to use the *protoc* compiler to generate the supporting classes and data structures to implement the *Dispatcher* service. + + +##### Tyk Protocol Buffers + +Issue the following command to download and extract the Tyk Protocol Buffers from the Tyk GitHub repository: + +```bash +curl -sL "https://github.com/TykTechnologies/tyk/archive/master.tar.gz " -o tyk.tar.gz && \ + mkdir tyk && \ + tar -xzvf tyk.tar.gz --strip-components=1 -C tyk && \ + mv tyk/coprocess/proto/* . && \ + rm -r tyk tyk.tar.gz +``` + +##### Install Dependencies + +We are going to setup a Python virtual environment and install some supporting dependencies. Assuming that you have Python [virtualenv](https://virtualenv.pypa.io/en/latest/) already installed, then issue the following commands to setup a Python virtual environment containing the grpcio and grpcio-tools libraries: + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install –upgrade pip +pip install grpcio grpcio-tools grpcio-reflection +``` + +The [grpcio](https://pypi.org/project/grpcio/) library offers essential functionality to support core gRPC features such as message serialisation and deserialisation. The [grpcio-tools](https://pypi.org/project/grpcio-tools/) library provides the Python *protoc* compiler that we will use to generate the supporting classes and data structures to implement our gRPC server. The [grpcio-reflection](https://pypi.org/project/grpcio-reflection/) library allows clients to query information about the services and methods provided by a gRPC server at runtime. It enables clients to dynamically discover available services, their RPC methods, in addition to the message types and field names associated with those methods. + + +##### Install grpcurl + +Follow the [installation instructions](https://github.com/fullstorydev/grpcurl?tab=readme-ov-file#installation) to install grpcurl. We will use grpcurl to send test requests to our gRPC server. + + +##### Generate Python Bindings + +We are now able to generate the Python classes and data structures to allow us to implement our gRPC server. To accomplish this we will use the Python *protoc* command as listed below: + +```bash +python -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. *.proto +``` + +This compiles the Protocol Buffer files (*.proto) from the current working directory and generates the Python classes representing the Protocol Buffer messages and services. A series of *.py* files should now exist in the current working directory. We are interested in the *coprocess_object_pb2_grpc.py* file, containing a default implementation of *Tyk’s Dispatcher* service. + +Inspect the generated Python file, *coprocess_object_pb2_grpc.py*, containing the *DispatcherServicer* class: + +```python +class DispatcherServicer(object): + """ GRPC server interface, that must be implemented by the target language """ + def Dispatch(self, request, context): + """ Accepts and returns an Object message """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def DispatchEvent(self, request, context): + """ Dispatches an event to the target language """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') +``` + +This superclass contains a default stub implementation for the **Dispatch** and **DispatchEvent** RPC methods, each defining request and context parameters: + +The *request* parameter allows our server to access the message payload sent by Tyk Gateway. We can use this data, pertaining to the request and session, to process and generate a response. + +The *context* parameter provides additional information and functionalities related to the RPC call, such as timeout limits, cancelation signals etc. This is a [grpc.ServicerContext](https://grpc.github.io/grpc/python/grpc.html#grpc.ServicerContext) or a [grpc.aio.ServicerContext](https://grpc.github.io/grpc/python/grpc_asyncio.html#grpc.aio.ServicerContext), object depending upon whether a synchronous or AsyncIO gRPC server is implemented. + +In the next step we will implement a subclass that will handle requests made by Tyk Gateway for remote execution of custom plugins. + + +#### Implement Dispatcher Service + +We will now develop the *Dispatcher* service, adding implementations of the *Dispatch* and *DispatchEvent* methods, to allow our gRPC server to integrate with Tyk Gateway. Before we continue, create a file, *async_server.py*, within the same folder as the generated Protocol Buffer (.proto) files. + + +##### Dispatch + +Our implementation of the Dispatch RPC method will deserialize the request payload and output to the console as JSON format. This serves as a useful development and debugging aid, allowing inspection of the request and session state dispatched by Tyk Gateway to our gRPC server. + +Copy and paste the following source code into the *async_server.py* file. Notice that we have used type hinting to aid readability. The type hints are located within the type hint files (.pyi) we generated with the protoc compiler. + + +```python +import asyncio +import grpc +import json +import signal +import logging +from google.protobuf.json_format import MessageToJson +from grpc_reflection.v1alpha import reflection +import coprocess_object_pb2_grpc +import coprocess_object_pb2 +from coprocess_common_pb2 import HookType +from coprocess_session_state_pb2 import SessionState +class PythonDispatcher(coprocess_object_pb2_grpc.DispatcherServicer): + async def Dispatch( + self, object: coprocess_object_pb2.Object, context: grpc.aio.ServicerContext + ) -> coprocess_object_pb2.Object: + logging.info(f"STATE for {object.hook_name}\n{MessageToJson(object)}\n") + if object.hook_type == HookType.Pre: + logging.info(f"Pre plugin name: {object.hook_name}") + logging.info(f"Activated Pre Request plugin from API: {object.spec.get('APIID')}") + elif object.hook_type == HookType.CustomKeyCheck: + logging.info(f"CustomAuth plugin: {object.hook_name}") + logging.info(f"Activated CustomAuth plugin from API: {object.spec.get('APIID')}") + elif object.hook_type == HookType.PostKeyAuth: + logging.info(f"PostKeyAuth plugin name: {object.hook_name}") + logging.info(f"Activated PostKeyAuth plugin from API: {object.spec.get('APIID')}") + elif object.hook_type == HookType.Post: + logging.info(f"Post plugin name: {object.hook_name}") + logging.info(f"Activated Post plugin from API: {object.spec.get('APIID')}") + elif object.hook_type == HookType.Response: + logging.info(f"Response plugin name: {object.hook_name}") + logging.info(f"Activated Response plugin from API: {object.spec.get('APIID')}") + logging.info("--------\n") + return object +``` + +Our *Dispatch* RPC method accepts the two parameters, *object* and *context*. The object parameter allows us to inspect the state and session of the request object dispatched by Tyk Gateway, via accessor methods. The *context* parameter can be used to set timeout limits etc. associated with the RPC call. + +The important takeaways from the source code listing above are: + +- The [MessageToJson](https://googleapis.dev/python/protobuf/latest/google/protobuf/json_format.html#google.protobuf.json_format.MessageToJson) function is used to deserialize the request payload as JSON. +- In the context of custom plugins we access the *hook_type* and *hook_name* attributes of the *Object* message to determine which plugin to execute. +- The ID of the API associated with the request is accessible from the spec dictionary, *object.spec.get('APIID')*. + +An implementation of the *Dispatch* RPC method must return the object payload received from Tyk Gateway. The payload can be modified by the service implementation, for example to add or remove headers and query parameters before the request is sent upstream. + + +##### DispatchEvent + +Our implementation of the *DispatchEvent* RPC method will deserialize and output the event payload as JSON. Append the following source code to the *async_server.py* file: + +```python + async def DispatchEvent( + self, event: coprocess_object_pb2.Event, context: grpc.aio.ServicerContext + ) -> coprocess_object_pb2.EventReply: + event = json.loads(event.payload) + http://logging.info (f"RECEIVED EVENT: {event}") + return coprocess_object_pb2.EventReply() +``` + +The *DispatchEvent* RPC method accepts the two parameters, *event* and *context*. The event parameter allows us to inspect the payload of the event dispatched by Tyk Gateway. The context parameter can be used to set timeout limits etc. associated with the RPC call. + +The important takeaways from the source code listing above are: + +- The event data is accessible from the *payload* attribute of the event parameter. +- An implementation of the *DispatchEvent* RPC method must return an instance of *coprocess_object_pb2.EventReply*. + + +#### Create gRPC Server + +Finally, we will implement an AsyncIO gRPC server to handle requests from Tyk Gateway to the *Dispatcher* service. We will add functions to start and stop our gRPC server. Finally, we will use *grpcurl* to issue a test payload to our gRPC server to test that it is working. + + +##### Develop gRPC Server + +Append the following source code from the listing below to the *async_server.py* file: + +```python +async def serve() -> None: + server = grpc.aio.server() + coprocess_object_pb2_grpc.add_DispatcherServicer_to_server( + PythonDispatcher(), server + ) + listen_addr = "[::]:50051" + SERVICE_NAMES = ( + coprocess_object_pb2.DESCRIPTOR.services_by_name["Dispatcher"].full_name, + reflection.SERVICE_NAME, + ) + + reflection.enable_server_reflection(SERVICE_NAMES, server) + server.add_insecure_port(listen_addr) + + logging.info ("Starting server on %s", listen_addr) + + await server.start() + await server.wait_for_termination() + +async def shutdown_server(server) -> None: + http://logging.info ("Shutting down server...") + await server.stop(None) +``` + +The *serve* function starts the gRPC server, listening for requests on port 50051 with reflection enabled. + +Clients can use reflection to list available services, obtain their RPC methods and retrieve their message types and field names dynamically. This is particularly useful for tooling and debugging purposes, allowing clients to discover server capabilities without prior knowledge of the service definitions. + + + +**note** + +A descriptor is a data structure that describes the structure of the messages, services, enums and other elements defined in a .proto file. The purpose of the descriptor is primarily metadata: it provides information about the types and services defined in the protocol buffer definition. The *coprocess_object_pb2.py* file that we generated using *protoc* contains a DESCRIPTOR field that we can use to retrieve this metadata. For further details consult the documentation for the Google's protobuf [FileDescriptor](https://googleapis.dev/python/protobuf/latest/google/protobuf/descriptor.html#google.protobuf.descriptor.FileDescriptor.services_by_name) class. + + + +The *shutdown_server* function stops the gRPC server via the *stop* method of the server instance. + +The key takeaways from the source code listing above are: + +- An instance of a gRPC server is created using *grpc.aio.server()*. +- A service implementation should be registered with the gRPC server. We register our *PythonDispatcher* class via *coprocess_object_pb2_grpc.add_DispatcherServicer_to_server(PythonDispatcher(), server)*. +- Reflection can be enabled to allow clients to dynamically discover the services available at a gRPC server. We enabled our *Dispatcher* service to be discovered via *reflection.enable_server_reflection(SERVICE_NAMES, server)*. SERVICE_NAMES is a tuple containing the full names of two gRPC services: the *Dispatcher* service obtained by using the DESCRIPTOR field within the *coprocess_object_pb2* module and the other being the standard reflection service. +- The server instance should be started via invoking and awaiting the *start* and *wait_for_termination* methods of the server instance. +- A port may be configured for the server. In this example we configured an insecure port of 50051 on the server instance via the [add_insecure_port function](https://grpc.github.io/grpc/python/grpc.html#grpc.Server.add_insecure_port). It is also possible to add a secure port via the [add_secure_port](https://grpc.github.io/grpc/python/grpc.html#grpc.Server.add_secure_port) method of the server instance, which accepts the port number in addition to an SSL certificate and key to enable TLS encryption. +- The server instance can be stopped via its stop method. + +Finally, we will allow our server to terminate upon receipt of SIGTERM and SIGINT signals. To achieve this, append the source code listed below to the *async_server.py* file. + +```python +def handle_sigterm(sig, frame) -> None: + asyncio.create_task(shutdown_server(server)) + +async def handle_sigint() -> None: + loop = asyncio.get_running_loop() + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(sig, loop.stop) + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + server = None + signal.signal(signal.SIGTERM, handle_sigterm) + try: + asyncio.get_event_loop().run_until_complete(serve()) + except KeyboardInterrupt: + pass +``` + + +##### Start gRPC Server + +Issue the following command to start the gRPC server: + +```bash +python3 -m async_server +``` + +A message should be output on the console, displaying the port number and confirming that the gRPC server has started. + + +##### Test gRPC Server + +To test our gRPC server is working, issue test requests to the *Dispatch* and *DispatchEvent* methods, using *grpcurl*. + + +####### Send Dispatch Request + +Use the *grpcurl* command to send a test dispatch request to our gRPC server: + +```bash +grpcurl -plaintext -d '{ + "hookType": "Pre", + "hookName": "MyPreCustomPluginForBasicAuth", + "request": { + "headers": { + "User-Agent": "curl/8.1.2", + "Host": "tyk-gateway.localhost:8080", + "Authorization": "Basic ZGV2QHR5ay5pbzpwYXN0cnk=", + "Accept": "*/*" + }, + "url": "/basic-authentication-valid/get", + "returnOverrides": { + "responseCode": -1 + }, + "method": "GET", + "requestUri": "/basic-authentication-valid/get", + "scheme": "https" + }, + "spec": { + "bundle_hash": "d41d8cd98f00b204e9800998ecf8427e", + "OrgID": "5e9d9544a1dcd60001d0ed20", + "APIID": "04e911d3012646d97fcdd6c846fafc4b" + } +}' localhost:50051 coprocess.Dispatcher/Dispatch +``` + +Inspect the console output of your gRPC server. It should echo the payload that you sent in the request. + + +####### Send DispatchEvent Request + +Use the grpcurl command to send a test event payload to our gRPC server: + +```bash +grpcurl -plaintext -d '{"payload": "{\"event\": \"test\"}"}' localhost:50051 coprocess.Dispatcher/DispatchEvent +``` + +Inspect the console output of your gRPC server. It should display a log similar to that shown below: + +```bash +INFO:root:RECEIVED EVENT: {'event': 'test'} +``` + +The response received from the server should be an empty event reply, similar to that shown below: + +```bash +grpcurl -plaintext -d '{"payload": "{\"event\": \"test\"}"}' localhost:50051 coprocess.Dispatcher/DispatchEvent +{} +``` + +At this point we have tested, independently of Tyk Gateway, that our gRPC Server can handle an example request payload for gRPC plugin execution. In the next section we will create a test environment for testing that Tyk Gateway integrates with our gRPC server for API requests. + + +#### Configure Test Environment + +Now that we have implemented and started a gRPC server, Tyk Gateway needs to be configured to integrate with it. To achieve this we will enable the coprocess feature and configure the URL of the gRPC server. + +We will also create an API so that we can test that Tyk Gateway integrates with our gRPC server. + + +##### Configure Tyk Gateway + +Within the root of the *tyk.conf* file, add the following configuration, replacing host and port with values appropriate for your environment: + +```yaml +"coprocess_options": { + "enable_coprocess": true, + "coprocess_grpc_server": "tcp://host:port" +} +``` + +Alternatively, the following environment variables can be set in your .env file: + +```bash +TYK_GW_COPROCESSOPTIONS_ENABLECOPROCESS=true +TYK_GW_COPROCESSOPTIONS_COPROCESSGRPCSERVER=tcp://host:port +``` + +Replace host and port with values appropriate for your environment. + + +##### Configure API + +Before testing our gRPC server we will create and configure an API with 2 plugins: + +- **Pre Request**: Named *MyPreRequestPlugin*. +- **Response**: Named *MyResponsePlugin* and configured so that Tyk Gateway dispatches the session state with the request. + +Each plugin will be configured to use the *grpc* plugin driver. + +Tyk Gateway will forward details of an incoming request to the gRPC server, for each of the configured API plugins. + + +####### Tyk Classic API + +gRPC plugins can be configured within the *custom_middleware* section of the Tyk Classic ApiDefinition, as shown in the listing below: + +```yaml +{ + "created_at": "2024-03-231T12:49:52Z", + "api_model": {}, + "api_definition": { + ... + ... + "custom_middleware": { + "pre": [ + { + "disabled": false, + "name": "MyPreRequestPlugin", + "path": "", + "require_session": false, + "raw_body_only": false + } + ], + "post": [], + "post_key_auth": [], + "auth_check": { + "disabled": false, + "name": "", + "path": "", + "require_session": false, + "raw_body_only": false + }, + "response": [ + { + "disabled": false, + "name": "MyResponsePlugin", + "path": "", + "require_session": true, + "raw_body_only": false + } + ], + "driver": "grpc", + "id_extractor": { + "disabled": false, + "extract_from": "", + "extract_with": "", + "extractor_config": {} + } + } +} +``` + +In the above listing, the plugin driver parameter has been configured with a value of *grpc*. Two plugins are configured within the *custom_middleware* section: a *Pre Request* plugin and a *Response* plugin. + +The *Response* plugin is configured with *require_session* enabled, so that Tyk Gateway will send details for the authenticated key / user with the gRPC request. Note, this is not configured for *Pre Request* plugins that are triggered before authentication in the request lifecycle. + + +####### Tyk OAS API + +To quickly get started, a Tyk OAS API schema can be created by importing the infamous [pet store](https://petstore3.swagger.io/api/v3/openapi.json) OAS schema. Then the [findByStatus](https://petstore3.swagger.io/#/pet/findPetsByStatus) endpoint can be used for testing. + +The resulting Tyk OAS API Definition contains the OAS JSON schema with an *x-tyk-api-gateway* section appended, as listed below. gRPC plugins can be configured within the middleware section of the *x-tyk-api-gateway* that is appended at the end of the OAS schema: + +```yaml +"x-tyk-api-gateway": { + "info": { + "id": "6e2ae9b858734ea37eb772c666517f55", + "dbId": "65f457804773a600011af41d", + "orgId": "5e9d9544a1dcd60001d0ed20", + "name": "Swagger Petstore - OpenAPI 3.0 Custom Authentication", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore3.swagger.io/api/v3/" + }, + "server": { + "listenPath": { + "value": "/custom_auth", + "strip": true + }, + "authentication": { + "enabled": true, + "custom": { + "enabled": true, + "header": { + "enabled": false, + "name": "Authorization" + } + } + } + }, + "middleware": { + "global": { + "pluginConfig": { + "driver": "grpc" + } + }, + "cors": { + "enabled": false, + "maxAge": 24, + "allowedHeaders": [ + "Accept", + "Content-Type", + "Origin", + "X-Requested-With", + "Authorization" + ], + "allowedOrigins": [ + "*" + ], + "allowedMethods": [ + "GET", + "HEAD", + "POST" + ] + }, + "prePlugin": { + "api-management/plugins/overview#": [ + { + "enabled": true, + "functionName": "MyPreRequestPlugin", + "path": "" + } + ] + }, + "responsePlugin": { + "api-management/plugins/overview#": [ + { + "enabled": true, + "functionName": "MyResponsePlugin", + "path": "", + "requireSession": true + } + ] + } + } +} +``` + +In the above listing, the plugin driver parameter has been set to *grpc*. Two plugins are configured within the middleware section: a *Pre Request* plugin and a *Response* plugin. + +The *Response* plugin is configured with *requireSession* enabled, so that Tyk Gateway will send details for the authenticated key / user with the gRPC request. Note, this is not configurable for *Pre Request* plugins that are triggered before authentication in the request lifecycle. + +Tyk Gateway will forward details of an incoming request to the gRPC server, for each plugin. + + +#### Test API + +We have implemented and configured a gRPC server to integrate with Tyk Gateway. Furthermore, we have created an API that has been configured with two gRPC plugins: a *Pre Request* and *Response* plugin. + +When we issue a request to our API and observe the console output of our gRPC server we should see a JSON representation of the request headers etc. echoed in the terminal. + +Issue a request for your API in the terminal window. For example: + +```bash +curl -L http://.localhost:8080/grpc-http-bin +``` + +Observe the console output of your gRPC server. Tyk Gateway should have dispatched two requests to your gRPC server; a request for the *Pre Request* plugin and a request for the *Response* plugin. + +The gRPC server we implemented echoes a JSON representation of the request payload dispatched by Tyk Gateway. + +Note that this is a useful feature for learning how to develop gRPC plugins and understanding the structure of the request payload dispatched by Tyk Gateway to the gRPC server. However, in production environments care should be taken to avoid inadvertently exposing sensitive data such as secrets in the session. + + +#### Summary + +In this guide, we've delved into the integration of a Python gRPC server with Tyk Gateway. + +We have explained how to implement a Python gRPC server and equipped developers with the necessary tools, knowledge and capabilities to effectively utilize Tyk Gateway through gRPC services. + +The following essential groundwork has been covered: + +- Setting up tools, libraries and service definitions for the integration. +- Developing a basic gRPC server with functionality to echo the request payload, received from Tyk Gateway, in JSON format. +- Configuring Tyk Gateway for seamless communication with our gRPC server. + + +### Create a Request Transformation Plugin with Java + +This tutorial will guide you through the creation of a gRPC-based Java plugin for Tyk. +Our plugin will inject a header into the request before it gets proxied upstream. For additional information about gRPC, check the official documentation [here](https://grpc.io/docs/guides/index.html). + +The sample code that we'll use implements a request transformation plugin using Java and uses the proper gRPC bindings generated from our Protocol Buffers definition files. + +#### Requirements + +- Tyk Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See [here][1] for more installation options. +- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from [https://github.com/TykTechnologies/tyk-cli][2]. +- In Tyk 2.8 the Tyk CLI is part of the gateway binary, you can find more information by running "tyk help bundle". +- Gradle Build Tool: https://gradle.org/install/. +- gRPC tools: https://grpc.io/docs/quickstart/csharp.html#generate-grpc-code +- Java JDK 7 or higher. + +#### Create the Plugin + +##### Setting up the Java Project + +We will use the Gradle build tool to generate the initial files for our project: + +```bash +cd ~ +mkdir tyk-plugin +cd tyk-plugin +gradle init +``` + +We now have a `tyk-plugin` directory containing the basic skeleton of our application. + +Add the following to `build.gradle` + +```{.copyWrapper} +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1' + } +} + +plugins { + id "com.google.protobuf" version "0.8.1" + id "java" + id "application" + id "idea" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.3.0" + } + plugins { + grpc { + artifact = 'io.grpc:protoc-gen-grpc-java:1.5.0' + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } + generatedFilesBaseDir = "$projectDir/src/generated" +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +mainClassName = "com.testorg.testplugin.PluginServer" + +repositories { + mavenCentral() +} + +dependencies { + compile 'io.grpc:grpc-all:1.5.0' +} + +idea { + module { + sourceDirs += file("${projectDir}/src/generated/main/java"); + sourceDirs += file("${projectDir}/src/generated/main/grpc"); + } +} +``` + +##### Create the Directory for the Server Class + +```bash +cd ~/tyk-plugin +mkdir -p src/main/java/com/testorg/testplugin +``` + +##### Install the gRPC Tools + +We need to download the Tyk Protocol Buffers definition files, these files contains the data structures used by Tyk. See [Data Structures](/api-management/plugins/rich-plugins#rich-plugins-data-structures) for more information: + +```bash +cd ~/tyk-plugin +git clone https://github.com/TykTechnologies/tyk +mv tyk/coprocess/proto src/main/proto +``` + +##### Generate the Bindings + +To generate the Protocol Buffers bindings we use the Gradle build task: + +```bash +gradle build +``` + +If you need to customize any setting related to the bindings generation step, check the `build.gradle` file. + +##### Implement Server + +We need to implement two classes: one class will contain the request dispatcher logic and the actual middleware implementation. The other one will implement the gRPC server using our own dispatcher. + +From the `~/tyk-plugin/src/main/java/com/testorg/testplugin` directory, create a file named `PluginDispatcher.java` with the following code: + +```java +package com.testorg.testplugin; + +import coprocess.DispatcherGrpc; +import coprocess.CoprocessObject; + +public class PluginDispatcher extends DispatcherGrpc.DispatcherImplBase { + + @Override + public void dispatch(CoprocessObject.Object request, + io.grpc.stub.StreamObserver responseObserver) { + CoprocessObject.Object modifiedRequest = null; + + switch (request.getHookName()) { + case "MyPreMiddleware": + modifiedRequest = MyPreHook(request); + default: + // Do nothing, the hook name isn't implemented! + } + + // Return the modified request (if the transformation was done): + if (modifiedRequest != null) { + responseObserver.onNext(modifiedRequest); + }; + + responseObserver.onCompleted(); + } + + CoprocessObject.Object MyPreHook(CoprocessObject.Object request) { + CoprocessObject.Object.Builder builder = request.toBuilder(); + builder.getRequestBuilder().putSetHeaders("customheader", "customvalue"); + return builder.build(); + } +} +``` + +In the same directory, create a file named `PluginServer.java` with the following code. This is the server implementation: + +```java +package com.testorg.testplugin; + +import coprocess.DispatcherGrpc; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PluginServer { + + private static final Logger logger = Logger.getLogger(PluginServer.class.getName()); + static Server server; + static int port = 5555; + + public static void main(String[] args) throws IOException, InterruptedException { + System.out.println("Initializing gRPC server."); + + // Our dispatcher is instantiated and attached to the server: + server = ServerBuilder.forPort(port) + .addService(new PluginDispatcher()) + .build() + .start(); + + blockUntilShutdown(); + + } + + static void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } +} +``` + +To run the gRPC server we can use the following command: + +```bash +cd ~/tyk-plugin +gradle runServer +``` + +The gRPC server will listen on port 5555 (as defined in `Server.java`). In the next steps we'll setup the plugin bundle and modify Tyk to connect to our gRPC server. + + +#### Bundle the Plugin + +We need to create a manifest file within the `tyk-plugin` directory. This file contains information about our plugin and how we expect it to interact with the API that will load it. This file should be named `manifest.json` and needs to contain the following: + +```json +{ + "custom_middleware": { + "driver": "grpc", + "pre": [{ + "name": "MyPreMiddleware" + }] + } +} +``` + +- The `custom_middleware` block contains the middleware settings like the plugin driver we want to use (`driver`) and the hooks that our plugin will expose. We use the `pre` hook for this tutorial. For other hooks see [here](/api-management/plugins/rich-plugins#coprocess-dispatcher-hooks). +- The `name` field references the name of the function that we implemented in our plugin code - `MyPreMiddleware`. This will be handled by our dispatcher gRPC method in `PluginServer.java`. + +To bundle our plugin run the following command in the `tyk-plugin` directory. Check your tyk-cli install path first: + +```bash +/opt/tyk-gateway/utils/tyk-cli bundle build -y +``` + +For Tyk 2.8 use: +```bash +/opt/tyk-gateway/bin/tyk bundle build -y +``` + +A plugin bundle is a packaged version of the plugin. It may also contain a cryptographic signature of its contents. The `-y` flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial. + +For more information on the Tyk CLI tool, see [here](/api-management/plugins/overview#plugin-bundles). + +You should now have a `bundle.zip` file in the `tyk-plugin` directory. + +#### Publish the Plugin + +To publish the plugin, copy or upload `bundle.zip` to a local web server like Nginx, or Apache or storage like Amazon S3. For this tutorial we'll assume you have a web server listening on `localhost` and accessible through `http://localhost`. + + + +#### What's Next? + +In this tutorial we learned how Tyk gRPC plugins work. For a production-level setup we suggest the following: + +- Configure an appropriate web server and path to serve your plugin bundles. + +[1]: /tyk-self-managed/install +[2]: https://github.com/TykTechnologies/tyk-cli +[3]: /img/dashboard/system-management/api_settings.png +[4]: /img/dashboard/system-management/plugin_options.png + +### Create Custom Authentication Plugin with .NET + + +This tutorial will guide you through the creation of a custom authentication plugin for Tyk with a gRPC based plugin with .NET and C#. For additional information check the official gRPC [documentation](https://grpc.io/docs/guides/index.html). + +The sample code that we’ll use implements a very simple authentication layer using .NET and the proper gRPC bindings generated from our Protocol Buffers definition files. + +Using gRPC for plugins + +#### Requirements + +- Tyk Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See [here][1] for more installation options. +- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from [https://github.com/TykTechnologies/tyk-cli][2] +- In Tyk 2.8 the Tyk CLI is part of the gateway binary, you can find more information by running "tyk help bundle". +- .NET Core for your OS: https://www.microsoft.com/net/core +- gRPC tools: https://grpc.io/docs/quickstart/csharp.html#generate-grpc-code + +#### Create the Plugin + +##### Create .NET Project + +We use the .NET CLI tool to generate the initial files for our project: + +```bash +cd ~ +dotnet new console -o tyk-plugin +``` + +We now have a `tyk-plugin` directory containing the basic skeleton of a .NET application. + +From the `tyk-plugin` directory we need to install a few packages that the gRPC server requires: + +```bash +dotnet add package Grpc --version 1.6.0 +dotnet add package System.Threading.ThreadPool --version 4.3.0 +dotnet add package Google.Protobuf --version 3.4.0 +``` + +- The `Grpc` package provides base code for our server implementation. +- The `ThreadPool` package is used by `Grpc`. +- The `Protobuf` package will be used by our gRPC bindings. + +##### Install the gRPC Tools + +We need to install the gRPC tools to generate the bindings. We recommended you follow the official guide here: https://grpc.io/docs/quickstart/csharp.html#generate-grpc-code. + +Run the following Commands (both MacOS and Linux): + +```bash +cd ~/tyk-plugin +temp_dir=packages/Grpc.Tools.1.6.x/tmp +curl_url=https://www.nuget.org/api/v2/package/Grpc.Tools/ +mkdir -p $temp_dir && cd $temp_dir && curl -sL $curl_url > tmp.zip; unzip tmp.zip && cd .. && cp -r tmp/tools . && rm -rf tmp && cd ../.. +chmod -Rf +x packages/Grpc.Tools.1.6.x/tools/ +``` + +Then run the following, depending on your OS: + +**MacOS (x64)** + +```bash +export GRPC_TOOLS=packages/Grpc.Tools.1.6.x/tools/macosx_x64 +``` + +**Linux (x64)** + +```bash +export GRPC_TOOLS=packages/Grpc.Tools.1.6.x/tools/linux_x64 +``` + +The `GRPC_TOOLS` environment variable will point to the appropriate GrpcTools path that matches our operating system and architecture. The last step is to export a variable for the `protoc` program; this is the main program used to generate bindings: + +```bash +export GRPC_PROTOC=$GRPC_TOOLS/protoc +``` + +Now that we can safely run `protoc`, we can download the Tyk Protocol Buffers definition files. These files contain the data structures used by Tyk. See [Data Structures](/api-management/plugins/rich-plugins#rich-plugins-data-structures) for more information: + +```bash +cd ~/tyk-plugin +git clone https://github.com/TykTechnologies/tyk +``` + +##### Generate the bindings + +To generate the bindings, we create an empty directory and run the `protoc` tool using the environment variable that was set before: + +```bash +mkdir Coprocess +$GRPC_PROTOC -I=tyk/coprocess/proto --csharp_out=Coprocess --grpc_out=Coprocess --plugin=protoc-gen-grpc=$GRPC_TOOLS/grpc_csharp_plugin tyk/coprocess/proto/*.proto +``` + +Run the following command to check the binding directory: + +```bash +ls Coprocess +``` + +The output will look like this: + +``` +CoprocessCommon.cs CoprocessObject.cs CoprocessReturnOverrides.cs +CoprocessMiniRequestObject.cs CoprocessObjectGrpc.cs CoprocessSessionState.cs +``` + +##### Implement Server + +Create a file called `Server.cs`. + +Add the following code to `Server.cs`. + +```c# +using System; +using System.Threading.Tasks; +using Grpc.Core; + +using Coprocess; + +class DispatcherImpl : Dispatcher.DispatcherBase +{ + public DispatcherImpl() + { + Console.WriteLine("Instantiating DispatcherImpl"); + } + + + // The Dispatch method will be called by Tyk for every configured hook, we'll implement a very simple dispatcher here: + public override Task Dispatch(Coprocess.Object thisObject, ServerCallContext context) + { + // thisObject is the request object: + Console.WriteLine("Receiving object: " + thisObject.ToString()); + + // hook contains the hook name, this will be defined in our plugin bundle and the implementation will be a method in this class (DispatcherImpl), we'll look it up: + var hook = this.GetType().GetMethod(thisObject.HookName); + + // If hook is null then a handler method for this hook isn't implemented, we'll log this anyway: + if (hook == null) + { + Console.WriteLine("Hook name: " + thisObject.HookName + " (not implemented!)"); + // We return the unmodified request object, so that Tyk can proxy this in the normal way. + return Task.FromResult(thisObject); + }; + + // If there's a handler method, let's log it and proceed with our dispatch work: + Console.WriteLine("Hook name: " + thisObject.HookName + " (implemented)"); + + // This will dynamically invoke our hook method, and cast the returned object to the required Protocol Buffers data structure: + var output = hook.Invoke(this, new object[] { thisObject, context }); + return (Task)output; + } + + // MyPreMiddleware implements a PRE hook, it will be called before the request is proxied upstream and before the authentication step: + public Task MyPreMiddleware(Coprocess.Object thisObject, ServerCallContext context) + { + Console.WriteLine("Calling MyPreMiddleware."); + // We'll inject a header in this request: + thisObject.Request.SetHeaders["my-header"] = "my-value"; + return Task.FromResult(thisObject); + } + + // MyAuthCheck implements a custom authentication mechanism, it will initialize a session object if the token matches a certain value: + public Task MyAuthCheck(Coprocess.Object thisObject, ServerCallContext context) + { + // Request.Headers contains all the request headers, we retrieve the authorization token: + var token = thisObject.Request.Headers["Authorization"]; + Console.WriteLine("Calling MyAuthCheck with token = " + token); + + // We initialize a session object if the token matches "abc123": + if (token == "abc123") + { + Console.WriteLine("Successful auth!"); + var session = new Coprocess.SessionState(); + session.Rate = 1000; + session.Per = 10; + session.QuotaMax = 60; + session.QuotaRenews = 1479033599; + session.QuotaRemaining = 0; + session.QuotaRenewalRate = 120; + session.Expires = 1479033599; + + session.LastUpdated = 1478033599.ToString(); + + thisObject.Metadata["token"] = token; + thisObject.Session = session; + return Task.FromResult(thisObject); + + } + + // If the token isn't "abc123", we return the request object in the original state, without a session object, Tyk will reject this request: + Console.WriteLine("Rejecting auth!"); + return Task.FromResult(thisObject); + } +} +``` + +Create a file called `Program.cs` to instantiate our dispatcher implementation and start a gRPC server. + +Add the following code to `Program.cs`. + +```bash +using System; +using Grpc.Core; + +namespace tyk_plugin +{ + class Program + { + + // Port to attach the gRPC server to: + const int Port = 5555; + + static void Main(string[] args) + { + // We initialize a Grpc.Core.Server and attach our dispatcher implementation to it: + Server server = new Server + { + Services = { Coprocess.Dispatcher.BindService(new DispatcherImpl()) }, + Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } + }; + server.Start(); + + Console.WriteLine("gRPC server listening on " + Port); + Console.WriteLine("Press any key to stop the server..."); + Console.ReadKey(); + + server.ShutdownAsync().Wait(); + + } + } +} +``` + +To run the gRPC server use the following command from the plugin directory: + +```bash +dotnet run +``` + +The gRPC server will listen on port 5555 (as defined in `Program.cs`). In the next steps we'll setup the plugin bundle and modify Tyk to connect to our gRPC server. + +#### Bundle the Plugin + +We need to create a manifest file within the `tyk-plugin` directory. This file contains information about our plugin and how we expect it to interact with the API that will load it. This file should be named `manifest.json` and needs to contain the following: + +```json +{ + "custom_middleware": { + "driver": "grpc", + "auth_check": { + "name": "MyAuthMiddleware", + "path": "", + "raw_body_only": false, + "require_session": false + } + } +} +``` + +- The `custom_middleware` block contains the middleware settings like the plugin driver we want to use (`driver`) and the hooks that our plugin will expose. We use the `auth_check` hook for this tutorial. For other hooks see [here](/api-management/plugins/rich-plugins#coprocess-dispatcher-hooks). +- The `name` field references the name of the function that we implement in our plugin code - `MyAuthMiddleware`. This will be handled by our dispatcher gRPC method (implemented in `Server.cs`). +- The `path` field is the path to the middleware component. +- The `raw_body_only` field +- The `require_session` field, if set to `true` gives you access to the session object. It will be supplied as a session variable to your middleware processor function + + +To bundle our plugin run the following command in the `tyk-plugin` directory. Check your tyk-cli install path first: + +```bash +/opt/tyk-gateway/utils/tyk-cli bundle build -y +``` + +From Tyk v2.8 upwards you can use: +```bash +/opt/tyk-gateway/bin/tyk bundle build -y +``` + +A plugin bundle is a packaged version of the plugin. It may also contain a cryptographic signature of its contents. The `-y` flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial. + +For more information on the Tyk CLI tool, see [here](/api-management/plugins/overview#plugin-bundles). + +You should now have a `bundle.zip` file in the `tyk-plugin` directory. + +#### Publish the Plugin + +To publish the plugin, copy or upload `bundle.zip` to a local web server like Nginx, or Apache or storage like Amazon S3. For this tutorial we'll assume you have a web server listening on `localhost` and accessible through `http://localhost`. + + + +#### What's Next? + +In this tutorial we learned how Tyk gRPC plugins work. For a production-level setup we suggest the following: + +- Configure an appropriate web server and path to serve your plugin bundles. +- See the following [GitHub repo](https://github.com/TykTechnologies/tyk-plugin-demo-dotnet) for a gRPC based .NET plugin that incorporates authentication based on Microsoft SQL Server. + +[1]: /tyk-self-managed/install +[2]: https://github.com/TykTechnologies/tyk-cli +[3]: /img/dashboard/system-management/plugin_options.png +[4]: /img/dashboard/system-management/plugin_auth_mode.png + +### Create Custom Authentication Plugin with NodeJS + +This tutorial will guide you through the creation of a custom authentication plugin for Tyk with a gRPC based plugin written in NodeJS. For additional information about gRPC, check the official documentation [here](https://grpc.io/docs/guides/index.html). + +The sample code that we'll use implements a very simple authentication layer using NodeJS and the proper gRPC bindings generated from our Protocol Buffers definition files. + +gRPC Auth Diagram + +#### Requirements + +- Tyk Gateway: This can be installed using standard package management tools like Yum or APT, or from source code. See [here](/tyk-self-managed/install) for more installation options. +- The Tyk CLI utility, which is bundled with our RPM and DEB packages, and can be installed separately from [https://github.com/TykTechnologies/tyk-cli](https://github.com/TykTechnologies/tyk-cli) +- In Tyk 2.8 and upwards the Tyk CLI is part of the gateway binary, you can find more information by running "tyk help bundle". +- NodeJS v6.x.x [https://nodejs.org/en/download/](https://nodejs.org/en/download/) + +#### Create the Plugin + +##### Create NodeJS Project + +We will use the NPM tool to initialize our project, follow the steps provided by the `init` command: + +```bash +cd ~ +mkdir tyk-plugin +cd tyk-plugin +npm init +``` + +Now we'll add the gRPC package for this project: + +```bash +npm install --save grpc +``` + +##### Install gRPC Tools + +Typically to use gRPC and Protocol Buffers you need to use a code generator and generate bindings for the target language that you're using. For this tutorial we'll skip this step and use the dynamic loader that's provided by the NodeJS gRPC library. This mechanism allows a program to load Protocol Buffers definitions directly from `.proto` files. See [this section](https://grpc.io/docs/tutorials/basic/node.html#loading-service-descriptors-from-proto-files) in the gRPC documentation for more details. + +To fetch the required `.proto` files, you may use an official repository where we keep the Tyk Protocol Buffers definition files: + +```bash +cd ~/tyk-plugin +git clone https://github.com/TykTechnologies/tyk +``` + +##### Implement Server + +Now we're ready to implement our gRPC server, create a file called `main.js` in the project's directory + +Add the following code to `main.js`. + +```nodejs +const grpc = require('grpc'), + resolve = require('path').resolve + +const tyk = grpc.load({ + file: 'coprocess_object.proto', + root: resolve(__dirname, 'tyk/coprocess/proto') +}).coprocess + +const listenAddr = '127.0.0.1:5555', + authHeader = 'Authorization' + validToken = '71f6ac3385ce284152a64208521c592b' + +// The dispatch function is called for every hook: +const dispatch = (call, callback) => { + var obj = call.request + // We dispatch the request based on the hook name, we pass obj.request which is the coprocess.Object: + switch (obj.hook_name) { + case 'MyPreMiddleware': + preMiddleware(obj, callback) + break + case 'MyAuthMiddleware': + authMiddleware(obj, callback) + break + default: + callback(null, obj) + break + } +} + +const preMiddleware = (obj, callback) => { + var req = obj.request + + // req is the coprocess.MiniRequestObject, we inject a header using the "set_headers" field: + req.set_headers = { + 'mycustomheader': 'mycustomvalue' + } + + // Use this callback to finish the operation, sending back the modified object: + callback(null, obj) +} + +const authMiddleware = (obj, callback) => { + var req = obj.request + + // We take the value from the "Authorization" header: + var token = req.headers[authHeader] + + // The token should be attached to the object metadata, this is used internally for key management: + obj.metadata = { + token: token + } + + // If the request token doesn't match the "validToken" constant we return the call: + if (token != validToken) { + callback(null, obj) + return + } + + // At this point the token is valid and a session state object is initialized and attached to the coprocess.Object: + var session = new tyk.SessionState() + session.id_extractor_deadline = Date.now() + 100000000000 + obj.session = session + callback(null, obj) +} + +main = function() { + server = new grpc.Server() + server.addService(tyk.Dispatcher.service, { + dispatch: dispatch + }) + server.bind(listenAddr, grpc.ServerCredentials.createInsecure()) + server.start() +} + +main() +``` + + +To run the gRPC server run: + +```bash +node main.js +``` + +The gRPC server will listen on port `5555` (see the `listenAddr` constant). In the next steps we'll setup the plugin bundle and modify Tyk to connect to our gRPC server. + + +#### Bundle the Plugin + +We need to create a manifest file within the `tyk-plugin` directory. This file contains information about our plugin and how we expect it to interact with the API that will load it. This file should be named `manifest.json` and needs to contain the following: + +```json +{ + "custom_middleware": { + "driver": "grpc", + "auth_check": { + "name": "MyAuthMiddleware", + "path": "", + "raw_body_only": false, + "require_session": false + } + } +} +``` + +- The `custom_middleware` block contains the middleware settings like the plugin driver we want to use (`driver`) and the hooks that our plugin will expose. We use the `auth_check` hook for this tutorial. For other hooks see [here](/api-management/plugins/rich-plugins#coprocess-dispatcher-hooks). +- The `name` field references the name of the function that we implement in our plugin code - `MyAuthMiddleware`. The implemented dispatcher uses a switch statement to handle this hook, and calls the `authMiddleware` function in `main.js`. +- The `path` field is the path to the middleware component. +- The `raw_body_only` field +- The `require_session` field, if set to `true` gives you access to the session object. It will be supplied as a session variable to your middleware processor function + +To bundle our plugin run the following command in the `tyk-plugin` directory. Check your tyk-cli install path first: + +```bash +/opt/tyk-gateway/utils/tyk-cli bundle build -y +``` + +For Tyk 2.8 use: +```bash +/opt/tyk-gateway/bin/tyk bundle build -y +``` + +A plugin bundle is a packaged version of the plugin. It may also contain a cryptographic signature of its contents. The `-y` flag tells the Tyk CLI tool to skip the signing process in order to simplify the flow of this tutorial. + +For more information on the Tyk CLI tool, see [here](/api-management/plugins/overview#plugin-bundles). + +You should now have a `bundle.zip` file in the `tyk-plugin` directory. + +#### Publish the Plugin + +To publish the plugin, copy or upload `bundle.zip` to a local web server like Nginx, Apache or storage like Amazon S3. For this tutorial we'll assume you have a web server listening on `localhost` and accessible through `http://localhost`. + + + + +#### What's Next? + +In this tutorial we learned how Tyk gRPC plugins work. For a production-level setup we suggest the following: + +- Configure an appropriate web server and path to serve your plugin bundles. + +[1]: /tyk-self-managed/install +[2]: https://github.com/TykTechnologies/tyk-cli +[3]: /img/dashboard/system-management/plugin_options.png +[4]: /img/dashboard/system-management/plugin_auth_mode.png + +### Create Custom Authentication Plugin With Python + +In the realm of API security, HMAC-signed authentication serves as a foundational concept. In this developer-focused blog post, we'll use HMAC-signed authentication as the basis for learning how to write gRPC custom authentication plugins with Tyk Gateway. Why learn how to write Custom Authentication Plugins? + +- **Foundational knowledge**: Writing custom authentication plugins provides foundational knowledge of Tyk's extensibility and customization capabilities. +- **Practical experience**: Gain hands-on experience in implementing custom authentication logic tailored to specific use cases, starting with HMAC-signed authentication. +- **Enhanced control**: Exercise greater control over authentication flows and response handling, empowering developers to implement advanced authentication mechanisms beyond built-in features. + +While Tyk Gateway offers built-in support for HMAC-signed authentication, this tutorial serves as a practical guide for developers looking to extend Tyk's capabilities through custom authentication plugins. It extends the gRPC server that we developed in our [getting started guide](/api-management/plugins/rich-plugins#using-python). + +We will develop a basic gRPC server that implements the Tyk Dispatcher service with a custom authentication plugin to handle authentication keys, signed using the HMAC SHA512 algorithm. Subsequently, you will be able to make a request to your API with a HMAC signed authentication key in the *Authorization* header. Tyk Gateway will intercept the request and forward it to your Python gRPC server for HMAC signature and token verification. + +Our plugin will only verify the key against an expected value. In a production environment it will be necessary to verify the key against Redis storage. + +Before we continue ensure that you have: + +- Read and completed our getting started guide that explains how to implement a basic Python gRPC server to echo the request payload received from Tyk Gateway. This tutorial extends the source code of the tyk_async_server.py file to implement a custom authentication plugin for a HMAC signed authentication key. +- Read our HMAC signatures documentation for an explanation of HMAC signed authentication with Tyk Gateway. A brief summary is given in the HMAC Signed Authentication section below. + + +#### HMAC Signed Authentication + +Before diving in further, we will give a brief overview of HMAC signed authentication using our custom authentication plugin. + +- **Client request**: The journey begins with a client requesting access to a protected resource on the Tyk API. +- **HMAC signing**: Before dispatching the request, the client computes an HMAC signature using a secret key and request date, ensuring the payload's integrity. +- **Authorization header**: The HMAC signature, along with essential metadata such as the API key and HMAC algorithm, is embedded within the Authorization header. +- **Tyk Gateway verification**: Upon receipt, Tyk Gateway forwards the request to our gRPC server to execute the custom authentication plugin. This will validate the HMAC signature, ensuring the request's authenticity before proceeding with further processing. + +Requests should be made to an API that uses our custom authentication plugin as follows. A HMAC signed key should be included in the *Authorization* header and a date/time string in the *Date* header. An example request is shown in the curl command below: + +```bash +curl -v -H 'Date: Fri, 03 May 2024 12:00:42 GMT' \ +-H 'Authorization: Signature keyId="eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==", \ +algorithm="hmac-sha512",signature="9kwBK%2FyrjbSHJDI7INAhBmhHLTHRDkIe2uRWHEP8bgQFQvfXRksm6t2MHeLUyk9oosWDZyC17AbGeP8EFqrp%2BA%3D%3D"' \ +http://localhost:8080/grpc-custom-auth/get +``` + +From the above example, it should be noted that: + +- The *Date* header contains a date string formatted as follows: *Fri, 03 May 2024 11:06:00 GMT*. +- The *Authorization* header is formatted as `Signature keyId="", algorithm="", signature=""` where: + + - **keyId** is a Tyk authentication key. + - **algorithm** is the HMAC algorithm used to sign the signature, *hmac-sha512* or *hmac-sha256*. + - **signature** is the HAMC signature calculated with the date string from the *Date* header, signed with a base64 encoded secret value, using the specified HMAC algorithm. The HMAC signature is then encoded as base64. + +#### Prerequisites + +Firstly, we need to create the following: + +- An API configured to use a custom authentication plugin. +- A HMAC enabled key with a configured secret for signing. + +This will enable us to issue a request to test that Tyk Gateway integrates with our custom authentication plugin on the gRPC server. + +###### Create API + +We will create an API served by Tyk Gateway, that will forward requests upstream to https://httpbin.org/. + +The API will have the following parameters configured: + +- **Listen path**: Tyk Gateway will listen to API requests on */grpc-custom-auth/* and will strip the listen path for upstream requests. +- **Target URL**: The target URL will be configured to send requests to *http://httpbin/*. +- **Authentication Mode**: The authentication mode will be configured for custom authentication. This is used to trigger CoProcess (gRPC), Python or JSVM plugins to handle custom authentication. + +You can use the following Tyk Classic API definition to get you started, replacing the *org_id* with the ID of your organization. + +```json +{ + "api_definition": { + "id": "662facb2f03e750001a03500", + "api_id": "6c56dd4d3ad942a94474df6097df67ed", + "org_id": "5e9d9544a1dcd60001d0ed20", + "name": "Python gRPC Custom Auth", + "enable_coprocess_auth": true, + "auth": { + "auth_header_name": "Authorization" + }, + "proxy": { + "preserve_host_header": false, + "listen_path": "/grpc-custom-auth/", + "disable_strip_slash": true, + "strip_listen_path": true, + "target_url": "http://httpbin/" + }, + "version_data": { + "not_versioned": false, + "versions": { + "Default": { + "name": "Default", + "expires": "", + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [] + } + } + }, + "default_version": "Default" + }, + "active": true + } +} +``` + +The Tyk API definition above can be imported via Tyk Dashboard. Alternatively, if using Tyk Gateway OSS, a POST request can be made to the *api/apis* endpoint of Tyk Gateway. Consult the [Tyk Gateway Open API Specification documentation](/tyk-gateway-api) for usage. + +An illustrative example using *curl* is given below. Please note that you will need to: + +- Update the location to use the protocol scheme, host and port suitable for your environment. +- Replace the value in the *x-tyk-authorization* header with the secret value in your *tyk.conf* file. +- Replace the *org_id* with the ID of your organization. + +```bash +curl -v \ + --header 'Content-Type: application/json' \ + --header 'x-tyk-authorization: your Gateway admin secret' \ + --location http://localhost:8080/tyk/apis/ \ + --data '{\ + "api_definition": {\ + "id": "662facb2f03e750001a03502",\ + "api_id": "6c56dd4d3ad942a94474df6097df67ef",\ + "org_id": "5e9d9544a1dcd60001d0ed20",\ + "name": "Python gRPC Custom Auth",\ + "enable_coprocess_auth": true,\ + "auth": {\ + "auth_header_name": "Authorization"\ + },\ + "proxy": {\ + "preserve_host_header": false,\ + "listen_path": "/grpc-custom-auth-error/",\ + "disable_strip_slash": true,\ + "strip_listen_path": true,\ + "target_url": "http://httpbin/"\ + },\ + "version_data": {\ + "not_versioned": false,\ + "versions": {\ + "Default": {\ + "name": "Default",\ + "expires": "",\ + "use_extended_paths": true,\ + "extended_paths": {\ + "ignored": [],\ + "white_list": [],\ + "black_list": []\ + }\ + }\ + },\ + "default_version": "Default"\ + },\ + "active": true\ + }\ + }' +``` + +A response similar to that given below will be returned by Tyk Gateway: + +```bash +{ + "key": "f97b748fde734b099001ca15f0346dfe", + "status": "ok", + "action": "added" +} +``` + +###### Create HMAC Key + +We will create an key configured to use HMAC signing, with a secret of *secret*. The key will configured to have access to our test API. + +You can use the following configuration below, replacing the value of the *org_id* with the ID of your organization. + +```bash +{ + "quota_max": 1000, + "quota_renews": 1596929526, + "quota_remaining": 1000, + "quota_reset": 1596843126, + "quota_used": 0, + "org_id": "5e9d9544a1dcd60001d0ed20", + "access_rights": { + "662facb2f03e750001a03500": { + "api_id": "662facb2f03e750001a03500", + "api_name": "Python gRPC Custom Auth", + "versions": ["Default"], + "allowed_urls": [], + "limit": null, + "quota_max": 1000, + "quota_renews": 1596929526, + "quota_remaining": 1000, + "quota_reset": 1596843126, + "quota_used": 0, + "per": 1, + "expires": -1 + } + }, + "enable_detailed_recording": true, + "hmac_enabled": true, + "hmac_string": "secret", + "meta_data": {} +} +``` + +You can use Tyk Gateway’s API to create the key by issuing a POST request to the *tyk/keys* endpoint. Consult the [Tyk Gateway Open API Specification documentation](/tyk-gateway-api) for usage. + +An illustrative example using *curl* is given below. Please note that you will need to: + +- Update the location to use the protocol scheme, host and port suitable for your environment. +- Replace the value in the *x-tyk-authorization* header with the secret value in your *tyk.conf* file. + +Replace the *org_id* with the ID of your organization. + +```bash +curl --location 'http://localhost:8080/tyk/keys/grpc_hmac_key' \ +--header 'x-tyk-authorization: your Gateay admin secret' \ +--header 'Content-Type: application/json' \ +--data '{\ + "alias": "grpc_hmac_key",\ + "quota_max": 1000,\ + "quota_renews": 1596929526,\ + "quota_remaining": 1000,\ + "quota_reset": 1596843126,\ + "quota_used": 0,\ + "org_id": "5e9d9544a1dcd60001d0ed20",\ + "access_rights": {\ + "662facb2f03e750001a03500": {\ + "api_id": "662facb2f03e750001a03500",\ + "api_name": "python-grpc-custom-auth",\ + "versions": ["Default"],\ + "allowed_urls": [],\ + "limit": null,\ + "quota_max": 1000,\ + "quota_renews": 1596929526,\ + "quota_remaining": 1000,\ + "quota_reset": 1596843126,\ + "quota_used": 0,\ + "per": 1,\ + "expires": -1\ + }\ + },\ + "enable_detailed_recording": true,\ + "hmac_enabled": true,\ + "hmac_string": "secret",\ + "meta_data": {}\ +}\ +' +``` + +A response similar to that given below should be returned by Tyk Gateway: + +```json +{ + "key": "eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==", + "status": "ok", + "action": "added", + "key_hash": "a72fcdc09caa86b5" +} +``` + + + +Make a note of the key ID given in the response, since we will need this to test our API. + + + +#### Implement Plugin + +Our custom authentication plugin will perform the following tasks: + +- Extract the *Authorization* and *Date* headers from the request object. +- Parse the *Authorization* header to extract the *keyId*, *algorithm* and *signature* attributes. +- Compute the HMAC signature using the specific algorithm and date included in the header. +- Verify that the computed HMAC signature matches the signature included in the *Authorization* header. A 401 error response will be returned if verification fails. Our plugin will only verify the key against an expected value. In a production environment it will be necessary to verify the key against Redis storage. +- Verify that the *keyId* matches an expected value (VALID_TOKEN). A 401 error response will be returned to Tyk Gateway if verification fails. +- If verification of the signature and key passes then update the session with HMAC enabled and set the HMAC secret. Furthermore, add the key to the *Object* metadata. + +Return the request *Object* containing the updated session back to Tyk Gateway. When developing custom authentication plugins it is the responsibility of the developer to update the session state with the token, in addition to setting the appropriate response status code and error message when authentication fails. + +##### Import Python Modules + +Ensure that the following Python modules are imported at the top of your *tyk_async_server.py* file: + +```python +import asyncio +import base64 +import hashlib +import hmac +import json +import re +import signal +import logging +import urllib.parse + +import grpc +from google.protobuf.json_format import MessageToJson +from grpc_reflection.v1alpha import reflection +import coprocess_object_pb2_grpc +import coprocess_object_pb2 +from coprocess_common_pb2 import HookType +from coprocess_session_state_pb2 import SessionState +``` + +##### Add Constants + +Add the following constants to the top of the *tyk_async_server.py* file, after the import statements: + +```bash +SECRET = "c2VjcmV0" +VALID_TOKEN = "eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==" +``` + +- **SECRET** is a base64 representation of the secret used for HMAC signing. +- **VALID_TOKEN** is the key ID that we will authenticate against. + +The values listed above are designed to align with the examples provided in the *Prerequisites* section, particularly those related to HMAC key generation. If you've made adjustments to the HMAC secret or you've modified the key alias referred to in the endpoint path (for instance, *grpc_hmac_key*), you'll need to update these constants accordingly. + +##### Extract headers + +Add the following function to your *tyk_async_server.py* file to extract a dictionary of the key value pairs from the *Authorization* header. We will use a regular expression to extract the key value pairs. + +```python +def parse_auth_header(auth_header: str) -> dict[str,str]: + pattern = r'(\w+)\s*=\s*"([^"]+)"' + + matches = re.findall(pattern, auth_header) + + parsed_data = dict(matches) + + return parsed_data +``` + +##### Compute HMAC Signature + +Add the following function to your *tyk_async_server.py* to compute the HMAC signature. + +```python +def generate_hmac_signature(algorithm: str, date_string: str, secret_key: str) -> str: + + if algorithm == "hmac-sha256": + hash_algorithm = hashlib.sha256 + elif algorithm == "hmac-sha512": + hash_algorithm = hashlib.sha512 + else: + raise ValueError("Unsupported hash algorithm") + + base_string = f"date: {date_string}" + + logging.info(f"generating signature from: {base_string}") + hmac_signature = hmac.new(secret_key.encode(), base_string.encode(), hash_algorithm) + + return base64.b64encode(hmac_signature.digest()).decode() +``` + +Our function accepts three parameters: + +- **algorithm** is the HMAC algorithm to use for signing. We will use HMAC SHA256 or HMAC SHA512 in our custom authentication plugin +- **date_string** is the date extracted from the date header in the request sent by Tyk Gateway. +- **secret_key** is the value of the secret used for signing. + +The function computes and returns the HMAC signature for a string formatted as *date: date_string*, where *date_string* corresponds to the value of the *date_string* parameter. The signature is computed using the secret value given in the *secret_key* parameter and the HMAC algorithm given in the *algorithm* parameter. A *ValueError* is raised if the hash algorithm is unrecognized. + +We use the following Python modules in our implementation: + +- hmac Python module to compute the HMAC signature. +- base64 Python module to encode the result. + +##### Verify HMAC Signature + +Add the following function to your *tyk_async_server.py* file to verify the HMAC signature provided by the client: + +```python +def verify_hmac_signature(algorithm: str, signature: str, source_string) -> bool: + + expected_signature = generate_hmac_signature(algorithm, source_string, SECRET) + received_signature = urllib.parse.unquote(signature) + + if expected_signature != received_signature: + error = f"Signatures did not match\nreceived: {received_signature}\nexpected: {expected_signature}" + logging.error(error) + else: + logging.info("Signatures matched!") + + return expected_signature == received_signature +``` + +Our function accepts three parameters: + +- **algorithm** is the HMAC algorithm to use for signing. We will use hmac-sha256 or hmac-sha512 in our custom authentication plugin. +- **signature** is the signature string extracted from the *Authorization* header. +- **source_string** is the date extracted from the date header in the request sent by Tyk Gateway. +- **secret_key** is the value of the secret used for signing. + +The function calls *generate_hmac_signature* to verify the signatures match. It returns true if the computed and client HMAC signatures match, otherwise false is returned. + +##### Set Error Response + +Add the following helper function to *tyk_async_server.py* to allow us to set the response status and error message if authentication fails. + +```python +def set_response_error(object: coprocess_object_pb2.Object, code: int, message: str) -> None: + object.request.return_overrides.response_code = code + object.request.return_overrides.response_error = message +``` + +Our function accepts the following three parameters: + +- **object** is an instance of the [Object](/api-management/plugins/rich-plugins#coprocess-object) message representing the payload sent by Tyk Gateway to the *Dispatcher* service in our gRPC server. For further details of the payload structure dispatched by Tyk Gateway to a gRPC server please consult our gRPC documentation. +- **code** is the HTTP status code to return in the response. +- **message** is the response message. + +The function modifies the *return_overrides* attribute of the request, updating the response status code and error message. The *return_overrides* attribute is an instance of a [ReturnOverrides](/api-management/plugins/rich-plugins#returnoverrides) message that can be used to override the response of a given HTTP request. When this attribute is modified the request is terminated and is not sent upstream. + +##### Authenticate + +Add the following to your *tyk_async_server.py* file to implement the main custom authentication function. This parses the headers to extract the signature and date from the request, in addition to verifying the HMAC signature and key: + +```python +def authenticate(object: coprocess_object_pb2.Object) -> coprocess_object_pb2.Object: + keys_to_check = ["keyId", "algorithm", "signature"] + + auth_header = object.request.headers.get("Authorization") + date_header = object.request.headers.get("Date") + + parse_dict = parse_auth_header(auth_header) + + if not all(key in parse_dict for key in keys_to_check) or not all([auth_header, date_header]): + set_response_error(object, 400, "Custom middleware: Bad request") + return object + + try: + signature_valid = verify_hmac_signature( + parse_dict["algorithm"], + parse_dict["signature"], + date_header + ) + except ValueError: + set_response_error(object, 400, "Bad HMAC request, unsupported algorithm") + return object + + if not signature_valid or parse_dict["keyId"] != VALID_TOKEN: + set_response_error(object, 401, "Custom middleware: Not authorized") + else: + new_session = SessionState() + new_session.hmac_enabled = True + new_session.hmac_secret = SECRET + + object.metadata["token"] = VALID_TOKEN + object.session.CopyFrom(new_session) + + return object +``` + +The *Object* payload received from the Gateway is updated and returned as a response from the *Dispatcher* service: + +- If authentication fails then we set the error message and status code for the response accordingly, using our *set_response_error* function. +- If authentication passes then we update the session attribute in the *Object* payload to indicate that HMAC verification was performed and provide the secret used for signing. We also add the verified key to the meta data of the request payload. + +Specifically, our function performs the following tasks: + +- Extracts the *Date* and *Authorization* headers from the request and verifies that the *Authorization* header is structured correctly, using our *parse_auth_header* function. We store the extracted *Authorization* header fields in the *parse_dict* dictionary. If the structure is invalid then a 400 bad request response is returned to Tyk Gateway, using our *set_response_error* function. +- We use our *verify_hmac_signature* function to compute and verify the HMAC signature. A 400 bad request error is returned to the Gateway if HMAC signature verification fails, due to an unrecognized HMAC algorithm. +- A 401 unauthorized error response is returned to the Gateway under the following conditions: + + - The client HMAC signature and the computed HMAC signature do not match. + - The extracted key ID does not match the expected key value in VALID_TOKEN. + +- If HMAC signature verification passed and the key included in the *Authorization* header is valid then we update the *SessionState* instance to indicate that HMAC signature verification is enabled, i.e. *hmac_enabled* is set to true. We also specify the HMAC secret used for signing in the *hmac_secret* field and include the valid token in the metadata dictionary. + +##### Integrate Plugin + +Update the *Dispatch* method of the *PythonDispatcher* class in your *tyk_async_server.py* file so that our authenticate function is called when the a request is made by Tyk Gateway to execute a custom authentication (*HookType.CustomKeyCheck*) plugin. + +```python +class PythonDispatcher(coprocess_object_pb2_grpc.DispatcherServicer): + async def Dispatch( + self, object: coprocess_object_pb2.Object, context: grpc.aio.ServicerContext + ) -> coprocess_object_pb2.Object: + + logging.info(f"STATE for {object.hook_name}\n{MessageToJson(object)}\n") + + if object.hook_type == HookType.Pre: + logging.info(f"Pre plugin name: {object.hook_name}") + logging.info(f"Activated Pre Request plugin from API: {object.spec.get('APIID')}") + + elif object.hook_type == HookType.CustomKeyCheck: + logging.info(f"CustomAuth plugin: {object.hook_name}") + logging.info(f"Activated CustomAuth plugin from API: {object.spec.get('APIID')}") + + authenticate(object) + + elif object.hook_type == HookType.PostKeyAuth: + logging.info(f"PostKeyAuth plugin name: {object.hook_name}") + logging.info(f"Activated PostKeyAuth plugin from API: {object.spec.get('APIID')}") + + elif object.hook_type == HookType.Post: + logging.info(f"Post plugin name: {object.hook_name}") + logging.info(f"Activated Post plugin from API: {object.spec.get('APIID')}") + + elif object.hook_type == HookType.Response: + logging.info(f"Response plugin name: {object.hook_name}") + logging.info(f"Activated Response plugin from API: {object.spec.get('APIID')}") + logging.info("--------\n") + + return object +``` + +#### Test Plugin + +Create the following bash script, *hmac.sh*, to issue a test request to an API served by Tyk Gateway. The script computes a HMAC signature and constructs the *Authorization* and *Date* headers for a specified API. The *Authorization* header contains the HMAC signature and key for authentication. + +Replace the following constant values with values suitable for your environment: + +- **KEY** represents the key ID for the HMAC signed key that you created at the beginning of this guide. +- **HMAC_SECRET** represents the base64 encoded value of the secret for your HMAC key that you created at the beginning of this guide. +- **BASE_URL** represents the base URL, containing the protocol scheme, host and port number that Tyk Gateway listens to for API requests. +- **ENDPOINT** represents the path of your API that uses HMAC signed authentication. + +```bash +#!/bin/bash + +BASE_URL=http://localhost:8080 +ENDPOINT=/grpc-custom-auth/get +HMAC_ALGORITHM=hmac-sha512 +HMAC_SECRET=c2VjcmV0 +KEY=eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ== +REQUEST_URL=${BASE_URL}${ENDPOINT} + + +function urlencode() { + echo -n "$1" | perl -MURI::Escape -ne 'print uri_escape($_)' | sed "s/%20/+/g" +} + +# Set date in expected format +date="$(LC_ALL=C date -u +"%a, %d %b %Y %H:%M:%S GMT")" + +# Generate the signature using hmac algorithm with hmac secret from created Tyk key and +# then base64 encoded +signature=$(echo -n "date: ${date}" | openssl sha512 -binary -hmac "${HMAC_SECRET}" | base64) + +# Ensure the signature is base64 encoded +url_encoded_signature=$(echo -n "${signature}" | perl -MURI::Escape -ne 'print uri_escape($_)' | sed "s/%20/+/g") + +# Output the date, encoded date, signature and the url encoded signature +echo "request: ${REQUEST_URL}" +echo "date: $date" +echo "signature: $signature" +echo "url_encoded_signature: $url_encoded_signature" + +# Make the curl request using headers +printf "\n\n----\n\nMaking request to http://localhost:8080/grpc-custom-auth/get\n\n" +set -x +curl -v -H "Date: ${date}" \ + -H "Authorization: Signature keyId=\"${KEY}\",algorithm=\"${HMAC_ALGORITHM}\",signature=\"${url_encoded_signature}\"" \ + ${REQUEST_URL} +``` + +After creating and saving the script, ensure that it is executable by issuing the following command: + +```bash +chmod +x hmac.sh +``` + +Issue a test request by running the script: + +```bash +./hmac.sh +``` + +Observe the output of your gRPC server. You should see the request payload appear in the console output for the server and your custom authentication plugin should have been triggered. An illustrative example is given below: + +```bash +2024-05-13 12:53:49 INFO:root:STATE for CustomHMACCheck +2024-05-13 12:53:49 { +2024-05-13 12:53:49 "hookType": "CustomKeyCheck", +2024-05-13 12:53:49 "hookName": "CustomHMACCheck", +2024-05-13 12:53:49 "request": { +2024-05-13 12:53:49 "headers": { +2024-05-13 12:53:49 "User-Agent": "curl/8.1.2", +2024-05-13 12:53:49 "Date": "Mon, 13 May 2024 11:53:49 GMT", +2024-05-13 12:53:49 "Host": "localhost:8080", +2024-05-13 12:53:49 "Authorization": "Signature keyId=\"eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==\",algorithm=\"hmac-sha512\",signature=\"e9OiifnTDgi3PW2EGJWfeQXCuhuhi6bGLiGhUTFpjEfgdKmX%2FQOFrePAQ%2FAoSFGU%2FzpP%2FCabmQi4zQDPdRh%2FZg%3D%3D\"", +2024-05-13 12:53:49 "Accept": "*/*" +2024-05-13 12:53:49 }, +2024-05-13 12:53:49 "url": "/grpc-custom-auth/get", +2024-05-13 12:53:49 "returnOverrides": { +2024-05-13 12:53:49 "responseCode": -1 +2024-05-13 12:53:49 }, +2024-05-13 12:53:49 "method": "GET", +2024-05-13 12:53:49 "requestUri": "/grpc-custom-auth/get", +2024-05-13 12:53:49 "scheme": "http" +2024-05-13 12:53:49 }, +2024-05-13 12:53:49 "spec": { +2024-05-13 12:53:49 "bundle_hash": "d41d8cd98f00b204e9800998ecf8427e", +2024-05-13 12:53:49 "OrgID": "5e9d9544a1dcd60001d0ed20", +2024-05-13 12:53:49 "APIID": "6c56dd4d3ad942a94474df6097df67ed" +2024-05-13 12:53:49 } +2024-05-13 12:53:49 } +2024-05-13 12:53:49 +2024-05-13 12:53:49 INFO:root:CustomAuth plugin: CustomHMACCheck +2024-05-13 12:53:49 INFO:root:Activated CustomAuth plugin from API: 6c56dd4d3ad942a94474df6097df67ed +2024-05-13 12:53:49 INFO:root:generating signature from: date: Mon, 13 May 2024 11:53:49 GMT +2024-05-13 12:53:49 INFO:root:Signatures matched! +2024-05-13 12:53:49 INFO:root:-------- +``` + +Try changing the SECRET and/or KEY constants with invalid values and observe the output of your gRPC server. You should notice that authentication fails. An illustrative example is given below: + +``` +2024-05-13 12:56:37 INFO:root:STATE for CustomHMACCheck +2024-05-13 12:56:37 { +2024-05-13 12:56:37 "hookType": "CustomKeyCheck", +2024-05-13 12:56:37 "hookName": "CustomHMACCheck", +2024-05-13 12:56:37 "request": { +2024-05-13 12:56:37 "headers": { +2024-05-13 12:56:37 "User-Agent": "curl/8.1.2", +2024-05-13 12:56:37 "Date": "Mon, 13 May 2024 11:56:37 GMT", +2024-05-13 12:56:37 "Host": "localhost:8080", +2024-05-13 12:56:37 "Authorization": "Signature keyId=\"eyJvcmciOiI1ZTlkOTU0NGExZGNkNjAwMDFkMGVkMjAiLCJpZCI6ImdycGNfaG1hY19rZXkiLCJoIjoibXVybXVyNjQifQ==\",algorithm=\"hmac-sha512\",signature=\"KXhkWOS01nbxuFfK7wEBggkydXlKJswxbukiplboJ2n%2BU6JiYOil%2Bx4OE4edWipg4EcG9T49nvY%2Fc9G0XFJcfg%3D%3D\"", +2024-05-13 12:56:37 "Accept": "*/*" +2024-05-13 12:56:37 }, +2024-05-13 12:56:37 "url": "/grpc-custom-auth/get", +2024-05-13 12:56:37 "returnOverrides": { +2024-05-13 12:56:37 "responseCode": -1 +2024-05-13 12:56:37 }, +2024-05-13 12:56:37 "method": "GET", +2024-05-13 12:56:37 "requestUri": "/grpc-custom-auth/get", +2024-05-13 12:56:37 "scheme": "http" +2024-05-13 12:56:37 }, +2024-05-13 12:56:37 "spec": { +2024-05-13 12:56:37 "bundle_hash": "d41d8cd98f00b204e9800998ecf8427e", +2024-05-13 12:56:37 "OrgID": "5e9d9544a1dcd60001d0ed20", +2024-05-13 12:56:37 "APIID": "6c56dd4d3ad942a94474df6097df67ed" +2024-05-13 12:56:37 } +2024-05-13 12:56:37 } +2024-05-13 12:56:37 +2024-05-13 12:56:37 INFO:root:CustomAuth plugin: CustomHMACCheck +2024-05-13 12:56:37 INFO:root:Activated CustomAuth plugin from API: 6c56dd4d3ad942a94474df6097df67ed +2024-05-13 12:56:37 INFO:root:generating signature from: date: Mon, 13 May 2024 11:56:37 GMT +2024-05-13 12:56:37 ERROR:root:Signatures did not match +2024-05-13 12:56:37 received: KXhkWOS01nbxuFfK7wEBggkydXlKJswxbukiplboJ2n+U6JiYOil+x4OE4edWipg4EcG9T49nvY/c9G0XFJcfg== +2024-05-13 12:56:37 expected: zT17C2tgDCYBJCgFFN/mknf6XydPaV98a5gMPNUHYxZyYwYedIPIhyDRQsMF9GTVFe8khCB1FhfyhpmzrUR2Lw== +``` + +#### Summary + +In this guide, we've explained how to write a Python gRPC custom authentication plugin for Tyk Gateway, using HMAC-signed authentication as a practical example. Through clear instructions and code examples, we've provided developers with insights into the process of creating custom authentication logic tailored to their specific API authentication needs. + +While Tyk Gateway already supports HMAC-signed authentication out of the box, this guide goes beyond basic implementation by demonstrating how to extend its capabilities through custom plugins. By focusing on HMAC-signed authentication, developers have gained valuable experience in crafting custom authentication mechanisms that can be adapted and expanded to meet diverse authentication requirements. + +It's important to note that the authentication mechanism implemented in this guide solely verifies the HMAC signature's validity and does not include access control checks against specific API resources. Developers should enhance this implementation by integrating access control logic to ensure authenticated requests have appropriate access permissions. + +By mastering the techniques outlined in this guide, developers are better equipped to address complex authentication challenges and build robust API security architectures using Tyk Gateway's extensibility features. This guide serves as a foundation for further exploration and experimentation with custom authentication plugins, empowering developers to innovate and customize API authentication solutions according to their unique requirements. + +--- + +
+ +### Performance + +These are some benchmarks performed on gRPC plugins. + +gRPC plugins may use different transports, we've tested TCP and Unix Sockets. + +#### TCP + +TCP Response Times + +TCP Hit Rate + +#### Unix Socket + +Unix Socket Response Times + +Unix Socket Hit Rate + +--- + +## Using Lua + +### Overview + +#### Requirements + +Tyk uses [LuaJIT](http://luajit.org/). The main requirement is the LuaJIT shared library, you may find this as `libluajit-x` in most distros. + +For Ubuntu 14.04 you may use: + +`$ apt-get install libluajit-5.1-2 +$ apt-get install luarocks` + +The LuaJIT required modules are as follows: + +* [lua-cjson](https://github.com/mpx/lua-cjson): in case you have `luarocks`, run: `$ luarocks install lua-cjson` + +#### How to write LuaJIT Plugins + +We have a demo plugin hosted in the repo [tyk-plugin-demo-lua](https://github.com/TykTechnologies/tyk-plugin-demo-lua). The project implements a simple middleware for header injection, using a Pre hook (see [Tyk custom middleware hooks](/api-management/plugins/javascript#using-javascript-with-tyk)) and [mymiddleware.lua](https://github.com/TykTechnologies/tyk-plugin-demo-lua/blob/master/mymiddleware.lua). +#### Lua Performance +Lua support is currently in beta stage. We are planning performance optimizations for future releases. +#### Tyk Lua API Methods +Tyk Lua API methods aren’t currently supported. + +### Lua Plugin Tutorial + +#### Settings in the API Definition + +To add a Lua plugin to your API, you must specify the bundle name using the `custom_middleware_bundle` field: + +```json +{ + "name": "Tyk Test API", + "api_id": "1", + "org_id": "default", + "definition": { + "location": "header", + "key": "version" + }, + "auth": { + "auth_header_name": "authorization" + }, + "use_keyless": true, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "expires": "3000-01-02 15:04", + "use_extended_paths": true, + "extended_paths": { + "ignored": [], + "white_list": [], + "black_list": [] + } + } + } + }, + "proxy": { + "listen_path": "/quickstart/", + "target_url": "http://httpbin.org", + "strip_listen_path": true + }, + "custom_middleware_bundle": "test-bundle", +} +``` + +#### Global settings + +To enable Lua plugins you need to add the following block to `tyk.conf`: + +```json +"coprocess_options": { + "enable_coprocess": true, +}, +"enable_bundle_downloader": true, +"bundle_base_url": "http://my-bundle-server.com/bundles/", +"public_key_path": "/path/to/my/pubkey", +``` + +`enable_coprocess` enables the rich plugins feature. + +`enable_bundle_downloader` enables the bundle downloader. + +`bundle_base_url` is a base URL that will be used to download the bundle, in this example we have "test-bundle" specified in the API settings, Tyk will fetch the following URL: `http://my-bundle-server.com/bundles/test-bundle`. + +`public_key_path` sets a public key, this is used for verifying signed bundles, you may omit this if unsigned bundles are used. + +#### Running the Tyk Lua build + +To use Tyk with Lua support you will need to use an alternative binary, it is provided in the standard Tyk package but it has a different service name. + +Firstly stop the standard Tyk version: + +```console +service tyk-gateway stop +``` + +and then start the Lua build: + +```console +service tyk-gateway-lua start +``` + + diff --git a/api-management/policies.mdx b/api-management/policies.mdx new file mode 100644 index 0000000000..51a3d474da --- /dev/null +++ b/api-management/policies.mdx @@ -0,0 +1,59 @@ +--- +title: "Understanding Policies" +description: "Learn what Tyk Policies are, why you should use them, and how they interact with Sessions to manage access at scale." +keywords: "Policies, Access Control, Rate Limits, Quotas, JWT Scopes, Multiple Policies" +sidebarTitle: "Understanding Policies" +--- + +## Introduction + +In Tyk, a [**Session**](/api-management/access-control/sessions-and-keys/understanding-sessions) dictates the lifecycle, limits, and access rights for a specific client. However, managing these settings individually for thousands or millions of clients is inefficient and error-prone. + +This is where **Policies** come in. A Policy is essentially a reusable template or blueprint that defines a set of access rights, rate limits, and quotas. Instead of configuring these rules directly on every individual Session, you configure them once in a Policy, and then link multiple Sessions to that Policy. + +## Why Use Policies? + +We highly recommend using Policies as the primary method for managing access control in Tyk. + +### 1. Management at Scale +If you need to change a rate limit or grant access to a new API endpoint, doing so without Policies would require updating all the relevant Sessions in your database. With Policies, you simply update the Policy object once, and the changes instantly apply to all linked Sessions. + +### 2. Tiered Access Models +Policies are perfect for implementing tiered subscription models (e.g., "Free", "Pro", and "Enterprise"). You can create a Policy for each tier with different rate limits and quotas, and assign clients to the appropriate Policy when they register. + +### 3. Granular Feature Toggles +Because you can apply multiple Policies to a single Session, you can use Policies to represent individual features or add-ons. If a user purchases an add-on, you simply link the corresponding Policy to their Session. + +## How Policies are Managed + +How you [create and manage Policies](/api-management/access-control/policies/managing-policies) depends on your Tyk deployment: + +- **Tyk Dashboard (Cloud & Self-Managed)**: Policies are managed centrally via the Dashboard UI or the Dashboard API. They are stored in your database and automatically synchronized across all your connected Gateways. +- **Tyk Open Source**: If you are using the open-source Gateway, Policies are typically defined in a local `policies.json` file or managed directly via the Gateway API. + +## How and When Policies are Applied + +When a Policy is linked to a Session, the Gateway does not permanently copy the Policy's rules into the Session data in Redis. + +Instead, Policies are [applied](/api-management/access-control/policies/applying-policies) **dynamically during request processing**. + +When a client makes a request, Tyk retrieves their Session from Redis (or the local cache). Tyk then looks up the linked Policies and overlays their rules onto the Session on the fly. + +This dynamic application is what makes Policies so powerful: **updating a Policy immediately affects all existing Sessions linked to it**. The next time a client makes a request, Tyk will apply the newly updated Policy rules. + +### Applying Multiple Policies + +Tyk allows you to link more than one Policy to a single Session. This is incredibly useful for combining base access tiers with specific feature add-ons. + +When [multiple Policies](/api-management/access-control/policies/applying-policies#applying-multiple-policies) are applied to a Session, Tyk must merge their rules to determine the final access rights. For example Tyk uses the following merging logic: + +- **Rate Limits:** Tyk applies the most permissive rate limit settings. If Policy A allows 10 requests per second, and Policy B allows 100 requests per second, the Session will be allowed 100 requests per second. +- **Access Rights:** Tyk combines the allowed URLs and methods from all policies. If Policy A allows `GET /users` and Policy B allows `POST /reports`, the Session will be allowed to do both. + +### JWT Scope-to-Policy Mapping + +For token-based authentication methods such as JWT and OAuth 2.0, Tyk offers a powerful feature called [**Scope-to-Policy Mapping**](/api-management/authentication/jwt-authorization#identifying-the-tyk-policies-to-be-applied). + +Instead of assigning a Policy to a client's Session statically, you can configure Tyk to inspect the claims within the token (such as the `scope` claim) and dynamically map those scopes to specific Tyk Policies. + +For example, if an Identity Provider issues a JWT with the scopes `read:data` and `admin`, Tyk can automatically apply the "Read-Only Policy" and the "Admin Policy" to the dynamically generated Session. This allows you to drive Tyk's granular access controls directly from your centralized Identity Provider. \ No newline at end of file diff --git a/api-management/rate-limit.mdx b/api-management/rate-limit.mdx new file mode 100644 index 0000000000..a2bb1ca1f4 --- /dev/null +++ b/api-management/rate-limit.mdx @@ -0,0 +1,926 @@ +--- +title: "Rate Limiting" +description: "Overview of Rate Limiting with the Tyk Gateway" +keywords: "Rate Limit, Rate Limiting, Rate Limit Algorithms, Distributed Rate Limiter, Redis Rate Limiter, Fixed Window, Spike Arrest, Rate Limit Scope, Local, Local rate Limits, Tyk Classic, Tyk Classic API, Tyk OAS, Tyk OAS API, Rate Limiting, Global limits, Per API limits" +sidebarTitle: "Rate Limiting" +--- + +## Introduction + +API rate limiting is a technique that allows you to control the rate at which clients can consume your APIs and is one of the fundamental aspects of managing traffic to your services. It serves as a safeguard against abuse, overloading, and denial-of-service attacks by limiting the rate at which an API can be accessed. By implementing rate limiting, you can ensure fair usage, prevent resource exhaustion, and maintain system performance and stability, even under high traffic loads. + +## What is rate limiting? + +Rate limiting involves setting thresholds for the maximum number of requests that can be made within a specific time window, such as requests per second, per minute, or per day. Once a client exceeds the defined rate limit, subsequent requests may be delayed, throttled, or blocked until the rate limit resets or additional capacity becomes available. + +## When might you want to use rate limiting? + +Rate limiting may be used as an extra line of defense around attempted denial of service attacks. For instance, if you have load-tested your current system and established a performance threshold that you would not want to exceed to ensure system availability and/or performance then you may want to set a global rate limit as a defense to ensure it hasn't exceeded. + +Rate limiting can also be used to ensure that one particular user or system accessing the API is not exceeding a determined rate. This makes sense in a scenario such as APIs which are associated with a monetization scheme where you may allow so many requests per second based on the tier in which that consumer is subscribed or paying for. + +Of course, there are plenty of other scenarios where applying a rate limit may be beneficial to your APIs and the systems that your APIs leverage behind the scenes. + +## How does rate limiting work? + +At a basic level, when rate limiting is in use, Tyk Gateway will compare the incoming request rate against the configured limit and will block requests that arrive at a higher rate. For example, let’s say you only want to allow a client to call the API a maximum of 10 times per minute. In this case, you would apply a rate limit to the API expressed as "10 requests per 60 seconds". This means that the client will be able to successfully call the API up to 10 times within any 60 second interval (or window) and after for any further requests within that window, the user will get an [HTTP 429 (Rate Limit Exceeded)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) error response stating the rate limit has been exceeded. + +Tyk's rate limiter is configured using two variables: +- `rate` which is the maximum number of requests that will be permitted during the interval (window) +- `per` which is the length of the interval (window) in seconds + +So for this example you would configure `rate` to 10 (requests) and `per` to 60 (seconds). + +### Rate Limit Response Headers + +When rate limiting is active, API clients expect to see `X-RateLimit-*` headers in the HTTP response to inform clients about their current limits. + +Historically, Tyk populated these headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) with quota data rather than rate limit data. + +From Tyk 5.13.0, you can control the behavior of these headers using the [`rate_limit_response_headers`](/tyk-oss-gateway/configuration#rate_limit_response_headers) configuration in your `tyk.conf` (or the equivalent environment variable): + +- **`rate_limits`**: the headers are only generated if a rate limit is configured; they are populated with rate limit data. +- **`quotas`**: the headers are only generated if a quota is configured; they are populated with quota data. + +If `rate_limit_response_headers` is not set, it defaults to `quotas` for backward compatibility. + + +### Rate limiting scopes: API-level vs key-level + +Rate limiting can be applied at different scopes to control API traffic effectively. This section covers the two primary scopes - API-level rate limiting and key-level rate limiting. Understanding the distinctions between these scopes will help you configure appropriate rate limiting policies based on your specific requirements. + +#### API-level rate limiting + +API-level rate limiting aggregates the traffic coming into an API from all sources and ensures that the overall rate limit is not exceeded. Overwhelming an endpoint with traffic is an easy and efficient way to execute a denial of service attack. By using a API-level rate limit you can easily ensure that all incoming requests are within a specific limit so excess requests are rejected by Tyk and do not reach your service. You can calculate the rate limit to set by something as simple as having a good idea of the maximum number of requests you could expect from users of your API during a period. You could alternatively apply a more scientific and precise approach by considering the rate of requests your system can handle while still performing at a high-level. This limit may be easily determined with some performance testing of your service under load. + +#### Key-level rate limiting + +Key-level rate limiting is more focused on controlling traffic from individual sources and making sure that users are staying within their prescribed limits. This approach to rate limiting allows you to configure a policy to rate limit in two ways: + +- **key-level global limit** limiting the rate of calls the user of a key can make to all APIs authorized by that key +- **key-level per-API limit** limiting the rate of calls the user of a key can make to specific individual APIs +- **key-level per-endpoint limit** limiting the rate of calls the user of a key can make to specific individual endpoints of an API + +These guides include explanation of how to configure key-level rate limits when using [API Keys](/api-management/gateway-config-managing-classic#access-an-api) and [Security Policies](/api-management/gateway-config-managing-classic#secure-an-api). + +#### Which scope should I use? + +The simplest way to figure out which level of rate limiting you’d like to apply can be determined by asking a few questions: + +- do you want to protect your service against denial of service attacks or overwhelming amounts of traffic from **all users** of the API? **You’ll want to use an API-level rate limit!** +- do you have a health endpoint that consumes very little resource on your service and can handle significantly more requests than your other endpoints? **You'll want to use an API-level per-endpoint rate limit!** +- do you want to limit the number of requests a specific user can make to **all APIs** they have access to? **You’ll want to use a key-level global rate limit!** +- do you want to limit the number of requests a specific user can make to **specific APIs** they have access to? **You’ll want to use a key-level per-API rate limit.** +- do you want to limit the number of requests a specific user can make to a **specific endpoint of an API** they have access to? **You’ll want to use a key-level per-endpoint rate limit.** + +### Applying multiple rate limits + +When multiple rate limits are configured, they are assessed in this order (if applied): + +1. API-level per-endpoint rate limit (configured in API definition) +2. API-level rate limit (configured in API definition) +3. Key-level per-endpoint rate limit (configured in access key) +4. Key-level per-API rate limit (configured in access key) +5. Key-level global rate limit (configured in access key) + +### Combining multiple policies configuring rate limits + +If more than one policy defining a rate limit is applied to a key then Tyk will apply the highest request rate permitted by any of the policies that defines a rate limit. + +If `rate` and `per` are configured in multiple policies applied to the same key then the Gateway will determine the effective rate limit configured for each policy and apply the highest to the key. + +Given, policy A with `rate` set to 90 and `per` set to 30 seconds (3rps) and policy B with `rate` set to 100 and `per` set to 10 seconds (10rps). If both are applied to a key, Tyk will take the rate limit from policy B as it results in a higher effective request rate (10rps). + + + +Prior to Tyk 5.4.0 there was a long-standing bug in the calculation of the effective rate limit applied to the key where Tyk would combine the highest `rate` and highest `per` from the policies applied to the key, so for the example above the key would have `rate` set to 100 and `per` set to 30 giving an effective rate limit of 3.33rps. This has now been corrected. + + + +## Rate limiting algorithms + +Different rate limiting algorithms are employed to cater to varying requirements, use cases and gateway deployments. A one-size-fits-all approach may not be suitable, as APIs can have diverse traffic patterns, resource constraints, and service level objectives. Some algorithms are more suited to protecting the upstream service from overload whilst others are suitable for per-client limiting to manage and control fair access to a shared resource. + +Tyk offers the following rate limiting algorithms: + +1. [Distributed Rate Limiter](#distributed-rate-limiter): recommended for most use cases, implements the [token bucket algorithm](https://en.wikipedia.org/wiki/Token_bucket) +2. [Redis Rate Limiter](#redis-rate-limiter): implements the [sliding window log algorithm](https://developer.redis.com/develop/dotnet/aspnetcore/rate-limiting/sliding-window) +3. [Fixed Window Rate Limiter](#fixed-window-rate-limiter): implements the [fixed window algorithm](https://redis.io/learn/develop/dotnet/aspnetcore/rate-limiting/fixed-window) + +When the rate limits are reached, Tyk will block requests with an [HTTP 429 (Rate Limit Exceeded)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) response. + + + +Tyk supports selection of the rate limit algorithm at the Gateway level, so the same algorithm will be applied to all APIs. +It can be configured to switch dynamically between two algorithms depending on the request rate, as explained [here](#dynamic-algorithm-selection-based-on-request-rate). + + + +### Distributed Rate Limiter + +The Distributed Rate Limiter (DRL) is the default rate limiting mechanism in Tyk Gateway. It is +implemented using a token bucket implementation that does not use Redis. +In effect, it divides the configured rate limit between the number of +addressable gateway instances. + +The characteristics of DRL are: + +- a rate limit of 100 requests/min with 2 gateways yields 50 requests/min per gateway +- unreliable at low rate limits where requests are not fairly balanced +- if [configured to return rate limits](/api-management/rate-limit#rate-limit-response-headers), the `X-RateLimit-Remaining` response header will only reflect the current Gateway's share of the limit, rather than the true global remaining limit across the cluster + +DRL can face challenges in scenarios where traffic is not evenly +distributed across gateways, such as with sticky sessions or keepalive +connections. These conditions can lead to certain gateways becoming +overloaded while others remain underutilized, compromising the +effectiveness of configured rate limiting. This imbalance is particularly +problematic in smaller environments or when traffic inherently favors +certain gateways, leading to premature rate limits on some nodes and +excess capacity on others. + +DRL will be used automatically unless one of the other rate limit +algorithms are explicitly enabled via configuration. + +It's important to note that this algorithm will yield approximate results due to the nature of the local +rate limiting, where the total allowable request rate is split between the gateways; uneven distribution +of requests could lead to exhaustion of the limit on some gateways before others. + +### Redis Rate Limiter + +This algorithm implements a sliding window log algorithm and can be enabled via the [enable_redis_rolling_limiter](/tyk-oss-gateway/configuration#enable_redis_rolling_limiter) configuration option. + +The characteristics of the Redis Rate Limiter (RRL) are: + +- using Redis lets any gateway respect a cluster-wide rate limit (shared counter) +- a record of each request, including blocked requests that return `HTTP 429`, is written to the sliding log in Redis +- the log is constantly trimmed to the duration of the defined window +- requests are blocked if the count in the log exceeds the configured rate limit + +An important behavior of this rate limiting algorithm is that it blocks +access to the API when the rate exceeds the rate limit and does not let +further API calls through until the rate drops below the specified rate +limit. For example, if the configured rate limit is 3000 requests/minute the call rate would +have to be reduced below 3000 requests/minute for a whole minute before the `HTTP 429` +responses stop and traffic is resumed. This behavior is called **spike arrest**. + +The complete request log is stored in Redis so resource usage when using this rate limiter is high. +This algorithm will use significant resources on Redis even when blocking requests, as it must +maintain the request log, mostly impacting CPU usage. Redis resource +usage increases with traffic therefore shorter `per` values are recommended to +limit the amount of data being stored in Redis. + +If you wish to avoid spike arrest behavior but the DRL is not suitable, you might use the [Fixed Window Rate Limiter](#fixed-window-rate-limiter) algorithm. + +You can configure [Rate Limit Smoothing](#rate-limit-smoothing) to manage the traffic spike, allowing time to increase upstream capacity if required. + +The [Redis Sentinel Rate Limiter](#redis-sentinel-rate-limiter) reduces latency for clients, however increases resource usage on Redis and Tyk Gateway. + +#### Rate Limit Smoothing + +Rate Limit Smoothing is an optional mechanism of the RRL that dynamically adjusts the request +rate limit based on the current traffic patterns. It helps in managing +request spikes by gradually increasing or decreasing the rate limit +instead of making abrupt changes or blocking requests excessively. + +This mechanism uses the concept of an intermediate *current allowance* (rate limit) that moves between an initial lower +bound (`threshold`) and the maximum configured request rate (`rate`). As the request rate approaches the current +*current allowance*, Tyk will emit an event to notify you that smoothing has been triggered. When the event is emitted, +the *current allowance* will be increased by a defined increment (`step`). A hold-off counter (`delay`) must expire +before another event is emitted and the *current allowance* further increased. If the request rate exceeds the +*current allowance* then the rate limiter will block further requests, returning `HTTP 429` as usual. + +As the request rate falls following the spike, the *current allowance* will gradually reduce back to the lower bound (`threshold`). + +Events are emitted and adjustments made to the *current allowance* based on the following calculations: + +- when the request rate rises above `current allowance - (step * trigger)`, + a `RateLimitSmoothingUp` event is emitted and *current allowance* increases by `step`. +- when the request rate falls below `allowance - (step * (1 + trigger))`, + a `RateLimitSmoothingDown` event is emitted and *current allowance* decreases by `step`. + +##### Configuring rate limit smoothing + +When Redis Rate Limiter is in use, rate limit smoothing is configured with the following options within the `smoothing` object alongside the standard `rate` and `per` parameters: + +- `enabled` (boolean) to enable or disable rate limit smoothing +- `threshold` is the initial rate limit (*current allowance*) beyond which smoothing will be applied +- `step` is the increment by which the *current allowance* will be increased or decreased each time a smoothing event is emitted +- `trigger` is a fraction (typically in the range 0.1-1.0) of the `step` at which point a smoothing event will be emitted as the request rate approaches the *current allowance* +- `delay` is a hold-off between smoothing events and controls how frequently the current allowance will step up or down (in seconds). + +Rate Limit Smoothing is configured using the `smoothing` object within access keys and policies. For API-level rate limiting, this configuration is within the `access_rights[*].limit` object. + +An example configuration would be as follows: + +```yaml + "smoothing": { + "enabled": true, + "threshold": 5, + "trigger": 0.5, + "step": 5, + "delay": 30 + } +``` + +#### Redis Sentinel Rate Limiter + +The Redis Sentinel Rate Limiter option will: + +- write a sentinel key into Redis when the request limit is reached +- use the sentinel key to block requests immediately for `per` duration +- requests, including blocked requests, are written to the sliding log in a background thread + +This optimizes the latency for connecting clients, as they don't have to +wait for the sliding log write to complete. This algorithm exhibits spike +arrest behavior the same as the basic Redis Rate Limiter, however recovery may take longer as the blocking is in +effect for a minimum of the configured window duration (`per`). Gateway and Redis +resource usage is increased with this option. + +This option can be enabled using the following configuration option +[enable_sentinel_rate_limiter](/tyk-oss-gateway/configuration#enable_sentinel_rate_limiter). + +To optimize performance, you may configure your rate limits with shorter +window duration values (`per`), as that will cause Redis to hold less +data at any given moment. + +Performance can be improved by enabling the [enable_non_transactional_rate_limiter](/tyk-oss-gateway/configuration#enable_non_transactional_rate_limiter). This leverages Redis Pipelining to enhance the performance of the Redis operations. Please consult the [Redis documentation](https://redis.io/docs/manual/pipelining/) for more information. + +Please consider the [Fixed Window Rate Limiter](#fixed-window-rate-limiter) algorithm as an alternative, if Redis performance is an issue. + +### Fixed Window Rate Limiter + +The Fixed Window Rate Limiter will limit the number of requests in a +particular window in time. Once the defined rate limit has been reached, +the requests will be blocked for the remainder of the configured window +duration. After the window expires, the counters restart and again allow +requests through. + +- the implementation uses a single counter value in Redis +- the counter expires after every configured window (`per`) duration. + +The implementation does not smooth out traffic bursts within a window. For any +given `rate` in a window, the requests are processed without delay, until +the rate limit is reached and requests are blocked for the remainder of the +window duration. + +When using this option, resource usage for rate limiting does not +increase with traffic. A simple counter with expiry is created for every +window and removed when the window elapses. Regardless of the traffic +received, Redis is not impacted in a negative way, resource usage remains +constant. + +This algorithm can be enabled using the following configuration option [enable_fixed_window_rate_limiter](/tyk-oss-gateway/configuration#enable_fixed_window_rate_limiter). + +If you need spike arrest behavior, the [Redis Rate Limiter](#redis-rate-limiter) should be used. + +### Dynamic algorithm selection based on request rate + +The Distributed Rate Limiter (DRL) works by distributing the +rate allowance equally among all gateways in the cluster. For example, +with a rate limit of 1000 requests per second and 5 gateways, each +gateway can handle 200 requests per second. This distribution allows for +high performance as gateways do not need to synchronize counters for each +request. + +DRL assumes an evenly load-balanced environment, which is typically +achieved at a larger scale with sufficient requests. In scenarios with +lower request rates, DRL may generate false positives for rate limits due +to uneven distribution by the load balancer. For instance, with a rate of +10 requests per second across 5 gateways, each gateway would handle only +2 requests per second, making equal distribution unlikely. + +It's possible to configure Tyk to switch automatically between the Distributed Rate Limiter +and the Redis Rate Limiter by setting the `drl_threshold` configuration. + +This threshold value is used to dynamically switch the rate-limiting +algorithm based on the volume of requests. This option sets a +minimum number of requests per gateway that triggers the Redis Rate +Limiter. For example, if `drl_threshold` is set to 2, and there are 5 +gateways, the DRL algorithm will be used if the rate limit exceeds 10 +requests per second. If it is 10 or fewer, the system will fall back to +the Redis Rate Limiter. + +See [DRL Threshold](/tyk-oss-gateway/configuration#drl_threshold) for details on how to configure this feature. + + +## Custom Rate Limiting + +Different business models may require applying rate limits and quotas not only by credentials but also by other entities, such as per application, per developer, per organization, etc. For example, if an API Product is sold to a B2B customer, the quota of API calls is usually applied to all developers and their respective applications combined, in addition to a specific credential. + +To enable this, Tyk introduced support for custom rate limit keys in [Tyk 5.3.0](/developer-support/release-notes/dashboard#5-3-0-release-notes). This feature allows you to define custom patterns for rate limiting that go beyond the default credential-based approach. + +### How Custom Rate Limiting Works + +Custom rate limit keys are applied at a policy level. When a custom rate limit key is specified, quota, rate limit and throttling will be calculated against the specified value and not against a credential ID. + +To specify a custom rate limit key, add to a policy a new metadata field called `rate_limit_pattern`. In the value field you can specify any value or expression that you want to use as a custom rate limit key for your APIs. + +The `rate_limit_pattern` field supports: +- Referencing session metadata using `$tyk_meta.FIELD_NAME` syntax +- Concatenating multiple values together using the pipe operator (`|`) + +### Configuring Custom Rate Limit Keys + +Custom rate limit keys are configured in the Tyk Dashboard by adding a metadata field to your policy: + +1. Navigate to your policy in the Tyk Dashboard +2. Add a new metadata field called `rate_limit_pattern` +3. Set the value to your desired pattern expression + +For example, if you want to specify a rate limit pattern to calculate the rate limit for a combination of developers and plans, where all credentials of a developer using the same plan share the same rate limit, you can use the following expression (assuming that the `DeveloperID` and `PlanID` metadata fields are available in a session): + +```gotemplate +$tyk_meta.DeveloperID|$tyk_meta.PlanID +``` + +Configuring custom rate limit keys + +### Important Considerations + + + +**Updating credential metadata** + +The custom rate limit key capability uses only metadata objects, such as credentials metadata available in a session. Therefore, if the `rate_limit_pattern` relies on credentials metadata, this capability will work only if those values are present. If, after evaluating the `rate_limit_pattern`, its value is equal to an empty string, the rate limiter behavior defaults to rate limiting by credential IDs. + + + +### Advanced Custom Rate Limiting + +As mentioned above, we can easily configure custom rate limit keys for simple scenarios that do not require awareness of the request context. When more complex logic or integration with external services is required to determine the rate-limiting key, for example when you want to rate limit per requester IP address, a [custom authentication plugin](/api-management/plugins/plugin-types#authentication-plugins) can be used to identify and generate the rate limiter key. + +We use an authentication plugin because [it lets us modify the session object](/api-management/plugins/plugin-types#hook-capabilities). + +
+ + + +This mechanism works only for authenticated APIs, since the authentication plugin is not invoked for unauthenticated (keyless) APIs. + + + +#### Example: Rate Limiting by IP Address + +The example below shows an IP based rate limiter implemented as a custom Go plugin for a Tyk OAS API. Note that the Go library function to [obtain the API definition](/api-management/plugins/golang#accessing-the-api-definition) is specific to Tyk OAS APIs, so you would need to modify the plugin for a Tyk Classic API. + +1. It extracts the client's IP address from the request +2. Creates a session object with rate limiting parameters (2 requests per 5 seconds) +3. Sets a custom `rate_limit_pattern` in the session's metadata to use the IP as the rate limiting key +4. Stores this session in Tyk's session store + +When Tyk processes subsequent requests, it uses the IP address as the rate-limiting key, allowing you to rate-limit by IP address. + + + +```go +// IP Rate Limiter for Tyk OAS APIs +func Authenticate(rw http.ResponseWriter, r *http.Request) { + // Get the API definition + requestedAPI := ctx.GetOASDefinition(r) + if requestedAPI == nil { + logger.Error("Could not get Tyk OAS API Definition") + rw.WriteHeader(http.StatusInternalServerError) + return + } + + // Extract the client's real IP address + realIp := request.RealIP(r) + + // Create a session object with rate limiting parameters + sessionObject := &user.SessionState{} + sessionObject = &user.SessionState{ + OrgID: requestedAPI.OrgID, + Rate: 2, // Allow 2 requests + Per: 5, // Per 5 seconds + AccessRights: map[string]user.AccessDefinition{ + requestedAPI.APIID: { + APIID: requestedAPI.APIID, + }, + }, + MetaData: map[string]interface{}{ + "rate_limit_pattern": realIp, // Use IP address as rate limit key + }, + } + + logger.Info("Session Alias: ", sessionObject.Alias) + + // Set session state using session object + ctx.SetSession(r, sessionObject, false) + logger.Info("Session created for request") +} +``` + +#### How to Use This + +1. **Build and Deploy plugin**: Build the plugin and deploy it to your Tyk Gateway. Refer to the [Go Plugin Development Guide](/api-management/plugins/golang#setting-up-your-environment) for instructions on building and deploying Go plugins. + +2. **Configure your API**: Create an authenticated API and set up your API to use the [custom authentication plugin](/api-management/plugins/golang#loading-custom-go-plugins-into-tyk). Note that you will need to select the [multiple authentication](/basic-config-and-security/security/authentication-authorization/multiple-auth) method to invoke both the plugin and your chosen auth method. + +3. **Test your implementation**: Make requests to your API and verify that rate limiting is applied based on client IP addresses. + +While this example demonstrates IP-based rate limiting, you can modify the `rate_limit_pattern` to use any value you want as the rate limiting key, such as: +- A specific header value: `request.Header.Get("X-Custom-ID")` +- A combination of values: `userID + "-" + deviceID` +- A value extracted from the request body or JWT claims + +## Rate Limiting Layers + +You can protect your upstream services from being flooded with requests by configuring rate limiting in Tyk Gateway. Rate limits in Tyk are configured using two parameters: allow `rate` requests in any `per` time period (given in seconds). + +As explained in the [Rate Limiting Concepts](/api-management/rate-limit#introduction) section, Tyk supports configuration of rate limits at both the API-Level and Key-Level for different use cases. + +The API-Level rate limit takes precedence over Key-Level, if both are configured for a given API, since this is intended to protect your upstream service from becoming overloaded. The Key-Level rate limits provide more granular control for managing access by your API clients. + +### Configuring the rate limiter at the API-Level + +If you want to protect your service with an absolute limit on the rate of requests, you can configure an API-level rate limit. You can do this from the API Designer in Tyk Dashboard as follows: + +1. Navigate to the API for which you want to set the rate limit +2. From the **Core Settings** tab, navigate to the **Rate Limiting and Quotas** section +3. Ensure that **Disable rate limiting** is not selected +4. Enter in your **Rate** and **Per (seconds)** values +5. **Save/Update** your changes + +Tyk will now accept a maximum of **Rate** requests in any **Per** period to the API and will reject further requests with an `HTTP 429 Too Many Requests` error. + +Check out the following video to see this being done. + + + +### Configuring the rate limiter at the Key-Level + +If you want to restrict an API client to a certain rate of requests to your APIs, you can configure a Key-Level rate limit via a [Policy](/api-management/policies). The allowance that you configure in the policy will be consumed by any requests made to APIs using a key generated from the policy. Thus, if a policy grants access to three APIs with `rate=15 per=60` then a client using a key generated from that policy will be able to make a total of 15 requests - to any combination of those APIs - in any 60 second period before receiving the `HTTP 429 Too Many Requests` error. + + + +It is assumed that the APIs being protected with a rate limit are using the [auth token](/api-management/authentication/bearer-token) client authentication method and policies have already been created. + + + +You can configure this rate limit from the API Designer in Tyk Dashboard as follows: + +1. Navigate to the Tyk policy for which you want to set the rate limit +2. Ensure that API(s) that you want to apply rate limits to are selected +3. Under **Global Limits and Quota**, make sure that **Disable rate limiting** is not selected and enter your **Rate** and **Per (seconds)** values +4. **Save/Update** the policy + +### Setting up a Key-Level Per-API rate limit + +If you want to restrict API clients to a certain rate of requests for a specific API you will also configure the rate limiter via the security policy. However this time you'll assign per-API limits. The allowance that you configure in the policy will be consumed by any requests made to that specific API using a key generated from that policy. Thus, if a policy grants access to an API with `rate=5 per=60` then three clients using keys generated from that policy will each independently be able to make 5 requests in any 60 second period before receiving the `HTTP 429 Too Many Requests` error. + + + +It is assumed that the APIs being protected with a rate limit are using the [auth token](/api-management/authentication/bearer-token) client authentication method and policies have already been created. + + + +You can configure this rate limit from the API Designer in Tyk Dashboard as follows: + +1. Navigate to the Tyk policy for which you want to set the rate limit +2. Ensure that API that you want to apply rate limits to is selected +3. Under **API Access**, turn on **Set per API Limits and Quota** +4. You may be prompted with "Are you sure you want to disable partitioning for this policy?". Click **CONFIRM** to proceed +5. Under **Rate Limiting**, make sure that **Disable rate limiting** is not selected and enter your **Rate** and **Per (seconds)** values +6. **Save/Update** the policy + +Check out the following video to see this being done. + + + +### Setting up a key-level per-endpoint rate limit + +To restrict the request rate for specific API clients on particular endpoints, you can use the security policy to assign per-endpoint rate limits. These limits are set within the policy and will be #enforced for any requests made to that endpoint by clients using keys generated from that policy. + +Each key will have its own independent rate limit allowance. For example, if a policy grants access to an endpoint with a rate limit of 5 requests per 60 seconds, each client with a key from that policy can make 5 requests to the endpoint in any 60-second period. Once the limit is reached, the client will receive an HTTP `429 Too Many Requests` error. + +If no per-endpoint rate limit is defined, the endpoint will inherit the key-level per-API rate limit or the global rate limit, depending on what is configured. + + + +The following assumptions are made: + - The [ignore authentication](/api-management/traffic-transformation/ignore-authentication) middleware should not be enabled for the relevant endpoints. + - If [path-based permissions](/api-management/gateway-config-managing-classic#path-based-permissions) are configured, they must grant access to these endpoints for keys generated from the policies. + + + +You can configure per-endpoint rate limits from the API Designer in Tyk Dashboard as follows: + +1. Navigate to the Tyk policy for which you want to set the rate limit +2. Ensure that API that you want to apply rate limits to is selected +3. Under **API Access** -> **Set endpoint-level usage limits** click on **Add Rate Limit** to configure the rate limit. You will need to provide the rate limit and the endpoint path and method. +4. **Save/Update** the policy + + +### Setting Rate Limits in the Tyk Community Edition Gateway (CE) + +#### Configuring the rate limiter at the (Global) API-Level + +Using the `global_rate_limit` field in the API definition you can specify the API-level rate limit in the following format: `{"rate": 10, "per": 60}`. + +An equivalent example using Tyk Operator is given below: + +```yaml {linenos=table,hl_lines=["14-17"],linenostart=1} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-global-rate-limit +spec: + name: httpbin-global-rate-limit + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + # setting a global rate-limit for the API of 10 requests per 60 seconds + global_rate_limit: + rate: 10 + per: 60 +``` + +### Configuring the rate limiter on the session object + +All actions on the session object must be done via the Gateway API. + +1. Ensure that `allowance` and `rate` are set to the same value: this should be number of requests to be allowed in a time period, so if you wanted 100 requests every second, set this value to 100. + +2. Ensure that `per` is set to the time limit. Again, as in the above example, if you wanted 100 requests per second, set this value to 1. If you wanted 100 requests per 5 seconds, set this value to 5. + +#### Can I disable the rate limiter? + +Yes, the rate limiter can be disabled for an API Definition by selecting **Disable Rate Limits** in the API Designer, or by setting the value of `disable_rate_limit` to `true` in your API definition. + +Alternatively, you could also set the values of `Rate` and `Per (Seconds)` to be 0 in the API Designer. + + + +Disabling the rate limiter at the API-Level does not disable rate limiting at the Key-Level. Tyk will enforce the Key-Level rate limit even if the API-Level limit is not set. + + + +#### Can I set rate limits by IP address? + +Not yet, though IP-based rate limiting is possible using custom pre-processor middleware JavaScript that generates tokens based on IP addresses. See our [Middleware Scripting Guide](/api-management/plugins/javascript#using-javascript-with-tyk) for more details. + +## Rate Limiting by API + +### Tyk Classic API Definition + +The per-endpoint rate limit middleware allows you to enforce rate limits on specific endpoints. This middleware is configured in the Tyk Classic API Definition, either via the Tyk Dashboard API or in the API Designer. + +To enable the middleware, add a new `rate_limit` object to the `extended_paths` section of your API definition. + +The `rate_limit` object has the following configuration: + +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `enabled`: boolean to enable or disable the rate limit +- `rate`: the maximum number of requests that will be permitted during the interval (window) +- `per`: the length of the interval (window) in seconds + +You can set different rate limits for various endpoints by specifying multiple `rate_limit` objects. + +#### Simple endpoint rate limit + +For example: + +```json {linenos=true, linenostart=1} +{ + "use_extended_paths": true, + "extended_paths": { + "rate_limit": [ + { + "path": "/anything", + "method": "GET", + "enabled": true, + "rate": 60, + "per": 1 + } + ] + } +} +``` + +In this example, the rate limit middleware has been configured for HTTP +`GET` requests to the `/anything` endpoint, limiting requests to 60 per +second. + +#### Advanced endpoint rate limit + +For more complex scenarios, you can configure rate limits for multiple +paths. The order of evaluation matches the order defined in the +`rate_limit` array. For example, if you wanted to limit the rate of +`POST` requests to your API allowing a higher rate to one specific +endpoint you could configure the API definition as follows: + +```json {linenos=true, linenostart=1} +{ + "use_extended_paths": true, + "extended_paths": { + "rate_limit": [ + { + "path": "/user/login", + "method": "POST", + "enabled": true, + "rate": 100, + "per": 1 + }, + { + "path": "/.*", + "method": "POST", + "enabled": true, + "rate": 60, + "per": 1 + } + ] + } +} +``` + +In this example, the first rule limits `POST` requests to `/user/login` +to 100 requests per second (rps). Any other `POST` request matching the +regex pattern `/.*` will be limited to 60 requests per second. The order +of evaluation ensures that the specific `/user/login` endpoint is matched +and evaluated before the regex pattern. + +The per-endpoint rate limit middleware allows you to enforce rate limits on specific endpoints. This middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation), either via the Tyk Dashboard API or in the API Designer. + +If you’re using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/rate-limit#tyk-classic-api-definition) page. + +### Tyk OAS API Definition + +The design of the Tyk OAS API Definition takes advantage of the +`operationId` defined in the OpenAPI Document that declares both the path +and method for which the middleware should be added. Endpoint `paths` +entries (and the associated `operationId`) can contain wildcards in the +form of any string bracketed by curly braces, for example +`/status/{code}`. These wildcards are so they are human-readable and do +not translate to variable names. Under the hood, a wildcard translates to +the “match everything” regex of: `(.*)`. + +The rate limit middleware (`rateLimit`) can be added to the `operations` section of the +Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition +for the appropriate `operationId` (as configured in the `paths` section +of your OpenAPI Document). + +The `rateLimit` object has the following configuration: + +- `enabled`: enable the middleware for the endpoint +- `rate`: the maximum number of requests that will be permitted during the interval (window) +- `per`: the length of the interval (window) in time duration notation (e.g. 10s) + +#### Simple endpoint rate limit + +For example: + +```json {hl_lines=["39-43"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-rate-limit", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/status/200": { + "get": { + "operationId": "status/200get", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-rate-limit", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-rate-limit/", + "strip": true + } + }, + "middleware": { + "operations": { + "status/200get": { + "rateLimit": { + "enabled": true, + "rate": 60, + "per": "1s" + } + } + } + } + } +} +``` + +In this example, a rate limit has been configured for the `GET +/status/200` endpoint, limiting requests to 60 per second. + +The configuration above is a complete and valid Tyk OAS API Definition +that you can import into Tyk to try out the Per-endpoint Rate Limiter +middleware. + +#### Advanced endpoint rate limit + +For more complex scenarios, you can configure rate limits for multiple +paths. The order of evaluation matches the order that endpoints are +defined in the `paths` section of the OpenAPI description. For example, +if you wanted to limit the rate of `POST` requests to your API allowing a +higher rate to one specific endpoint you could configure the API +definition as follows: + +```json {hl_lines=["49-53", "56-60"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "advanced-rate-limit", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/user/login": { + "post": { + "operationId": "user/loginpost", + "responses": { + "200": { + "description": "" + } + } + } + }, + "/{any}": { + "post": { + "operationId": "anypost", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "advanced-rate-limit", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/advanced-rate-limit/", + "strip": true + } + }, + "middleware": { + "operations": { + "user/loginpost": { + "rateLimit": { + "enabled": true, + "rate": 100, + "per": "1s" + } + }, + "anypost": { + "rateLimit": { + "enabled": true, + "rate": 60, + "per": "1s" + } + } + } + } + } +} +``` + +In this example, the first rule limits requests to the `POST /user/login` +endpoint to 100 requests per second (rps). Any other `POST` request to an +endpoint path that matches the regex pattern `/{any}` will be limited to +60 rps. The order of evaluation ensures that the specific `POST +/user/login` endpoint is matched and evaluated before the regex pattern. + +The configuration above is a complete and valid Tyk OAS API Definition +that you can import into Tyk to try out the Per-endpoint Rate Limiter +middleware. + +### Configuring the middleware in the API Designer + +Configuring per-endpoint rate limits for your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Rate Limit middleware** + + Select **ADD MIDDLEWARE** and choose **Rate Limit** from the *Add Middleware* screen. + + Adding the Rate Limit middleware + +3. **Configure the middleware** + + You must provide the path to the compiled plugin and the name of the Go function that should be invoked by Tyk Gateway when the middleware is triggered. + + Configuring the per-endpoint custom plugin + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes. + +## Rate Limiting with Tyk Streams + +A rate limit is a strategy for limiting the usage of a shared resource across parallel components in a Tyk Streams instance, or potentially across multiple instances. They are configured as a resource: + +```yaml +rate_limit_resources: + - label: foobar + local: + count: 500 + interval: 1s +``` + +And most components that hit external services have a field `rate_limit` for specifying a rate limit resource to use. For example, if we wanted to use our `foobar` rate limit with a HTTP request: + +```yaml +input: + http_client: + url: TODO + verb: GET + rate_limit: foobar +``` + +By using a rate limit in this way we can guarantee that our input will only poll our HTTP source at the rate of 500 requests per second. + +{/* TODO: when rate-limit processor supported: +Some components don't have a `rate_limit` field but we might still wish to throttle them by a rate limit, in which case we can use the rate_limit processor that applies back pressure to a processing pipeline when the limit is reached. For example: + +```yaml +input: + http_server: + path: /post +output: + http_server: + ws_path: /subscribe +pipeline: + processors: + - rate_limit: + resource: example_rate_limit +rate_limit_resources: + - label: example_rate_limit + local: + count: 3 + interval: 20s +``` */} + + +### Local + +The local rate limit is a simple X every Y type rate limit that can be shared across any number of components within the pipeline but does not support distributed rate limits across multiple running instances of Tyk Streams. + +```yml +# Config fields, showing default values +label: "" +local: + count: 1000 + interval: 1s +``` + +**Configuration Fields** + +**count** + +The maximum number of requests to allow for a given period of time. + + +Type: `int` +Default: `1000` + +**interval** + +The time window to limit requests by. + + +Type: `string` +Default: `"1s"` diff --git a/api-management/request-quotas.mdx b/api-management/request-quotas.mdx new file mode 100644 index 0000000000..925d5bd1d0 --- /dev/null +++ b/api-management/request-quotas.mdx @@ -0,0 +1,777 @@ +--- +title: "Request Quotas" +description: "Overview of Rate Quotas with the Tyk Gateway" +keywords: "Request Quotas, API Quotas, Usage Limits, Consumption Control" +sidebarTitle: "Request Quotas" +--- + +## Introduction + +Request Quotas in Tyk Gateway allow you to set a maximum number of API requests for a specific API key or [Policy](/api-management/policies) over longer, defined periods (e.g., day, week, month). This feature is distinct from [rate limiting](/api-management/rate-limit) (which controls requests per second), and it is essential for managing API consumption, enforcing service tiers, and protecting your backend services from sustained overuse over time. + +```mermaid +flowchart LR + Client[API Client] -->|Makes Request| Gateway[Tyk Gateway] + Gateway -->|Check Quota| Redis[(Redis)] + Redis -->|Quota OK| Gateway + Redis -->|Quota Exceeded| Gateway + Gateway -->|If Quota OK| Upstream[Upstream API] + Gateway -->|If Quota Exceeded| Reject[Reject Request] + Upstream -->|Response| Gateway + Gateway -->|Response| Client +``` + +### Key Benefits + +* **Enforce Usage Limits:** Cap the total number of requests allowed over extended periods (days, weeks, months) per consumer. +* **Implement Tiered Access:** Easily define different usage allowances for various subscription plans (e.g., Free, Basic, Pro). +* **Protect Backend Services:** Prevent individual consumers from overwhelming upstream services with consistently high volume over long durations. +* **Enable Usage-Based Monetization:** Provide a clear mechanism for charging based on consumption tiers. + +--- +## Quick Start + +### Overview + +In this tutorial, we will configure Request Quotas on a [Policy](/api-management/policies) to limit the number of requests an API key can make over a defined period. Unlike [rate limits](/api-management/rate-limit) (requests per second), quotas control overall volume. We'll set a low quota limit with a short renewal period for easy testing, associate a key with the policy, observe blocked requests once the quota is exhausted, and verify that the quota resets after the period elapses. This guide primarily uses the Tyk Dashboard for configuration. + +### Prerequisites + +- **Working Tyk Environment:** You need access to a running Tyk instance that includes both the Tyk Gateway and Tyk Dashboard components. For Docker setup instructions, please refer to this [guide](/tyk-self-managed/install/docker). +- **Curl, Seq and Sleep**: These tools will be used for testing. + +### Instructions + +#### Create an API + +1. **Create an API:** + 1. Log in to your Tyk Dashboard. + 2. Navigate to **API Management > APIs** + 3. Click **Add New API** + 4. Click **Import** + 5. Select **Import Type** as **Tyk API** + 6. Copy the below Tyk OAS definition in the text box and click **Import API** to create an API. + + + + + ```json + { + "components": { + "securitySchemes": { + "authToken": { + "in": "header", + "name": "Authorization", + "type": "apiKey" + } + } + }, + "info": { + "title": "Request Quota Test", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "security": [ + { + "authToken": [] + } + ], + "servers": [ + { + "url": "http://tyk-gateway.localhost:8080/request-quota-test/" + } + ], + "x-tyk-api-gateway": { + "info": { + "name": "Request Quota Test", + "state": { + "active": true + } + }, + "middleware": { + "global": { + "contextVariables": { + "enabled": true + }, + "trafficLogs": { + "enabled": true + } + } + }, + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "authToken": { + "enabled": true + } + } + }, + "listenPath": { + "strip": true, + "value": "/request-quota-test/" + } + }, + "upstream": { + "url": "http://httpbin.org/" + } + } + } + ``` + + + + +#### Configure Policy and Quota + +2. **Create and Configure a Security Policy with a Request Quota:** + + + + + 1. Navigate to **API Security > Policies** in the Tyk Dashboard sidebar. + 2. Click the **Add Policy** button. + 3. Under the **1. Access Rights** tab, in the **Add API Access Rule** section, select the `Request Quota Test` API. + 4. Scroll down to the **Global Limits and Quota** section (still under the **1. Access Rights** tab): + * **Important:** Disable **[Rate Limiting](/api-management/rate-limit)** by selecting **Disable rate limiting** option, so it doesn't interfere with testing the quota. + * Set the following values for `Usage Quotas`: + * Uncheck the `Unlimited requests` checkbox + * Enter `10` into the **Max Requests per period** field. (This is our low quota limit for testing). + * Select `1 hour` from the **Quota resets every:** dropdown. (In the next step, we will modify it to 60 seconds via API for quick testing, as 1 hour is a very long period. From the dashboard, we can only select pre-configured options.) + 5. Select the **2. Configuration** tab. + 6. In the **Policy Name** field, enter `Request Quota Policy`. + 7. From the **Key expire after** dropdown, select `1 hour`. + 8. Click the **Create Policy** button. + + + + + policy with request quota configured + +4. **Update Quota Reset Period via API:** + + As the Dashboard UI doesn't allow setting a shorter duration, we will set the Quota reset period to a value of 1 minute for testing purposes. The following commands search for the policy, modify its `quota_renewal_rate` to 60 seconds, and update the API. + + **Note:** Obtain your Dashboard API key by clicking on the User profile at the top right corner, then click on `Edit Profile`, and select the key available under `Tyk Dashboard API Access Credentials`. Now in the below command replace `` with the API key you obtained from the Dashboard UI. + + ``` + curl -s --location 'http://localhost:3000/api/portal/policies/search?q=Request%20Quota%20Policy' \ + -H "Authorization: " \ + -H "Accept: application/json" > policy.json + + jq '.Data[0] | .quota_renewal_rate = 60' policy.json > updated_policy.json + jq -r '.Data[0]._id' policy.json > policy_id.txt + + curl --location "http://localhost:3000/api/portal/policies/$(cat policy_id.txt)" \ + -H "Authorization: " \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -X PUT \ + -d @updated_policy.json + ``` + +3. **Associate an Access Key with the Policy:** + + + + + 1. Navigate to **API Security > Keys** in the Tyk Dashboard sidebar. + 2. Click the **Add Key** button. + 3. Under the **1. Access Rights** tab: + * In the **Apply Policy** section, select the `Request Quota Policy`. + 4. Select the **2. Configuration** tab. + 5. In the **Alias** field, enter `Request Quota Key`. This provides a human-readable identifier. + 6. From the **Expires** dropdown, select `1 hour`. + 7. Click the **Create Key** button. + 8. A pop-up window **"Key created successfully"** will appear displaying the key details. **Copy the Key ID** value shown and save it securely. You will need this key to make API requests in the following steps. + 9. Click **OK** to close the pop-up. + + + + +#### Testing + +4. **Test Quota Exhaustion:** + + We've set a quota of 10 requests per 60 seconds. Let's send more than 10 requests within that window to observe the quota being enforced. + + 1. Open your terminal. + 2. Execute the following command, replacing `` with the API Key ID you saved earlier. This command attempts to send 15 requests sequentially. + + ```bash + for i in $(seq 1 15); do \ + echo -n "Request $i: "; \ + curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: " http://tyk-gateway.localhost:8080/request-quota-test/get; \ + sleep 0.1; \ + done + ``` + + *(Note: The `sleep 0.1` adds a tiny delay, but ensure all 15 requests execute well within the 60-second quota window).* + + 3. **Expected Observation:** You should see the first 10 requests succeed, returning an HTTP status code `200`. After the 10th request, the subsequent requests (11 through 15) should be blocked by the quota limit, returning an HTTP status code `403` (Forbidden). + + **Sample Output:** + + ```bash + Request 1: 200 + Request 2: 200 + Request 3: 200 + Request 4: 200 + Request 5: 200 + Request 6: 200 + Request 7: 200 + Request 8: 200 + Request 9: 200 + Request 10: 200 + Request 11: 403 + Request 12: 403 + Request 13: 403 + Request 14: 403 + Request 15: 403 + ``` + +5. **Test Quota Reset:** + + Now, let's wait for the quota period (60 seconds) to elapse and then send another request to verify that the quota allowance has been reset. + + 1. Wait slightly longer than the reset period in the same terminal. The command below waits for 70 seconds. + + ```bash + echo "Waiting for quota to reset (70 seconds)..." + sleep 70 + echo "Wait complete. Sending one more request..." + ``` + + 2. Send one more request using the same API key: + + ```bash + curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: " http://tyk-gateway.localhost:8080/request-quota-test/get + ``` + + 3. **Expected Observation:** This request should now succeed, returning an HTTP status code `200`. This demonstrates that because the 60-second quota period ended, the *next* request made after that period triggered the quota reset, replenishing the allowance. + + **Sample Output:** + + ```bash + Waiting for quota to reset (70 seconds)... + Wait complete. Sending one more request... + 200 + ``` + +This quick start demonstrates the fundamental behaviour of Request Quotas: they limit the total number of requests allowed within a specific period and automatically reset the allowance once that period renews (triggered by the next request). + +--- +## Configuration Options + +Request Quotas in Tyk can be configured at various levels. + +The configuration involves setting two specific fields: + +1. **QuotaMax**: The maximum number of requests allowed during the quota period. + - Set to `-1` for unlimited requests + - Set to a positive integer (e.g., `1000`) to limit total requests + +2. **QuotaRenewalRate**: The time in seconds for which the quota applies. + - Example: `3600` for the hourly quota (1 hour = 3600 seconds) + - Example: `86400` for the daily quota (24 hours = 86400 seconds) + - Example: `2592000` for the monthly quota (30 days = 2592000 seconds) + +### System-Level Configuration + +Global quota settings are configured in the Tyk Gateway configuration file (`tyk.conf`). These settings affect how quotas are enforced across the entire gateway. + + + + +```json +{ +// Partial config from tyk.conf + "enforce_org_quotas": true, + "enforce_org_data_detail_logging": false, + "monitor": { + "enable_trigger_monitors": true, + "global_trigger_limit": 80.0, + "monitor_user_keys": true, + "monitor_org_keys": true + }, +// ... more config follows +} +``` + +- `enforce_org_quotas`: When set to `true`, enables organization-level quota enforcement +- `monitor.enable_trigger_monitors`: Enables quota monitoring and webhook triggers +- `monitor.global_trigger_limit`: Percentage of quota usage that triggers alerts (e.g., 80.0 means 80%) +- `monitor.monitor_user_keys`: Enables monitoring for individual API keys +- `monitor.monitor_org_keys`: Enables monitoring for organization quotas + + + +```bash +export TYK_GW_ENFORCEORGQUOTAS=true +``` + + + +Refer to the [Tyk Gateway Configuration Reference](/tyk-oss-gateway/configuration#enforce_org_quotas) for more details on this setting. + + +{/* ### Organization-Level Configuration + +Organization quotas limit the total number of requests across all APIs for a specific organization. These are enforced by the `OrganizationMonitor` middleware when `enforce_org_quotas` is enabled. + +- `quota_max`: Maximum number of requests allowed during the quota period +- `quota_renewal_rate`: Time in seconds for the quota period (e.g., 3600 for hourly quotas) + +Organization quotas are configured through the Tyk Dashboard API or Gateway API: + +```bash +curl -X POST -H "Authorization: {your-api-key}" \ + -H "Content-Type: application/json" \ + -d '{ + "quota_max": 1000, + "quota_renewal_rate": 3600, + }' \ + http://tyk-gateway:8080/tyk/org/keys/{org-id} +``` */} + +### API-Level Configuration + +You **cannot set** quota values within an API Definition, but you can **disable** quota checking entirely for all requests proxied through that specific API, regardless of Key or Policy settings. This is useful if an API should never have quota limits applied. + + + + + +In a Tyk OAS API Definition, you can globally disable quotas for specific APIs: + +- **skipQuota**: When set to true, disables quota enforcement for the API. +- **skipQuotaReset**: When set to true, prevents quota counters from being reset when creating or updating quotas. + +```json +{ + // Partial config from Tyk OAS API Definition + "middleware": { + "global": { + "skipQuota": true, + "skipQuotaReset": false + } + }, + // ... more config follows +} +``` + +Refer to the [Tyk OAS API Definition reference](/api-management/gateway-config-tyk-oas#global) for details. + + + + +In a Tyk Classic API Definition (JSON), set the `disable_quota` field to `true`. + +```json +{ + // Partial config from Tyk Classic API Definition + "disable_quota": true // Set to true to disable quota checks + // ... more config follows +} + +``` + +Refer to the [Tyk Classic API Definition reference](/api-management/gateway-config-tyk-classic) for details. + + + + + + + +### Configure via UI + +The Tyk Dashboard provides a straightforward interface to set request quota parameters using [Policies](/api-management/policies) or directly in the [Session](/api-management/access-control/sessions-and-keys/understanding-sessions) when creating a Key. + + + + + +The image below shows a policy with request quotas. Any key using this policy will inherit the quota settings and behave as follows: each key will be permitted 1000 requests per 24-hour (86400 seconds) cycle before the quota resets. + +policy with request quota configured + +
+ +1. Navigate to **API Security > Policies** in the Tyk Dashboard sidebar +2. Click the **Add Policy** button +3. Under the **1. Access Rights** tab and in the **Add API Access Rule** section, select the required API +4. Scroll down to the **Global Limits and Quota** section (still under the **1. Access Rights** tab): + * Enable `Request Quotas` by setting the following values in the `Usage Quotas` section: + * Uncheck the `Unlimited Requests` checkbox + * Field **Requests (or connection attempts) per period** - Enter the total number of requests a client can use during the defined quota period. + * Field **Quota resets every:** - Select the duration of the quota period. +5. Select the **2. Configuration** tab +6. In the **Policy Name** field, enter a name +7. From the **Key expire after** dropdown, select an option +8. Click the **Create Policy** button + + +
+ + + +The image below shows an access key with request quotas. This access key behaves as follows: each key will be permitted 1000 requests per 24-hour (86400 seconds) cycle before the quota resets. + +**Note:** Direct key configuration overrides policy settings only for that specific key. + +policy with request quota configured + +
+ +1. Navigate to **API Security > Keys** in the Tyk Dashboard sidebar +2. Click the **Create Key** button +3. Under the **1. Access Rights** tab: + * Select **Choose API** + * In the **Add API Access Rule** section, select the required API +4. Scroll down to the **Global Limits and Quota** section (still under the **1. Access Rights** tab): + * Enable `Request Quotas` by setting the following values in the `Usage Quotas` section: + * Uncheck the `Unlimited Requests` checkbox + * Field **Requests (or connection attempts) per period** - Enter the total number of requests a client can use during the defined quota period. + * Field **Quota resets every:** - Select the duration of the quota period. +5. Select the **2. Configuration** tab +6. In the **Alias** field, enter a name. This human-readable identifier makes tracking and managing this specific access key easier in your analytics and logs. +7. From the **Expires** dropdown, select an option +8. Click the **Create Key** button + + +
+ +
+ +### Configure via API + +These are the fields that you can set directly in the Policy object or the Access Key: + +```json +{ + // Partial policy/session object fields + "quota_max": 1000, // Allow one thousand requests + "quota_renewal_rate": 86400, // 1 day or 24 hours + // ... more config follows +} +``` + + + + + +To update the policy, do the following: +1. Retrieve the policy object using `GET /api/portal/policies/{POLICY_ID}` +2. Add or modify the `quota_max` and `quota_renewal_rate` fields within the policy JSON object +3. Update the policy using `PUT /api/portal/policies/{POLICY_ID}` with the modified object, or create a new one using `POST /api/portal/policies/` + +**Explanation:** +The above adds request quotas to a policy. Any key using this policy will inherit the quotas settings and behaves as follows: each key will be permitted 1000 requests per 24-hour (86400 seconds) cycle before the quota resets. + + + + + +**Note:** Direct key configuration overrides policy settings only for that specific key. + +To update the access key do the following: +1. Retrieve the key's session object using `GET /api/keys/{KEY_ID}` +2. Add or modify the `quota_max` and `quota_renewal_rate` fields within the session object JSON +3. Update the key using `PUT /api/keys/{KEY_ID}` with the modified session object + +**Explanation:** +The above adds quotas to an access key. Any request made by the key will behave as follows: each key will be permitted 1000 requests per 24-hour (86400 seconds) cycle before the quota resets. + + + + + +### Important Considerations + +* **Policy Precedence:** Quotas set on a [Policy](/api-management/policies) apply to all keys using that policy *unless* overridden by a specific quota set directly on the key (using the "Set per API Limits and Quota" option). +* **Unlimited Quota:** Setting `quota_max` to `-1` grants unlimited requests for the quota period. +* **Event-Driven Resets:** Quotas reset *after* the `quota_renewal_rate` (in seconds) has passed *and* upon the next request using the key. They do not reset automatically on a fixed schedule (e.g., precisely at midnight or the 1st of the month) unless external automation updates the session object. +* **Response Headers:** Historically, Tyk populated the `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers with quota data. Starting with Tyk v5.13.0, this [behavior is configurable](/api-management/rate-limit#rate-limit-response-headers) via the `rate_limit_response_headers` setting in the Gateway config. By default, for backward compatibility, these headers continue to reflect quota data, but they can be configured to reflect actual rate limits instead. + +--- +## How It Works + +Request Quotas in Tyk limit a client's total number of API requests within a defined period (hours, days, months). Unlike [rate limits](/api-management/rate-limit) that control the frequency of requests over short intervals (like seconds or minutes) to prevent immediate system overload, Request Quotas control the total volume of requests allowed over much longer periods to manage overall consumption and align with service tiers. + +When clients reach their quota limit, further requests are rejected until the quota period renews. It helps API providers implement usage-based pricing tiers, prevent API abuse, control infrastructure costs, and ensure fair resource distribution among clients. + +Think of Request Quotas as a prepaid phone plan with a fixed number of minutes per month. When you sign up, you get allocated a specific number of call minutes (API requests) that you can use over the billing period. You can make calls (API requests) at any pace you want – all at once or spread throughout the month – but once you've used up your allocated minutes, you can't make any more calls until the next billing cycle begins. + +```mermaid +flowchart LR + Client[API Client] -->|Makes Request| Gateway[Tyk Gateway] + Gateway -->|Check Quota| Redis[(Redis)] + Redis -->|Quota OK| Gateway + Redis -->|Quota Exceeded| Gateway + Gateway -->|If Quota OK| Upstream[Upstream API] + Gateway -->|If Quota Exceeded| Reject[Reject Request] + Upstream -->|Response| Gateway + Gateway -->|Response| Client +``` + +### How Tyk Implements Quotas + +Tyk implements request quotas using a [Redis](/tyk-configuration-reference/redis-cluster-sentinel) based counter mechanism with time-based expiration. Here's a detailed breakdown of the implementation: + +```mermaid +graph LR + A[API Request Received] --> B(Check Redis Quota Counter); + B -- Counter < QuotaMax --> C{Increment Redis Counter}; + C --> D[Calculate quota_remaining = QuotaMax - Counter]; + D --> E[Update Session State]; + E --> F[Forward Request to Upstream]; + B -- Counter >= QuotaMax --> G[Reject Request with 403]; +``` + +#### Core Components + +1. **[Redis Storage](/tyk-configuration-reference/redis-cluster-sentinel)**: Quotas are tracked in Redis using incrementing counters for each API key. The TTL is set to the quota renewal period, and the counter is reset to 0 on the next request after expiration. + + Here is a sample Redis key for a Request Quota: + ``` + quota-[scope]-[key_hash] + ``` + + Where: + - `scope` is optional and represents an API-specific allowance scope + - `key_hash` is the hashed API key (if hash keys are enabled) + +2. **Session State**: Quota configuration is stored in the user's `SessionState`, which contains several quota-related fields: + + - `QuotaMax`: Maximum number of requests allowed during the quota period. + - `QuotaRemaining`: Number of requests remaining for the current period. **Note:** This is a derived value, not the primary counter. + - `QuotaRenews`: Unix timestamp when the quota will reset. + - `QuotaRenewalRate`: Time in seconds for the quota period (e.g., 3600 for hourly quotas). + +3. **Middleware**: The quota check is performed by the `RateLimitAndQuotaCheck` middleware + +#### Quota Enforcement + +The core logic for checking and enforcing Request Quotas is executed within the `RateLimitAndQuotaCheck` middleware, which is a step in the request processing pipeline. Here's a breakdown of this process: + +1. **Initiation:** As a request enters the Tyk Gateway, it passes through configured middleware. The quota validation process begins when it hits the `RateLimitAndQuotaCheck` middleware. + +2. **Applicability Check:** The middleware first determines if quota enforcement is relevant: + * It checks the API Definition to see if quotas are globally disabled. If so, the process stops here for quotas and the request proceeds. + * It identifies the API key for the request and retrieves its associated `SessionState`. + +3. **Retrieve Limits:** The middleware accesses the `SessionState` to get the specific quota parameters applicable to this key and potentially the specific API being accessed (if per-API quotas are configured): + * `QuotaMax`: The maximum number of requests allowed. + * `QuotaRenewalRate`: The duration (in seconds) of the quota period for setting the TTL in Redis. + +4. **Redis Interaction & Enforcement:** This is the core enforcement step, interacting directly with Redis: + * **Construct Key:** Generates the unique Redis key for tracking this specific quota counter (e.g., `quota-{scope}-{api-key-hash}`). + * **Check Expiry/Existence:** It checks Redis to see if the key exists and if its TTL is still valid. + * **Handle Renewal (If Expired/Missing):** If the key doesn't exist or its TTL has passed, Tyk initiates the renewal logic described previously (attempting a distributed lock, setting the counter to 0, and applying the `QuotaRenewalRate` as the new TTL). + * **Increment Counter:** Tyk atomically increments the Redis counter value. This operation returns the *new* value of the counter *after* the increment. + * **Compare Against Limit:** The middleware compares this *new* counter value against the `QuotaMax` retrieved from the session state. + * **Decision:** + * If `new_counter_value <= QuotaMax`: The request is within the allowed quota. + * If `new_counter_value > QuotaMax`: This request has exceeded the quota limit. + +5. **Outcome:** + * **Quota OK:** The middleware allows the request to proceed to the next stage in the processing pipeline (e.g., other middleware, upstream service). + * **Quota Exceeded:** The middleware halts further request processing down the standard pipeline. It prepares and returns an error response to the client, typically `HTTP 403 Forbidden` with a "Quota exceeded" message. + +6. **Session State Update:** Regardless of whether the request was allowed or blocked due to the quota, the middleware calls an internal function (like `updateSessionQuota`) to update the in-memory `SessionState` associated with the API key. This update synchronizes the `QuotaRemaining` field in the session with the latest calculated state based on the Redis counter and its expiry. It ensures that subsequent operations within the same request lifecycle (if any) or diagnostic information have access to the most recent quota status. + +#### Quota Reset Mechanisms + +The available allowance (`QuotaRemaining`) for an API key is replenished back to its maximum (`QuotaMax`) through several distinct mechanisms: + +1. **Event-Driven Renewal (Primary Mechanism):** + * **Condition:** This occurs *after* the time duration specified by `QuotaRenewalRate` (in seconds) has elapsed since the quota period began (i.e., since the last reset or key creation/update). In Redis, this corresponds to the Time-To-Live (TTL) expiring on the quota tracking key. + * **Trigger:** The reset is **not** automatic based on a timer. It is triggered by the **next API request** made using that specific key *after* the `QuotaRenewalRate` duration has passed (and the Redis TTL has expired). + * **Process:** Upon detecting the expired TTL during that next request, Tyk resets the Redis counter (typically by setting it to 0 and immediately incrementing it to 1 for the current request) and applies a *new* TTL based on the `QuotaRenewalRate`. This effectively makes the full `QuotaMax` available for the new period starting from that moment. + + ```mermaid + graph LR + A[Request After Quota Period] --> B{Redis Key Expired?}; + B -- Yes --> C[Reset Counter to 0]; + C --> D[Set New Expiration]; + D --> E[Process Request Normally]; + B -- No --> F[Continue Normal Processing]; + ``` + +2. **Manual Reset via API:** + * **Mechanism:** You can force an immediate quota reset for a specific API key by calling an endpoint on the Tyk Gateway Admin API. + * **Effect:** This action directly deletes the corresponding quota tracking key in Redis. The *next* request using this API key will find no existing key, triggering the renewal logic (Step 1) as if the period had just expired, immediately granting the full `QuotaMax` and setting a new TTL. This provides an immediate, on-demand refresh of the quota allowance. + +3. **Key Creation or Update:** + * **Trigger:** When a new API key is created or an existing key's configuration is updated (e.g., via the Dashboard or the Gateway API), Tyk reapplies the quota settings based on the current policy or key-specific configuration. + * **Process:** This typically involves setting the `QuotaRemaining` value to `QuotaMax` in the key's session data and ensuring the corresponding Redis key is created with the correct initial value (or implicitly reset) and its TTL set according to the `QuotaRenewalRate`. This ensures the key starts with a fresh quota allowance according to its defined limits. + * **Exception:** This behavior can be suppressed if the API definition includes the `DontSetQuotasOnCreate` field (referred to as `SkipQuotaReset` in the OAS specification), which prevents automatic quota resets during key creation or updates. + +#### Key Technical Aspects + +1. **Time-Based Reset**: Unlike [rate limiting](/api-management/rate-limit), which uses sliding windows, quotas have a fixed renewal time determined by `QuotaRenewalRate` (in seconds) + +2. **Atomic Operations**: Redis pipelining is used to ensure atomic increment and expiration setting: + +3. **Race Condition Handling**: Distributed locks prevent multiple servers from simultaneously resetting quotas + +4. **Quota Scope Support**: The implementation supports both global quotas and API-specific quotas through the scoping mechanism + +--- +## FAQs + + + +Request Quotas in Tyk limit the total number of API requests a client can make within a specific time period. Unlike rate limits (which control requests per second), quotas control the total number of requests over longer periods like hours, days, or months. Once a quota is exhausted, further requests are rejected until the quota is renewed. + + + +While both control API usage, they serve different purposes: +- **[Rate Limits](/api-management/rate-limit)** control the frequency of requests (e.g., 10 requests per second) to prevent traffic spikes and ensure consistent performance +- **Request Quotas** control the total volume of requests over a longer period (e.g., 10,000 requests per month) to manage overall API consumption and often align with business/pricing models + + + +Refer this [documentation](#configuration-options). + + + +The main parameters for configuring quotas are: +- `quota_max`: Maximum number of requests allowed during the quota period +- `quota_remaining`: Number of requests remaining for the current period +- `quota_renewal_rate`: Time in seconds during which the quota is valid (e.g., 3600 for hourly quotas) +- `quota_renews`: Timestamp indicating when the quota will reset + + + +You can disable quotas for specific APIs by setting the `disable_quota` flag to `true` in the API definition. This config will bypass quota checking for all requests to that API, regardless of any quotas set at the key or policy level. + +Refer this [documentation](#api-level-configuration). + + + +When a quota is exceeded: +1. The request is rejected with a 403 Forbidden status code +2. A "QuotaExceeded" event is triggered (which can be used for notifications or monitoring) +3. The client must wait until the quota renewal period before making additional requests +4. The quota violation is logged and can be monitored in the Tyk Dashboard + + + +Tyk stores quota information in Redis: +- Quota keys are prefixed with "quota-" followed by the key identifier +- For each request, Tyk increments a counter in Redis and checks if it exceeds the quota_max +- When a quota period expires, the counter is reset +- For distributed deployments, quota information is synchronized across all Tyk nodes + + + +Yes, you can implement per-endpoint quotas using policies enabling the "per_api" partitioning. This config allows you to define different quota limits for API endpoints, giving you fine-grained control over resource usage. + + + +Organization quotas allow you to limit the total number of requests across all keys belonging to an organization. When enabled (using `enforce_org_quotas`), Tyk tracks the combined usage of all keys in the organization and rejects requests when the organization quota is exceeded, regardless of individual key quotas. + + + +Yes, Tyk provides quota monitoring capabilities: +- You can set up trigger monitors with percentage thresholds +- When usage reaches a threshold (e.g., 80% of quota), Tyk can trigger notifications +- These notifications can be sent via webhooks to external systems +- The monitoring configuration is set in the `monitor` section of your Tyk configuration + + + +Tyk's quota renewal is event-driven rather than time-driven. Quotas don't automatically reset at specific times (like midnight); instead, they reset when the first request is made after the renewal period has passed. If no requests are made after renewal, the quota counter remains unchanged until the next request triggers the check and renewal process. + + + +You can manually reset a quota for a specific key in two ways: + +**Via Tyk Dashboard:** +1. Navigate to the "Keys" section +2. Find and select the key you want to reset +3. Click on "Reset Quota" button + +**Via Tyk Gateway API:** +``` +POST /tyk/keys/reset/{key_id} +Authorization: {your-gateway-secret} +``` +This endpoint will immediately reset the quota for the specified key, allowing the key to make requests up to its quota limit again. + + + +It depends on where the failure occurs: + +1. **Gateway Rejections (Do NOT count against quota):** Requests rejected by Tyk's early middleware checks—such as Authentication failures (401/403) or Rate Limit exceeded (429)—do *not* decrement your quota. This is because these checks happen *before* the quota evaluation in the middleware chain. + +2. **Upstream Rejections (DO count against quota):** Once a request passes Tyk's rate limiting and authentication checks, it decrements the quota. If the upstream service then returns an error (e.g., 400 Bad Request, 500 Internal Server Error), the request *will still count* against the quota. + +This behavior is designed to prevent abuse and ensure consistent quota enforcement regardless of the upstream API's response, while not penalizing users for requests that Tyk blocks at the edge. + + +In multi-datacenter or multi-region setups, quota inconsistencies can occur due to: + +1. **Redis replication lag**: If you're using separate Redis instances with replication, there may be delays in syncing quota information +2. **Network latency**: In geographically distributed setups, network delays can cause temporary inconsistencies +3. **Configuration issues**: Each gateway must be properly configured to use the same Redis database for quota storage + +To resolve this, ensure all gateways are configured to use the same Redis database or a properly configured Redis cluster with minimal replication lag. Consider using Redis Enterprise or a similar solution with cross-region synchronization capabilities for multi-region deployments. + + + +In some older versions of Tyk, setting `quota_max` to -1 (to disable quotas) would generate an error log message: `Quota disabled: quota max <= 0`. This was a known issue that has been fixed in more recent versions. + +If you're still seeing these logs, consider: +1. Upgrading to the latest version of Tyk +2. Adjusting your log level to reduce noise +3. Using the API definition's `disable_quota` flag instead of setting `quota_max` to -1 + +This log message is informational and doesn't indicate a functional problem with your API. + + + +By default, Tyk counts all requests against the quota regardless of the response code. There is no built-in configuration to count only successful (2xx) responses toward quota limits. + +If you need this functionality, you have two options: +1. Implement a custom middleware plugin that conditionally decrements the quota based on response codes +2. Use the Tyk Pump to track successful vs. failed requests separately in your analytics platform and implement quota management at the application level + + + +If you modify a quota configuration mid-period (before the renewal time): + +1. For **increasing** the quota: The new maximum will apply, but the current remaining count stays the same +2. For **decreasing** the quota: If the new quota is less than what's already been used, further requests will be rejected +3. For **changing the renewal rate**: The new renewal period will apply from the next renewal + +Changes to quota settings take effect immediately, but don't reset the current usage counter. Use the "Reset Quota" functionality to apply new settings and reset the counter immediately. + + + +Yes, Tyk provides several ways to implement different quota plans: + +1. **Policies**: Create different policies with varying quota limits and assign them to keys based on subscription level +2. **Key-specific settings**: Override policy quotas for individual keys when necessary +3. **Meta Data**: Use key metadata to adjust quota behavior through middleware dynamically +4. **Multiple APIs**: Create separate API definitions with different quota configurations for different service tiers + +This flexibility allows you to implement complex quota schemes that align with your business model and customer tiers. + + + +When troubleshooting quota issues: + +1. **Check Redis**: Ensure Redis is functioning properly and examine the quota keys directly +2. **Review logs**: Look for quota-related messages in the Tyk Gateway logs +3. **Verify configuration**: Confirm that quota settings are correctly configured in policies and API definitions +4. **Test with the API**: Use the Tyk Gateway API to check quota status for specific keys +5. **Monitor request headers**: Examine the `X-Rate-Limit-Remaining` headers in API responses + +For multi-gateway setups, verify that all gateways use the same Redis instance and that there are no synchronization issues between Redis clusters. + + + diff --git a/api-management/request-throttling.mdx b/api-management/request-throttling.mdx new file mode 100644 index 0000000000..15abef1aa8 --- /dev/null +++ b/api-management/request-throttling.mdx @@ -0,0 +1,437 @@ +--- +title: "Request Throttling" +description: "Understand how request throttling works in Tyk Gateway to protect your upstream services from traffic spikes" +keywords: "Request Throttling" +sidebarTitle: "Request Throttling" +--- + +## Introduction + +Tyk's Request Throttling feature provides a mechanism to manage traffic spikes by queuing and automatically retrying client requests that exceed [rate limits](/api-management/rate-limit), rather than immediately rejecting them. This helps protect upstream services from sudden bursts and improves the resilience of API interactions during temporary congestion. + +--- +## Quick Start + +### Overview + +In this tutorial, we will configure Request Throttling on a Tyk Security Policy to protect a backend service from sudden traffic spikes. We'll start by defining a basic rate limit on a policy, then enable throttling with specific retry settings to handle bursts exceeding that limit, associate a key with the policy, and finally test the behaviour using simulated traffic. This guide primarily uses the Tyk Dashboard for configuration. + +### Prerequisites + +- **Working Tyk Environment:** You need access to a running Tyk instance that includes both the Tyk Gateway and Tyk Dashboard components. For Docker setup instructions, please refer to this [guide](/tyk-self-managed/install/docker). +- **Curl, seq and xargs**: These tools will be used for testing. + +### Instructions + +#### Create an API + +1. **Create an API:** + 1. Log in to your Tyk Dashboard. + 2. Navigate to **API Management > APIs** + 3. Click **Add New API** + 4. Click **Import** + 5. Select **Import Type** as **Tyk API** + 6. Copy the below Tyk OAS definition in the text box and click **Import API** to create an API + + + + + ```json + { + "components": { + "securitySchemes": { + "authToken": { + "in": "header", + "name": "Authorization", + "type": "apiKey" + } + } + }, + "info": { + "title": "Request Throttling Test", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "security": [ + { + "authToken": [] + } + ], + "servers": [ + { + "url": "http://tyk-gateway.localhost:8080/request-throttling-test/" + } + ], + "x-tyk-api-gateway": { + "info": { + "name": "Request Throttling Test", + "state": { + "active": true + } + }, + "middleware": { + "global": { + "contextVariables": { + "enabled": true + }, + "trafficLogs": { + "enabled": true + } + } + }, + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "authToken": { + "enabled": true + } + } + }, + "listenPath": { + "strip": true, + "value": "/request-throttling-test/" + } + }, + "upstream": { + "url": "http://httpbin.org/" + } + } + } + ``` + + + + +#### Configure Policy and Rate Limit + +2. **Create and Configure an Security Policy with Rate Limiting:** + + + + + 1. Navigate to **API Security > Policies** in the Tyk Dashboard sidebar + 2. Click the **Add Policy** button + 3. Under the **1. Access Rights** tab, in the **Add API Access Rule** section, select the `Request Throttling Test` API + 4. Scroll down to the **Global Limits and Quota** section (still under the **1. Access Rights** tab): + * Set the following values for `Rate Limiting` + * Enter `5` into the **Requests (or connection attempts)** field + * Enter `10` into the **Per (seconds):** field + 5. Select the **2. Configuration** tab + 6. In the **Policy Name** field, enter `Request Throttling Policy` + 7. From the **Key expire after** dropdown, select `1 hour` + 8. Click the **Create Policy** button + + + + + policy with throttling configured + +3. **Associate an Access Key with the Policy:** + + + + + 1. Navigate to **API Security > Keys** in the Tyk Dashboard sidebar + 2. Click the **Add Key** button + 3. Under the **1. Access Rights** tab: + * In the **Apply Policy** section, select the `Request Throttling Policy` API + 5. Select the **2. Configuration** tab + 6. In the **Alias** field, enter `Request Throttling Key`. This provides a human-readable identifier that makes tracking and managing this specific access key easier in your analytics and logs. + 7. From the **Expires** dropdown, select `1 hour` + 8. Click the **Create Key** button + 9. A pop-up window **"Key created successfully"** will appear displaying the key details. **Copy the Key ID** value shown and save it securely. You will need this key to make API requests in the following steps + 10. Click **OK** to close the pop-up + + + + +4. **Test Rate Limit** + + So far, we've created a policy for an API definition and created a key that complies with that policy. Before enabling throttling, let's observe the standard rate limiting behaviour. We'll send 10 requests in parallel using `xargs` to simulate a burst that exceeds our configured limit (5 requests per 10 seconds). + + 1. Open your terminal. + 2. Execute the following command, replacing `` with the API Key ID you saved earlier: + + ```bash + seq 10 | xargs -n1 -P10 -I {} bash -c 'curl -s -I -H "Authorization: " http://tyk-gateway.localhost:8080/request-throttling-test/ | head -n 1' + ``` + + 3. **Expected Observation:** You should see some requests succeed with `HTTP/1.1 200 OK`, and other requests failing with `HTTP/1.1 429 Too Many Requests` as the rate limit is immediately enforced. The order of `200s` vs `429s` might vary depending upon the processing time, but you will see immediate rejections once the limit is hit. + + **Sample Output (Illustrative):** + + ```bash + HTTP/1.1 429 Too Many Requests + HTTP/1.1 429 Too Many Requests + HTTP/1.1 429 Too Many Requests + HTTP/1.1 429 Too Many Requests + HTTP/1.1 429 Too Many Requests + HTTP/1.1 200 OK + HTTP/1.1 200 OK + HTTP/1.1 200 OK + HTTP/1.1 200 OK + HTTP/1.1 200 OK + ``` + +#### Configure Throttling + +Now that the policy enforces a basic rate limit, we will enable and configure Request Throttling. This adds the queue-and-retry behavior for requests that exceed the limit, preventing immediate rejection and helping to smooth out traffic spikes. + +5. **Configure Request Throttling by Updating the Security Policy** + + 1. Navigate to **API Security > Policies** in the Tyk Dashboard sidebar + 2. Click on the `Request Throttling Policy` + 3. Under the **1. Access Rights** tab: + * In the **Global Limits and Quota** section + * Set the following values for `Throttling` + * Uncheck the `Disable Throttling` checkbox + * Enter `3` into the **Throttle retries (or connection attempts)** field + * Enter `5` into the **Per (seconds):** field + 4. Click the **Update** button + 5. A pop-up window will appear to confirm the changes. Click **Update** to close the pop-up + +#### Testing + +6. **Test Request Throttling** + + 1. **Repeat the Test:** Open your terminal and execute the *exact same command* as in step 4: + + ```bash + seq 10 | xargs -n1 -P10 -I {} bash -c 'curl -s -I -H "Authorization: " http://tyk-gateway.localhost:8080/request-throttling-test/ | head -n 1' + ``` + + 2. **Expected Observation:** + * You will still see the first ~5 requests return `HTTP/1.1 200 OK` quickly + * Critically, the subsequent requests (6 through 10) will **not** immediately return `429`. Instead, you should observe a **delay** before their status lines appear + * After the delay (`throttle_interval`), Tyk will retry the queued requests. Some might now succeed (return `200 OK`) if the rate limit window allows + * If a request is retried `throttle_retry_limit` (3) times and still encounters the rate limit, *then* it will finally return `HTTP/1.1 429 Too Many Requests` + * Overall, you might see more `200 OK` responses compared to the previous test, and any `429` responses will appear significantly later + + **Sample Output (Illustrative - timing is key):** + + ```bash + HTTP/1.1 200 OK # Appears quickly + HTTP/1.1 200 OK # Appears quickly + HTTP/1.1 200 OK # Appears quickly + HTTP/1.1 200 OK # Appears quickly + HTTP/1.1 200 OK # Appears quickly + # --- Noticeable pause here --- + HTTP/1.1 200 OK + # --- Noticeable pause here --- + HTTP/1.1 200 OK + # --- Noticeable pause here --- + HTTP/1.1 200 OK + HTTP/1.1 200 OK + HTTP/1.1 200 OK + ``` + *(The exact mix of 200s and 429s on the delayed requests depends heavily on timing relative to the 10-second rate limit window reset and the retry attempts).* + +This comparison clearly shows how Request Throttling changes the behaviour from immediate rejection to queued retries, smoothing the traffic flow and potentially allowing more requests to succeed during bursts. + +--- +## Configuration Options + +Request Throttling is configured using [Policies](/api-management/policies) or directly on individual [Sessions](/api-management/access-control/sessions-and-keys/understanding-sessions). + +The configuration involves setting two specific fields: + +- `throttle_interval`: Defines the wait time (in seconds) between retry attempts for a queued request. (*Note*: Do not set it to `0`. If you do, no delay is applied, and the request is immediately retried. This will creates a “busy waiting” scenario that consumes more resources than a positive interval value) +- `throttle_retry_limit`: Sets the maximum number of retry attempts before the request is rejected. (*Note*: Do not set it to `0`. Setting it to `0` means that there will be no throttling on the request) + +To enable throttling, both fields must be set to a value greater than `0`. + +### Disable throttling + +The default value is `-1` and means it is disabled by default. +Setting `throttle_interval` and `throttle_retry_limit` values to any number smaller than `0`, to ensure the feature is diabled. + +You can configure these settings using either the Tyk Dashboard UI or the Tyk Dashboard API. + +### Configure via UI + +The Tyk Dashboard provides a straightforward interface to set throttling parameters on both Security Policies and Access Keys. + + + + + +The image below shows a policy with throttling. Any key using this policy will inherit the throttling settings and behaves as follows: wait 2 seconds between retries for queued requests, attempting up to 3 times before failing (so overall 6 seconds before getting another 429 error response). + +policy with throttling configured + +
+ +1. Navigate to **API Security > Policies** in the Tyk Dashboard sidebar +2. Click the **Add Policy** button +3. Under the **1. Access Rights** tab and in the **Add API Access Rule** section, select the required API +4. Scroll down to the **Global Limits and Quota** section (still under the **1. Access Rights** tab): + * To enable *Throttling*, we must configure *Rate Limiting* in the policy. + * Field **Requests (or connection attempts)** - Enter the number of requests you want to allow before rate limit is applied. + * Field **Per (seconds):** - Enter the time window in seconds during which the number of requests specified above is allowed. + * Now enable `Throttling` by setting the following values in the `Throttling` section: + * Uncheck the `Disable Throttling` checkbox + * Field **Throttle retries (or connection attempts)** - Enter the maximum number of times Tyk should attempt to retry a request after it has been queued due to exceeding a rate limit or quota. + * Field **Per (seconds):** - Enter the time interval in seconds Tyk should wait between each retry attempt for a queued request. +5. Select the **2. Configuration** tab +6. In the **Policy Name** field, enter a name +7. From the **Key expire after** dropdown, select an option +8. Click the **Create Policy** button + + +
+ + + +The image below shows an access key with throttling. This access key behaves as follows: wait 2 seconds between retries for queued requests, attempting up to 3 times before failing (so overall 6 seconds before getting another 429 error response). + +**Note:** Direct key configuration overrides policy settings only for that specific key. + +access key with throttling configured + +
+ +1. Navigate to **API Security > Keys** in the Tyk Dashboard sidebar +2. Click the **Create Key** button +3. Under the **1. Access Rights** tab: + * Select **Choose API** + * In the **Add API Access Rule** section, select the required API +4. Scroll down to the **Global Limits and Quota** section (still under the **1. Access Rights** tab): + * To enable *Throttling*, we must configure *Rate Limiting* in the Access Key. + * Field **Requests (or connection attempts)** - Enter the number of requests you want to allow before rate limit is applied. + * Field **Per (seconds):** - Enter the time window in seconds during which the number of requests specified above is allowed. + * Now enable `Throttling` by setting the following values in the `Throttling` section: + * Uncheck the `Disable Throttling` checkbox + * Field **Throttle retries (or connection attempts)** - Enter the maximum number of times Tyk should attempt to retry a request after it has been queued due to exceeding a rate limit or quota. + * Field **Per (seconds):** - Enter the time interval in seconds Tyk should wait between each retry attempt for a queued request. +5. Select the **2. Configuration** tab +6. In the **Alias** field, enter a name. This provides a human-readable identifier that makes tracking and managing this specific access key easier in your analytics and logs. +7. From the **Expires** dropdown, select an option +8. Click the **Create Key** button + + +
+ +
+ +### Configure via API + +These are the fields that you can set directly in the Policy object or the Access Key: + +```json +{ + // Partial policy/session object fields + "throttle_interval": 2, // Wait 2 second between retries + "throttle_retry_limit": 3, // Attempt a maximum of 3 retries + // ... more config follows +} +``` + + + + + +To update the policy, do the following: +1. Retrieve the policy object using `GET /api/portal/policies/{POLICY_ID}` +2. Add or modify the `throttle_interval` and `throttle_retry_limit` fields within the policy JSON object +3. Update the policy using `PUT /api/portal/policies/{POLICY_ID}` with the modified object, or create a new one using `POST /api/portal/policies/` + +**Explanation:** +The above adds throttling to a policy. Any key using this policy will inherit the throttling settings and behaves as follows: wait 1 second between retries for queued requests, attempting up to 5 times before failing (so overall 5 seconds before getting another 429 error response). + + + + + +Note: Direct key configuration overrides policy settings only for that specific key. + +To update the access key do the following: +1. Retrieve the key's session object using `GET /api/keys/{KEY_ID}` +2. Add or modify the `throttle_interval` and `throttle_retry_limit` fields within the session object JSON +3. Update the key using `PUT /api/keys/{KEY_ID}` with the modified session object + + +**Explanation:** +The above adds throttling to a key. Any request made by the key will behave as follows: wait 1 second between retries for queued requests, attempting up to 5 times before failing (so overall 5 seconds before getting another 429 error response). + + + + + +--- +## How It Works + +```mermaid +flowchart LR + A[Client Request] --> GW(Tyk Gateway); + + subgraph Rate Limits + GW --> RL{Rate Limit OK?}; + RL -- Yes --> Q{Quota OK?}; + RL -- No --> T{Throttle Enabled?}; + Q -- Yes --> Fwd[Forward Request]; + Q -- No --> Reject[Reject Request]; + end + + subgraph Throttling Logic + T -- No --> Reject; + T -- Yes --> Queue[Queue Request]; + Queue --> Wait[Wait ThrottleInterval]; + Wait --> RetryL{Retry Limit Reached?}; + RetryL -- Yes --> Reject; + RetryL -- No --> Recheck(Re-evaluate Rate Limit Only); + %% Loop back to rate limit check only %% + Recheck --> RL; + end + + Fwd --> Backend((Upstream Service)); + Backend --> Success((Success Response)); + Success --> Client; + Reject --> Failure((Failure Response)); + Failure --> Client; +``` + +Tyk's Request Throttling intercepts API requests *after* they have exceeded a configured [Rate Limit](/api-management/rate-limit). + +Instead of immediately rejecting these requests with a `429 Too Many Requests` error (which is the default rate-limiting behaviour), the Gateway temporarily holds them in a queue. After waiting for a specified duration (`throttle_interval`), Tyk attempts to process the request again, re-checking the rate limit status. + +This retry cycle repeats until either the request can be successfully processed (if capacity becomes available) or a configured maximum number of retries (`throttle_retry_limit`) is reached. Only after exhausting all retries does Tyk return the `429` error to the client. + +Think of it like trying to access a service with a restriction on how many people can enter per minute (Rate Limit). If you arrive when the per-minute limit is full, standard behaviour is to turn you awa +y immediately. With Throttling enabled, the service instead asks you to wait briefly (the interval) and tries your entry again shortly, checking if the rate limit has freed up capacity, repeating this a f +ew times (the retry limit) before finally turning you away if access is still restricted. + +--- +## FAQ + + + + +Request Throttling in Tyk is a mechanism that allows for graceful handling of rate limit violations. Instead of immediately rejecting requests that exceed rate limits, throttling gives clients a chance to retry after a specified delay. + + + +Rate Limiting is a mechanism to restrict the number of requests a client can make in a given time period (e.g., 100 requests per minute). Request Throttling is an extension of rate limiting that provides a retry mechanism when rate limits are exceeded. Instead of immediately failing with a 429 status code, throttling allows the gateway to wait and retry the request internally. + + + +No, Request Throttling in Tyk is exclusively linked to rate limits and does not work with request quotas. When a quota is exceeded, the request is immediately rejected without any throttling or retry attempts. Throttling is only applied when rate limits are exceeded. + + + +Refer to this [documentation](#configuration-options). + + + +Request Throttling can increase response times for requests that exceed rate limits, as the gateway will wait for the specified `ThrottleInterval` between retry attempts. The maximum additional latency would be `ThrottleInterval × ThrottleRetryLimit` seconds. This trade-off provides better success rates at the cost of potentially longer response times for some requests. + + + +Yes, Tyk tracks throttled requests in its health check metrics. You can monitor the `ThrottledRequestsPS` (throttled requests per second) metric to see how often requests are being throttled. Additionally, when a request is throttled, Tyk emits a `RateLimitExceeded` event that can be captured in your monitoring system. + + + +No, Request Throttling is not enabled by default. To enable throttling, you need to explicitly set `ThrottleRetryLimit` to a value greater than 0 and configure an appropriate `ThrottleInterval`. These settings can be applied through policies or directly in access keys. + + \ No newline at end of file diff --git a/api-management/response-caching.mdx b/api-management/response-caching.mdx new file mode 100644 index 0000000000..2d813d130a --- /dev/null +++ b/api-management/response-caching.mdx @@ -0,0 +1,754 @@ +--- +title: "Caching Responses" +description: "Learn how to configure response caching in Tyk Gateway to reduce upstream load and improve API response times" +keywords: "Caching, Request Optimization, Optimization, Endpoint Caching, Configuration, Cache" +sidebarTitle: "Response Caching" +--- + +## Overview + +The Tyk Gateway can cache responses from your upstream services. + +API Clients which make subsequent requests to a cached endpoint will receive the cached response directly from the Gateway, which: + - reduces load on the upstream service + - provides a quicker response to the API Client (reduces latency) + - reduces concurrent load on the API Gateway + +Caching is best used on endpoints where responses infrequently change and are computationally expensive for the upstream service to generate. + +### Caching with Tyk + +Tyk uses Redis to store the cached responses and, as you'd expect from Tyk, there is lots of flexibility in how you configure caching so that you can optimize the performance of your system. + +There are two approaches to configure caching for an API deployed with Tyk: + + - [Basic](/api-management/response-caching#basic-caching) or [Safe Request](/api-management/response-caching#global-cache-safe-requests) caching is applied at the API level for all requests for which it is safe to do so. + - [Advanced](/api-management/response-caching#endpoint-caching) caching options can be applied at the endpoint level. + +Tyk's advanced caching options allow you to selectively cache the responses to all requests, only those from specific paths or only responses with specific status codes returned by the API. You can even cache dynamically based upon instruction from the upstream service received within the response. + +Caching is enabled by default at the Gateway level, but no caching will happen until the API Definition is configured to do so. + +### Cache Terminology and Features + +#### Cache Key +Cache keys are used to differentiate cached responses, such that slight variations in the request can generate different cache keys. This enables you to configure the cache so that different API Clients receive different cached responses when accessing the same API endpoint. + +This makes for a very granular cache, which may result in duplication of cached responses. This is preferable to the cache not being granular enough and therefore rendering it unsuitable for use, such that two API Clients receive the same cached response when this is not desired. + +The cache key is calculated using many factors: + - request HTTP method + - request URL (API path/endpoint) + - keys and values of any headers specified by `cache_by_headers` property + - hash of the request body + - API Id of the requested API + - value of the authorization header, if present, or if not, the API Client IP address + +#### Cache Value +The value stored in the cache is a base64 encoded string of the response body. When a subsequent request matches the cache key (a **cache hit**), Tyk decodes the cache value and returns this to the API Client that made the request. + +#### Indicating a Cached Response +When a request causes a cache hit, the Gateway will add a special header to indicate that the response being received is from a cache: + - `X-Tyk-Cached-Response` is added to the response header with the value `1` + +The API Client can use this to identify cached responses from non-cached responses. + +#### Global Cache (Safe Requests) +We define a safe request as any category of API request that is considered cacheable without causing any undesired side effects or security concerns. These are requests made using the HTTP methods `GET`, `HEAD` or `OPTIONS` that do not modify data and can be safely cached for performance gains (i.e. they should be idempotent and so are good candidates for caching). If these methods are not idempotent for your API, then you should not use safe request caching. + +Safe request caching at the API level is enabled by setting the `cache_all_safe_requests` option to `true`, or by checking the equivalent checkbox in the Dashboard UI. This will enable safe request caching on all endpoints for an API. + +This mode of operation is referred to as Global Caching because it is applied globally within the scope of a single API. Picking this approach will override any per-endpoint (per-path) caching configuration, so it’s not suitable if granular control is required. + +Tyk does support safe request caching at the more granular, per-endpoint level, as described [here](/api-management/response-caching#request-selective-cache-control) - but `cache_all_safe_requests` must be set to `false` in that scenario. + +#### Cache Timeout +The cache timeout (Time-To-Live or TTL) value can be configured per API and is the maximum age for which Tyk will consider a cache entry to be valid. You should use this to optimize the tradeoff between reducing calls to your upstream service and potential for changes to the upstream data. + +If the timeout has been exceeded when a request is made to a cached API, that request will be passed to the upstream and the response will (if appropriate) be used to refresh the cache. + +The timeout is configured in seconds. + +#### Cache Response Codes +You can configure Tyk to cache only responses with certain HTTP status codes (e.g. 200 OK), for example to save caching error responses. You can configure multiple status codes that will be cached for an API, but note that this applies only to APIs that return with an HTTP status code in the response. + +#### Dynamic Caching +By default Tyk maintains its response cache with a separate entry for each combination of API key (if authorization is enabled), request method and request path. Dynamic caching is a more flexible method of caching API responses based on header or body content rather than just the request method and path. This allows for more granular caching control and maintainance of separate caches for different users or request properties. + +#### Upstream Cache Control +Upstream cache control refers to caching API responses based on instructions provided by the upstream service within the response headers. This allows the upstream service to have more control over which responses are cached and for how long. + +## Basic Caching + +_On this page we describe the use of Tyk's API response cache at the API level (Global); for details on the more advanced Endpoint level cache you should refer to [this](/api-management/response-caching#endpoint-caching) page._ + +Caching is configured separately for each API according to values you set within the API definition. Subsequently, the caching scope is restricted to an API definition, rather than being applied across the portfolio of APIs deployed in the Gateway. + +If you are using the Tyk Dashboard you can set these options from the Dashboard UI, otherwise you will need to edit the raw API definition. + +### Configuring Tyk's API-level cache +Within the API Definition, the cache controls are grouped within the `cache_options` section. + +The main configuration options are: + - `enable_cache`: Set to `true` to enable caching for the API + - `cache_timeout`: Number of seconds to cache a response for, after which the next new response will be cached + - `cache_response_codes`: The HTTP status codes a response must have in order to be cached + - `cache_all_safe_requests`: Set to `true` to apply the caching rules to all requests using `GET`, `HEAD` and `OPTIONS` HTTP methods + +For more advanced use of the API-level cache we also have: + - `cache_by_headers`: used to create multiple cache entries based on the value of a [header value](#selective-caching-by-header-value) of your choice + - `enable_upstream_cache`: used to allow your [upstream service](/api-management/response-caching#upstream-cache-control-1) to identify the responses to be cached + - `cache_control_ttl_headers`: used with `enable_upstream_cache` + +#### An example of basic caching +To enable global caching for all safe requests to an API, only storing HTTP 200 responses, with a 10 second time-to-live (TTL), you would set: +``` +"cache_options": { + "enable_cache": true, + "cache_timeout": 10, + "cache_all_safe_requests": true, + "cache_response_codes": [200] +} +``` + + + +If you set `cache_all_safe_requests` to true, then the cache will be global and *all* inbound requests made to the API will be evaluated by the caching middleware. This is great for simple APIs, but for most, a finer-grained control is required. This control will over-ride any per-endpoint cache configuration. + + + +#### Selective caching by header value +To create a separate cache entry for each response that has a different value in a specific HTTP header you would configure the `cache_option.cache_by_headers` option with a list of the headers to be cached. + +For example, to cache each value in the custom `Unique-User-Id` header of your API response separately you would set: +``` + "cache_options": { + "cache_by_headers": ["Unique-User-Id"] +} +``` + + + +The `cache_by_headers` configuration is not currently exposed in the Dashboard UI, so it must be enabled though either the raw API editor or the Dashboard API. + + + +### Configuring the Cache via the Dashboard +Follow these simple steps to enable and configure basic API caching via the Dashboard. + +**Steps for Configuration:** + +1. **Go to the Advanced Options** + + From the API Designer, select the **Advanced Options** tab: + + Advanced options tab location + +2. **Set the Cache Options for the Global Cache** + + Cache settings + + Here you must set: + + 1. **Enable caching** to enable the cache middleware + 2. **Cache timeout** to set the [TTL](/api-management/response-caching#cache-timeout) (in seconds) for cached requests + 3. **Cache only these status codes** to set which [response codes](/api-management/response-caching#cache-response-codes) to cache (ensure that you click **ADD** after entering each response code so that it is added to the list) + 4. **Cache all safe requests** to enable the [global cache](/api-management/response-caching#global-cache-safe-requests) + +## Endpoint Caching + +### Overview + +On this page we describe how to configure Tyk's API response cache per endpoint within an API. This gives granular control over which paths are cached and allows you to vary cache configuration across API versions. For details on the API level (Global) cache you should refer to the [global-cache](/api-management/response-caching#basic-caching) configuration page. + +When you use the API-level cache, Tyk will maintain a cache entry for each combination of request method, request path (endpoint) and API key (if authentication is enabled) for an API. The Endpoint Caching middleware gives you granular control over which paths are cached and allows you to vary cache configuration across API versions. + +For details on the API-level cache you should refer to the [API-level cache](/api-management/response-caching#basic-caching) configuration page. + +#### When to use the Endpoint Caching middleware + +##### API with multiple endpoints +When your API has more than one endpoint the upstream data could have different degrees of freshness, for example the data returned by one endpoint might refresh only once every five minutes (and so should be suitably cached) whilst another might give real-time data and so should not be cached. The endpoint cache allows you to optimize the caching of each endpoint to meet your requirements. + +##### Request based caching +If you have an API that's providing search capability (for example into a catalog of products) and want to optimize the performance for the most frequently requested search terms, you could use the endpoint cache's [request-selective](#request-selective-cache-control) capability to cache only a subset of all requests to an endpoint. + +#### How the endpoint cache works +If caching is enabled then, by default, Tyk will create separate cache entries for every endpoint (path) of your API. This may be unnecessary for your particular API, so Tyk provides a facility to cache only specific endpoint(s). + +The endpoint-level cache relies upon the API-level cache being enabled but then allows you to enable the middleware for the specific endpoints that you wish to cache. No other endpoint requests will be cached. + +For each endpoint in your API with endpoint caching middleware enabled, you can configure which response codes should be cached (for example, you might not want to cache error responses) and also the refresh interval - or timeout - for the cache entries. + + + +It's important to note that the [cache all safe requests](/api-management/response-caching#global-cache-safe-requests) feature of the API-level cache will overrule the per-endpoint configuration so you must ensure that both are not enabled for the same API. + + + +##### Request-selective cache control +For ultimate control over what Tyk caches, you can optionally configure the endpoint cache middleware to look for specific content in the request body. Tyk will then create a separate cache entry for each response where the request matches the specific combination of method, path and body content. + +You define a regex pattern and, if Tyk finds a match for this anywhere in the request body, the response will be cached. + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + ## Internal Endpoint middleware summary + - The Endpoint Cache middleware is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Endpoint Cache middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +### Using Tyk OAS API + +The [Endpoint Caching](/api-management/response-caching#endpoint-caching) middleware allows you to perform selective caching for specific endpoints rather than for the entire API, giving you granular control over which paths are cached. + +When working with Tyk OAS APIs the middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/response-caching#using-classic-api) page. + +#### Configuring the middleware in the Tyk OAS API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. The `path` can contain wildcards in the form of any string bracketed by curly braces, for example `{user_id}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +**Configuring the endpoint cache is performed in two parts:** + +1. **Enable Tyk's caching function** + + The caching function is enabled by adding the `cache` object to the `global` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API. + + This object has the following configuration: + - `enabled`: enable the cache for the API + - `timeout`: set as the default cache refresh period for any endpoints for which you don't want to configure individual timeouts (in seconds) + +2. **Enable and configure the middleware for the specific endpoint** + + The endpoint caching middleware (`cache`) should then be added to the `operations` section of `x-tyk-api-gateway` for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + + The `cache` object has the following configuration: + - `enabled`: enable the middleware for the endpoint + - `timeout`: set to the refresh period for the cache (in seconds) + - `cacheResponseCodes`: HTTP responses codes to be cached (for example `200`) + - `cacheByRegex`: Pattern match for [selective caching by body value](/api-management/response-caching#request-selective-cache-control) + + For example: + ```json {hl_lines=["37-40", "45-51"],linenos=true, linenostart=1} + { + "components": {}, + "info": { + "title": "example-endpoint-cache", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/delay/5": { + "post": { + "operationId": "delay/5post", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-endpoint-cache", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-endpoint-cache/", + "strip": true + } + }, + "global": { + "cache": { + "enabled": true, + "timeout": 60 + } + }, + "middleware": { + "operations": { + "delay/5post": { + "cache": { + "enabled": true, + "cacheResponseCodes": [ + 200 + ], + "timeout": 5 + } + } + } + } + } + } + ``` + + In this example the endpoint cache middleware has been configured to cache `HTTP 200` responses to requests to the `POST /delay/5` endpoint. The cache will refresh after 5 seconds. Note that requests to other endpoints will also be cached, with a default cache timeout of 60 seconds according to the configuration in lines 37-40. + + The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the endpoint caching. + +#### Configuring the middleware in the API Designer + +Adding endpoint caching to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Endpoint Cache middleware** + + Select **ADD MIDDLEWARE** and choose the **Cache** middleware from the *Add Middleware* screen. + + Adding the Endpoint Cache middleware + +3. **Configure the middleware** + + Set the timeout and HTTP response codes for the endpoint. You can remove a response code from the list by clicking on the `x` next to it. + + Configuring the endpoint cache middleware for a Tyk OAS API + + + + + Body value match or [request selective](/api-management/response-caching#request-selective-cache-control) caching is not currently exposed in the Dashboard UI, so it must be enabled though either the raw API editor or the Dashboard API. + + + + Select **UPDATE MIDDLEWARE** to apply the change to the middleware configuration. + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +### Using Classic API + +The [Endpoint Caching](/api-management/response-caching#endpoint-caching) middleware allows you to perform selective caching for specific endpoints rather than for the entire API, giving you granular control over which paths are cached. + +When working with Tyk Classic APIs the middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](/api-management/response-caching#using-tyk-oas-api) page. + +If using Tyk Operator please refer to section [configuring the middleware in the Tyk Operator](#tyk-operator). + +#### Configuring the middleware in the Tyk Classic API Definition + +When using the Tyk Classic API Definition, there are two options for endpoint caching - simple and advanced. + +The [simple](#simple-endpoint-cache) option works with the API-level cache and allows you to select which endpoints are cached, but relies upon the cache timeout (refresh) configured at the API-level. It will cache all responses received from the endpoint regardless of the HTTP response code for all [safe requests](/api-management/response-caching#global-cache-safe-requests). + +The [advanced](#advanced-endpoint-cache) option allows you to cache more selectively, giving control over the HTTP response codes to be cached, a per-endpoint cache timeout and also the possibility of caching responses only to requests containing specific data in the request body. + +##### Simple endpoint cache + +To enable the simple middleware you must add a new `cache` object to the `extended_paths` section of your API definition. The `cache` object is a list of endpoints for which you wish to cache all safe requests. + +In the API-level `cache_options` you must enable caching and configure the timeout whilst ensuring that the option to cache all safe requests is disabled. + +The `cache_options` object has the following configuration: +- `enable_cache`: set to `true` to enable caching for this API +- `cache_all_safe_requests`: set to `false` to allow selective caching per-endpoint +- `cache_timeout`: set to the refresh period for the cache (in seconds) + +For example: +```json {linenos=true, linenostart=1} +{ + "cache_options": { + "enable_cache": true, + "cache_timeout": 60, + "cache_all_safe_requests": false + }, + + "extended_paths": { + "cache": [ + { + "/widget", + "/fish" + } + ] + } +} +``` + +In this example, the endpoint caching middleware has been configured to cache all safe requests to two endpoints (`/widget` and `/fish`) with a cache refresh period of 60 seconds. + +##### Advanced endpoint cache + + +For ultimate control over what Tyk caches, you should use the advanced configuration options for the per-endpoint cache. You can separately configure, for each HTTP method for an endpoint: +- an individual cache refresh (timeout) +- a list of HTTP response codes that should be cached +- a pattern match to cache only requests containing specific data in the [request body](/api-management/response-caching#request-selective-cache-control) + +To enable the advanced middleware you must add a new `advance_cache_config` object to the `extended_paths` section of your API definition. + +In the API-level `cache_options` you must enable caching and ensure that the option to cache all safe requests is disabled. The timeout that you set here will be used as a default for any endpoints for which you don't want to configure individual timeouts. + +The `advance_cache_config` object has the following configuration: +- `path`: the endpoint path +- `method`: the endpoint method +- `timeout`: set to the refresh period for the cache (in seconds) +- `cache_response_codes`: HTTP response codes to be cached (for example `200`) +- `cache_key_regex`: pattern match for selective caching by body value + +For example: +```json {linenos=true, linenostart=1} +{ + "cache_options": { + "enable_cache": true, + "cache_timeout": 60, + "cache_all_safe_requests": false + }, + + "extended_paths": { + "advance_cache_config": [ + { + "disabled": false, + "method": "POST", + "path": "/widget", + "cache_key_regex": "", + "cache_response_codes": [ + 200 + ], + "timeout": 10 + }, + { + "disabled": false, + "method": "GET", + "path": "/fish", + "cache_key_regex": "^shark$", + "cache_response_codes": [ + 200, 300 + ], + "timeout": 0 + } + ] + } +} +``` + +In this example the endpoint caching middleware has been configured to cache requests to two endpoints (`/widget` and `/fish`) as follows: + +| endpoint | HTTP response codes to cache | cache refresh timeout | body value regex | +| :---------- | :------------------------------ | :----------------------- | :------------------ | +| `POST /widget` | 200 | 10 seconds | none | +| `GET /fish` | 200, 300 | 60 seconds (taken from `cache_options`) | `shark` | + +#### Configuring the middleware in the API Designer + +You can use the API Designer in the Tyk Dashboard to configure the endpoint caching middleware for your Tyk Classic API by following these steps. + +##### Simple endpoint cache + +To enable and configure the simple endpoint cache, follow these instructions: + +1. **Configure the API level caching options** + + From the **Advanced Options** tab configure the cache as follows: + - **Enable caching** to enable the cache middleware + - **Cache timeout** to configure the timeout (in seconds) for cached requests + - **Cache only these status codes** is a list of HTTP status codes that should be cached, remember to click **Add** after entering each code to add it to the list + - **Cache all safe requests** ensure that this is **not** selected, otherwise the responses from all endpoints for the API will be cached + + Cache Options + +2. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to cache responses. Select the **Cache** plugin. + + Dropdown list showing Cache plugin + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +##### Advanced endpoint cache + +To enable and configure the advanced endpoint cache, follow these instructions: + +1. **Configure the API level caching options** + + From the **Advanced Options** tab configure the cache as follows: + - **Enable caching** to enable the cache middleware + - **Cache timeout** to configure the default timeout (in seconds) for any endpoints for which you don't want to configure individual timeouts + - **Cache only these status codes** leave this blank + - **Cache all safe requests** ensure that this is **not** selected, otherwise the responses from all endpoints for the API will be cached + + Cache Options + +2. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to cache responses. Select the **Advanced Cache** plugin. + + Selecting the Advanced Cache plugin for a Tyk Classic API + +3. **Configure the Advanced Cache plugin** + + Set the timeout and HTTP response codes for the endpoint. If you don't need to set a specific timeout for an endpoint you can leave this blank and Tyk will use the cache timeout configured at the API level. + + Endpoint cache configuration for Tyk Classic API + + + + + Body value match or [request selective](/api-management/response-caching#request-selective-cache-control) caching is not currently exposed in the Dashboard UI, so it must be configured through either the raw API editor or the Dashboard API. + + + +4. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +#### Configuring the middleware in the Tyk Operator + + +You can use Tyk Operator to configure the endpoint caching middleware for your Tyk Classic API by following these steps. + +##### Simple endpoint cache + +Configuring simple endpoint caching in Tyk Operator is similar to the process for a Tyk Classic API Definition. A list of endpoints for which you wish to cache safe requests should be configured within the `cache` list in the `extended_paths` section. + +In the API-level `cache_options` object, you must enable caching by setting `enable_cache` to true and configure the cache refresh period by setting a value for the `cache_timeout` in seconds. To allow selective caching per endpoint you should also set `cache_all_safe_requests`to `false`. + +```yaml {linenos=true, linenostart=1, hl_lines=["26-35"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-cache +spec: + name: httpbin-cache + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-cache + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + cache: + - /get + - /anything + cache_options: + cache_all_safe_requests: false +# cache_by_headers: [] + cache_timeout: 10 + cache_response_codes: + - 400 + enable_cache: true +``` + +##### Advanced endpoint cache + +Advanced caching with Tyk Operator is a similar process to that for configuring the [advanced caching middleware in the Tyk Classic API Definition](#tyk-classic-advanced-caching). + +To enable the advanced middleware you must add a new `advance_cache_config` object to the `extended_paths` section of your API definition. + +This allows you to configure caching per endpoint. For each endpoint, it is possible to specify the endpoint path, method, list of response codes to cache, cache timeout and a cache key regular expression. The cache key regular expression represents a pattern match to cache only requests containing specific data in the [request body](/api-management/response-caching#request-selective-cache-control) + +For example: + +```yaml {linenos=true, linenostart=1, hl_lines=["26-35"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-advance-cache +spec: + name: httpbin-advance-cache + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-advance-cache + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + advance_cache_config: + - path: /anything + method: GET + cache_key_regex: "" + cache_response_codes: [200] + cache_options: + cache_timeout: 30 + enable_cache: true +``` + +In this example the endpoint caching middleware has been configured to cache requests for the `/anything` endpoint as follows: + +| endpoint | HTTP response codes to cache | cache refresh timeout | body value regex | +| :---------- | :------------------------------ | :----------------------- | :------------------ | +| `GET /anything` | 200 | 30 seconds (taken from `cache_options`) | none | + +## Upstream Cache Control + +Upstream cache control refers to the caching of API responses based on instructions provided by the upstream service. This allows the upstream service to have control over which responses are cached and for how long and can be used to perform caching of traditionally "non-safe" requests. The upstream service controls the cache using parameters in the response header. + +This approach gives the most granular control as it will also only cache responses based on the request method. + +For example, if you only want to cache requests made with the `OPTIONS` method, you can configure the upstream cache control accordingly and return cache control headers only in those responses. With this configuration, Tyk will cache only those responses, not those for other methods for the same path. + +Upstream cache control is configured on a per-API and per-endpoint basis, giving maximum flexibility. All configuration is performed within the API definition. + +### Enabling upstream cache control for an API + +To set up upstream cache control, you must configure `cache_options` in the API definition as follows: + - first enable the Tyk cache (using `enable_cache`) + - ensure that global/safe request caching is disabled (`cache_all_safe_requests` is set to `false`) + - set `enable_upstream_cache_control` to `true` + - add the endpoints to be cached to the list in `extended_paths.cache` + +For example, to enable upstream cache control for the `/ip` endpoint (path) of your API you would add the following to the API definition: + +``` +"cache_options": { + "enable_cache": true, + "cache_all_safe_requests": false, + "enable_upstream_cache_control": true, + "extended_paths": { + "cache": [ + "ip" + ] + } +} +``` + +If you are using Tyk Dashboard, you can configure these settings within the Advanced Settings section of the API Designer. You should select **Enable upstream cache control** and deselect **Global cache**, then follow the steps for per-path caching. + +### Operating cache control from the upstream server + +When upstream cache control is configured, the Gateway will check the response from the upstream server for the header `x-tyk-cache-action-set`: + - if this is provided in the response header and is set to `1` or `true` then the response will be stored in the cache + - if the header is empty or absent, Tyk follows its default behavior, which typically involves not caching the request, or caching only valid response codes (`cache_response_codes`) + +The upstream server also controls the length of time that Tyk should cache the response (Time-To-Live or TTL). + +Tyk looks for the header `x-tyk-cache-action-set-ttl` in the response: + - if this is found and has a positive integer value, the Gateway will cache the response for that many seconds + - if the header is not present, Tyk falls back to the value specified in `cache_options.cache_timeout` + +By configuring these headers in the responses from your services, you can have precise control over caching behavior. + +#### Using a custom TTL header key +If you wish to use a different header value to indicate the TTL you can do so by adding the `cache_control_ttl_header` option to the API definition. + +For example, if you configure: + ``` + "cache_options": { + "cache_control_ttl_header": "x-expire" + } + ``` + +and also send `x-expire: 30` in the response header, Tyk will cache that specific response for 30 seconds. + + + +## Invalidating the Cache + +The cache for an API can be invalidated (or flushed) to force the creation of a new cache entry before the cache’s normal expiry. + +This is achieved by calling one of the dedicated cache invalidation API endpoints. There is a cache invalidation endpoint in both the Tyk Dashboard API and Tyk Gateway API; the URLs differ slightly, but they have the same effect. + +For Dashboard-managed deployments, it’s recommended to call the Dashboard API version, as this will handle the delivery of the message to all Gateways in the cluster. + +Caches are cleared on per-API basis, so the request to the invalidation endpoint must include the ID of the API in the path. + +For example, with the Tyk Gateway API: + +``` +DELETE /tyk/cache/{api-id} +``` + +and with the Tyk Dashboard API: + +``` +DELETE /api/cache/{api-id} +``` + +Note that prior to Tyk version 3.0.9 and 4.0, this was not supported on MDCB Data Plane gateways. + + + +Cache invalidation is performed at the API level, so all cache entries for the API will be flushed. + + + +## Optimizing the Cache Storage + +Tyk creates the API cache in Redis, as it gives high performance and low latency. By default, the cache will use the same database that is used to store the API keys, minimizing the deployment footprint. + +For [multi-data center](/api-management/mdcb#redis) deployments, the Data Planes have a locally deployed Redis. This enables them to have a localised cache close to the traffic-serving Gateways. + +The [cache key](/api-management/response-caching#cache-key) is used as the Redis key, for quick lookups. + +For high-traffic systems that make heavy use of caching, it can make sense to use separate Redis databases for cache storage and for API keys, at the expense of increased deployment footprint. + +### Configuring a separate cache +To enable a separate cache server, you must deploy additional Redis instance(s) and apply additional configuration within your Tyk Gateway's `tyk.conf` configuration file. + +You must + - set `enable_separate_cache_store` to `true` + - provide additional Redis connection information in the `cache_storage` section + +For example: +```json +{ +"enable_separate_cache_store": true, +"cache_storage": { + "type": "redis", + "host": "", + "port": 0, + "addrs": [ + "localhost:6379" + ], + "username": "", + "password": "", + "database": 0, + "optimisation_max_idle": 3000, + "optimisation_max_active": 5000, + "enable_cluster": false + } +} +``` + +The configuration of the separate Redis Cache is the same (and uses the same underlying driver) as the regular configuration, so [Redis Cluster](/tyk-configuration-reference/redis-cluster-sentinel#configure-redis-cluster) is fully supported. If you set `enable_cluster` to `false`, you only need to set one entry in `addrs`. + + + +Prior to Tyk Gateway v2.9.3, `hosts` was used instead of `addrs`; since v2.9.3 `hosts` has been deprecated. + + + diff --git a/api-management/routing-traffic/overview.mdx b/api-management/routing-traffic/overview.mdx new file mode 100644 index 0000000000..5820ef0c2f --- /dev/null +++ b/api-management/routing-traffic/overview.mdx @@ -0,0 +1,81 @@ +--- +title: "Routing Traffic" +description: "Learn how Tyk Gateway routes incoming requests to their destinations" +keywords: "routing, traffic routing, request matching, request forwarding, internal routing" +sidebarTitle: "Overview" +--- + +Tyk Gateway sits between clients and upstream services, receiving every API request and deciding where it goes. This section covers the mechanisms that control those decisions: how Tyk identifies which API and endpoint owns a request, where it sends the matched request, and what happens when the standard request-response flow needs to be modified or bypassed. + +## The Request Journey + +{/* + **DIAGRAM REQUEST: "The Tyk Request Journey"** + + Purpose: Show how a request moves through Tyk's routing pipeline, + including the three possible exit paths and the internal routing loop. + + Structure (top to bottom, with one loop): + + Client + ↓ + Matching + (listen path → API, URL pattern → endpoint) + ↓ + Path Modification? ──── No ────┐ + ↓ Yes │ + URL Rewrite │ + └─────────────────────────────┘ + ↓ + Route to... (three branches) + ├── External Upstream + │ ↓ + │ Load Balancing / Service Discovery + │ ↓ + │ Upstream Service → Client + │ + ├── Internal Target (tyk://) + │ └── Arrow curves back up to "Matching" + │ Label this arc: "Re-enters pipeline on target API" + │ + └── Gateway Response + ↓ + Client (no upstream reached) + + Design notes: + - Use three visually distinct colours for the three exit branches + (External Upstream, Internal Target, Gateway Response) + - The Internal Target arc looping back to Matching is the most + important visual element — it should be prominent and clearly labelled + to show the request re-enters the full pipeline, not just jumps to an endpoint + - Path Modification is a bypass, not a required stage — show it as + an optional detour rather than a mandatory box + - "Client" appears at both the top (origin) and bottom (response receipt) + to bookend the journey + - Keep labels concise — the bold stage names in the text below + correspond directly to the boxes in this diagram +*/} + +A request arriving at Tyk passes through a sequence of routing stages. + +**[Matching](/getting-started/key-concepts/url-matching):** Tyk compares the incoming request against all configured APIs to find the one whose listen path matches the +request URL. Within that API, it identifies the endpoint whose path pattern matches. No further processing occurs until a +match is found. + +**[Forwarding](/planning-for-production/ensure-high-availability/load-balancing):** Tyk forwards the matched request to the configured upstream target. Where multiple targets are configured, +load balancing distributes traffic across them. Where upstream addresses are dynamic, [service discovery](/planning-for-production/ensure-high-availability/service-discovery) resolves the current +target at request time. + +**[Path Modification](/transform-traffic/url-rewriting):** Before forwarding, the request path can be rewritten. This handles cases where the path a client +sends differs from the path the upstream expects, or where request content should determine the destination. + +**[Internal Routing](/advanced-configuration/transform-traffic/looping):** Rather than forwarding to an external upstream, a request can be routed to another API or endpoint +within the same Tyk instance using the `tyk://` addressing scheme. This enables multi-step processing pipelines that stay +entirely inside the gateway. + +**[Request Termination](/api-management/traffic-transformation/mock-response):** Some requests are handled entirely by the gateway and never reach an upstream. A mock response, for +example, returns a pre-configured static payload directly to the client. + +## Protocol Support + +The stages above apply to all traffic Tyk handles. Where behavior differs for [gRPC](/key-concepts/grpc-proxy), [WebSocket](/advanced-configuration/websockets), [Server-Sent Events](/advanced-configuration/sse-proxy), or [TCP](/key-concepts/tcp-proxy) traffic, the relevant page notes the difference. diff --git a/api-management/security-best-practices.mdx b/api-management/security-best-practices.mdx new file mode 100644 index 0000000000..931c3ca0e0 --- /dev/null +++ b/api-management/security-best-practices.mdx @@ -0,0 +1,418 @@ +--- +title: "Security Best Practices" +description: "Guide on API management and security best practices, including authentication, authorization, resource protection, governance, and OWASP threat mitigation with Tyk." +keywords: "OWASP, Security, Top Ten, API Management best practice, API Security, Authentication, Security, Configuration, SSL, Certificates, Authentication, Authorization, API security, API Gateway Security" +sidebarTitle: "Security Best Practices" +--- + +## Overview + +This section serves as a detailed resource for understanding key concepts and tools related to API security. It provides explanations of critical practices such as authentication, authorization, and governance, offering insights into how these concepts work and why they matter. Whether you're looking to mitigate threats identified by the [OWASP API Security Top 10](https://owasp.org/API-Security/editions/2023/en/0x00-header/) or to configure your APIs for better resilience, this page breaks down the essentials. + +Two of the most prevalent topics are [authentication](#authentication) and [authorization](#authorization), which occupy four of the top five positions. These are critical elements of API security, which verify the identity of API clients and control what they’re able to do. Alongside these are a number of other beneficial topics that are also within the remit of API management, all of which will be covered in this section. These include: + +- [Governance](#governing-apis-effectively) +- [Configuration](#configuration-best-practices) +- [Resource Consumption](#managing-api-resources) + +## Mitigating The Top 10 OWASP Threats + +The Open Web Application Security Project (OWASP) provides a top ten threat awareness document compiled by security experts. For more details on the OWASP project visit [https://www.owasp.org](https://www.owasp.org). Below are the top ten threats and how Tyk guards against them. For further details please visit our [blog](https://tyk.io/blog/res-owasp-api-security-intro/) + +##### 1 - Broken Object Level Authorization (BOLA) + +Broken Object Level Authorization (BOLA) can occur due to a lack of access control to API resources. This vulnerability allows attackers to manipulate or bypass authorization mechanisms, typically by tampering with resource identifiers to gain unauthorized access to specific resources or data. BOLA is a critical security concern as it can lead to data breaches and unauthorized actions within a system. + +It is the responsibility of the API to handle this form of attack since it can access and understand the data needed to make authorization decisions on individual objects within the application database. + +##### 2 - Broken Authentication + +Authentication is a vital aspect of API security. Failure to do so, as noted by OWASP, leads to *Broken Authentication* posing a significant risk to both API providers and data. + +Tyk provides the following features and authentication mechanisms: +- Prioritize secure methods, like [mutual TLS](/api-management/implement-tls#secure-hosted-apis-with-mtls), over [basic authentication](/api-management/authentication/basic-authentication) wherever feasible. +- API owners can integrate external Identity Providers (IdPs) supporting methods like [OpenID Connect](/api-management/client-authentication#integrate-with-openid-connect-deprecated), [OAuth 2.0](/api-management/authentication/oauth-2#using-the-authorization-code-grant) or [JSON Web Tokens](/basic-config-and-security/security/authentication-authorization/json-web-tokens). +- [Single Sign-On](/tyk-identity-broker/dashboard-sso) can be used for a centralized and trusted authentication source. API operators can choose from common authentication methods such as OAuth 2.0, LDAP, and SAML. +- [Dynamic Client Registration](/tyk-developer-portal/tyk-portal-classic/dynamic-client-registration#oauth-2-0-dynamic-client-registration-protocol-dcr), enables third-party authorization servers to issue client credentials via the Tyk Developer Portal. This streamlines Identity Management, eliminating the need to manage credentials across multiple systems. +- Tyk's default authentication setup disallows credentials in URLs, reducing the risk of inadvertent exposure through backend logs. +- Tyk Gateway can be configured to enforce a [minimum TLS version](/api-management/certificates#tls-or-ssl), enhancing security by blocking outdated and insecure TLS versions. + +##### 3 - Broken Object Property Level Authorization (BOPLA) + +REST APIs provide endpoints that return all properties of an object in the reponse, some of which could contain sensitive data. Conversely, GraphQL API requests allow the clients to specify which properties of an object should be retrieved. + +From a REST API perspespective, it is the responsibility of the API to ensure that the correct data is retrieved. The Gateway can provide additional security measures as follows: +- [Body transformation plugins](/api-management/traffic-transformation/request-method) can be used to remove sensitive data from the response if the API is unable to do so itself. +- [JSON Schema validation](/api-management/traffic-transformation/request-validation#request-validation-using-classic) to validate that an incoming data payload meets a defined schema. Payloads that do not adhere to the schema are rejected. + +For GraphQL APIs, the gateway can be used to define the GraphQL schemas, limiting which properties of an object are queryable. Furthermore, access can be controlled to specific properties by configuring [field-based permissions](/api-management/graphql#field-based-permissions). Subsequently, the visiblity of a schema's properties can be controlled for different consumers of the GraphQL API. + + +##### 4 - Unrestricted Resource Consumption + +APIs can become overwhelmed if the resources upon which they rely are fully consumed. In such situations, an API can no longer operate, and will no longer be able to service requests, or potentially even be unable to complete those currently in progress. + +As an APIM product, Tyk Gateway can be configured to use the following out-of-the-box functionality when handling API traffic for legitimate users: + +- [Circuit breaker](/planning-for-production/ensure-high-availability/circuit-breakers) +- [Payload size limiter](/api-management/traffic-transformation/request-size-limits) +- [Rate limiter / throttling](/api-management/rate-limit#introduction) +- [Caching](/api-management/response-caching) +- [Enforced timeout](/planning-for-production/ensure-high-availability/enforced-timeouts) +- [IP restriction](/api-management/gateway-config-tyk-classic#ip-access-control) +- [GraphQL query complexity limiting](/api-management/graphql#complexity-limiting-1) + +For Denial of Service (DoS) attacks it is recommended to use specialist 3rd party services to prevent DoS attacks from reaching your infrastructure. + +##### 5 - Broken Function Level Authorization (BFLA) + +To prevent Broken Functional Level Authorization (BFLA), requests to REST API endpoints must be authorized correctly. This involves validating client permissions against the requested resources. Requests from clients with insufficient permissions must be rejected. + +Tyk offers several measures to assist with protection from BFLA threats: + +- *Establish path-based access rights*: [Policies](/api-management/policies) are predefined sets of rules which grant access to particular APIs. These can include [path-based permissions](/api-management/access-control/sessions-and-keys/access-rights#granular-endpoint-access), which restrict access to particular paths and methods within an API. Clients can be assigned one or more policies which the Gateway will validate when it receives a request. +- *Access Control*: Tyk has plugins that control access to API endpoints. They are known as [allowlist](/api-management/traffic-transformation/allow-list#api-definition) and [blocklist](/api-management/traffic-transformation/block-list#api-designer) and can be configured via the Endpoint Designer of an API Definition. Both plugins grant and deny access to API paths and methods, but do so in different ways, which makes them mutually exclusive. When the allowlist plugin is used, only the marked paths and methods are allowed, all other paths and methods are blocked. This can be perceived as *deny by default* since it provides the least privileges. The reverse is true for the blocklist plugin, only the paths and methods marked as blocklist are blocked, all other paths and methods are allowed. It is recommended to use the *allowlist* approach, since it is the most restrictive, only allowing marked endpoint paths and paths. +- *CORS*: This [functionality](/api-management/gateway-config-tyk-classic#cross-origin-resource-sharing-cors) allows the Tyk Gateway to limit API access to particular browser-based consumers. + +##### 6 - Unrestricted Access To Sensitive Business Flows + +This involves attackers understanding an API's business model, identifying sensitive business processes and automating unauthorized access to these processes. This can disrupt business operations by preventing legitimate users from making purchases for example. Attackers manually locate target resources and work to bypass any existing mitigation measures. + +These business flows are application specific, being unique to the API's backend systems. Subsequently, the API owner is responsible for addressing the security issues posed by this threat. Furthermore, to discover points of exploitation and test IT security breaches, pentesting is recommended. + +The APIM can be used to protect sensitive endpoints using authentication and authorization. Tyk recommends considering splitting Admin APIs from client facing APIs. This allows authentication and authorization checks to be defined and managed by different governance models, thus establishing clear role models. + +Furthermore, the APIM can validate authentication and authorization by scope to ensure that the client has the correct credentials before the upstream API processes the request. + +##### 7 - Server Side Request Forgery (SSRF) + +Server Side Request Forgery (SSRF) is a security vulnerability in web applications where an attacker can manipulate a server to make unauthorized requests to internal or external resources, potentially leading to data leaks or remote code execution. This can allow an attacker to probe or attack other parts of the application's infrastructure, potentially compromising sensitive information and systems. + +This is application specific and is largely the responsibility of the API. However, Tyk Gateway can assist with this form of attack through [JSON schema validation](/api-management/traffic-transformation/request-validation#request-validation-using-classic) for incoming payloads. For example, a schema could contain a regular expression to reject localhost URLs. These URLs could be used by an attacker to perform port scanning for example. + +##### 8 - Security Misconfiguration + +Tyk offers several mechanisms to help protect an API from Security Misconfiguration exploits: + +- Use [response header manipulation](/api-management/traffic-transformation/response-headers) to remove or modify API sensitive information. +- Use [response body manipulation](/api-management/traffic-transformation/response-body) to remove or modify parts containing sensitive information. +- [TLS](/api-management/certificates) to ensure that clients use the right service and encrypt traffic. +- [Mutual TLS](/api-management/implement-tls#secure-hosted-apis-with-mtls) with both the clients and API to ensure that callers with explicitly allowed client certificates can connect to the endpoints. +- [Error Customization](/api-management/custom-error-responses) can be used to return a response body based on status code and content type. This can help minimize the implementation details returned to the client. +- [CORS functionality](/api-management/gateway-config-tyk-classic#cross-origin-resource-sharing-cors) allows the Tyk Gateway to limit API access to particular browser-based consumers. +- [Policy Path-Based Permissions](/api-management/access-control/sessions-and-keys/access-rights#granular-endpoint-access) and the [allowlist](/api-management/traffic-transformation/allow-list#api-definition) plugin can be used to prevent clients from accessing API endpoints using non-authorized HTTP methods. For example, blocking the use of the DELETE method on an endpoint which should only accept GET requests. +- [Environment variables](/tyk-oss-gateway/configuration) can help standardize configuration across containerised deployments. +- For GraphQL APIs: +- [Schema Introspection](/api-management/graphql#introspection) ensures that the Tyk Dashboard automatically uses the schema of the upstream GraphQL API and can keep it synchronised if it changes. +- [GraphQL Schema Validation](/api-management/graphql#schema-validation) prevents invalid schemas from being saved. This catches errors such as duplicate type names and usage of unknown types. +- Third-party [Secret Storage](/tyk-configuration-reference/kv-store) to centralise configuration of sensitive data such as passwords. This data can then be dynamically referenced by Tyk configuration files, rather than being hard coded. +- Users can can write their own [custom plugins](/api-management/plugins/overview#) in a variety of languages, either directly or through gRPC calls, to implement their requirements. + +The Ops team should also take reponsibility for monitoring the APIs for errors and patching accordingly. Regular [Penetration Tests](https://en.wikipedia.org/wiki/Penetration_test) should be scheduled to ensure the security of published services. Tyk, through our Professional Services or Partners, can assist in the process. + +##### 9 - Improper Inventory Management + +Tyk offers the following features to support improper inventory management: + +- [Versioning](/api-management/api-versioning) allows newer versions of APIs to coexist with the older versions, facilitating deprecation and sunsetting. +- [Sunsetting](/api-management/api-versioning#sunsetting-api-versions) allows versions to be configured with an Expiry Time, ensuring that a version is not accessible after the expiry date. +- [Key expiry](/api-management/access-control/sessions-and-keys/session-lifecycle) ensures that access to an API is short lived, with a per key configurable Time to Live (TTL) for which a token remains valid before it expires. The implementation of key expiry, with a configurable Time To Live (TTL), mitigates the impact of compromised tokens by narrowing the window of vulnerability. Setting a TTL reduces the time frame during which a compromised token could be exploited, enhancing overall security. +- Tyk Developer Portal catalogs APIs and facilitates granting access to them. Integrated with a CMDB it can help keep documentation updated. +- [Tyk Analytics](/api-management/dashboard-analytics#traffic-analytics) can help identify the stagnant APIs and used stale APIs. +- [Tyk Pump](/api-management/tyk-pump) can ship metrics needed for analytics into Tyk Dashboard and other systems. +- Third-party [Secret Storage](/tyk-configuration-reference/kv-store) can be used to centralise and protect sensitive configuration data such as passwords, rather than exposing them as plain text in Tyk configuration files. + +In addition, it is best practice to consider any definition of done to include corresponding documentation updates. + +##### 10 - Unsafe Consumption Of APIs + +Attackers may identify and target the third party APIs/services used by an API. This can lead to leaked sensitive information, denial of service, injection attacks etc. + +It is the responsibility of the API to provide protection against these attacks. However, if the organization uses the Gateway as a forwarding proxy to third party APIs, then the following features could be used: + +- [JSON Schema validation](/api-management/traffic-transformation/request-validation#request-validation-using-classic) to validate that an incoming data payload meets a defined schema. Payloads that do not adhere to the schema are rejected. +- [Versioning](/api-management/api-versioning) allows newer versions of third party APIs to coexist with the older versions, facilitating deprecation and sunsetting. +- [TLS](/api-management/certificates) to ensure that clients use the right service and encrypt traffic. + + +## Managing Authentication and Authorization + +### Authentication + +Authentication is the process of identifying API clients. It’s a broad topic, with many approaches to choose from. Choosing the right approach is important, as it forms a fundamental part of the overall security strategy. The decision depends on many risk factors; users, functionality, data, accessibility and compliance, to name just a few. While there isn’t necessarily a single, correct choice, it’s usually safe to assume that some form of authentication is needed, as it’s a crucial prerequisite in performing subsequent identity-based authorization checks. + +**Implement Appropriate Authentication** + +Choose a suitable authentication approach based on the risk profile of the API. Is it publicly accessible or internal? Does it require user interaction or is it machine to machine? How sensitive is the data and functionality provided by the API? Simplistic approaches, such as [Auth Tokens](/api-management/authentication/bearer-token), can work for low risk, basic APIs, but for higher risk or more sophisticated APIs, it may be more appropriate to use a standards-based approach such as [OAuth 2.0](/api-management/authentication/oauth-2) or [OpenID Connect](/api-management/client-authentication#integrate-with-openid-connect-deprecated). Furthermore, using an [external identity provider](/api-management/client-authentication#integrate-with-external-authorization-server-deprecated) can deliver additional benefits, such as [single sign-on](/tyk-identity-broker/dashboard-sso), as well as multi-factor authentication approaches such as [biometric verification](https://www.okta.com/identity-101/biometrics-secure-authentication). + +**Handle Data Securely** + +Don’t undermine the authentication process by leaking sensitive authentication data. Use [transport layer security](/api-management/certificates) and hashing to prevent credentials from being intercepted and stolen through insecure transmission and storage. These principles also apply to upstream requests made by the gateway and upstream API to other APIs and services. + +**Enforce Good Practices** + + +Establish rules that reduce risk and enhance overall system security. Use [password policies](/platform-management/dashboard-users#password-policy) to prevent the use of weak passwords, and [TLS policies](/api-management/certificates#tls-or-ssl) to prevent the use of older TLS versions that are now deprecated and considered vulnerable. + +**Protect Sensitive Endpoints** + +Reduce susceptibility of sensitive endpoints to brute force dictionary or password stuffing attacks. The typical target for this type of attack are endpoints that use credentials, such as login and password recovery. Unfortunately, anonymous access is required for these endpoints, so authentication cannot be used to protect them, so the best approach is to hinder access by using techniques such as [rate limiting](/api-management/rate-limit#rate-limiting-layers), [captcha](https://en.wikipedia.org/wiki/CAPTCHA) and one-time URLs. + + +### Authorization +Authorization is the process of validating API client requests against the access rights they have been granted, ensuring that the requests comply with any imposed limitations. It’s the most prevalent topic on the OWASP list, with three entries covering different levels of authorization. + +Almost any part of a request can be scrutinised as part of authorization, but choosing the best approach depends on the type of API. For example, with REST APIs, the requested method and path are good candidates, but they aren’t relevant for GraphQL APIs, which should focus on the GraphQL query instead. + +Authorization can be a complex process that occurs at multiple locations throughout the request lifecycle. For example, a gateway can use access control policies to determine whether a required path is acceptable. But for decisions based on object data, such as when a client requests a particular record from the database, it’s the API that’s best positioned, as only it has access to the necessary data. For more information about the authorization process, see Authorization Levels in the appendix. + +#### Split Authorization + +Implement authorization in the best locations across the stack. For an overview of the different authorization levels across the stack please visit this [page](#managing-authorization-levels). Use the gateway to handle general API authorization related to hosts, methods, paths and properties. This leaves the API to handle the finer details of object-level authorization. In terms of OWASPs authorization categories, it can be split as follows: + +##### Object Level Authorization + +Handle with the API. It can access and understand the data needed to make authorization decisions on individual objects within its database. + +##### Object Property Level Authorization + +Handle with both the API and the gateway. The approach depends on the type of API: + +For REST APIs, it’s the API that’s primarily responsible for returning the correct data. To complement this, the gateway can use [body transforms](/api-management/traffic-transformation/response-body) to remove sensitive data from responses if the API is unable to do so itself. The gateway can also enforce object property-level restrictions using [JSON validation](/api-management/traffic-transformation/request-validation#request-validation-using-classic), for scenarios where the client is sending data to the API. + +For GraphQL APIs, use the gateway to define [GraphQL schemas](/api-management/graphql#managing-gql-schema) to limit which properties are queryable, then optionally use [field-based permissions](/api-management/graphql#field-based-permission) to also specify access rights to those properties. + +##### Function Level Authorization + +Handle with the gateway. Use [Policies](/api-management/policies), [path-based permissions](/api-management/access-control/sessions-and-keys/access-rights#granular-endpoint-access), [allow lists](/api-management/traffic-transformation/allow-list#api-definition) and [block lists](/api-management/traffic-transformation/block-list#api-designer) to manage authorization of hosts and paths. + +#### Assign Least Privileges + +Design [Policies](/api-management/policies) that contain the least privileges necessary for users to achieve the workflows supported by the API. By favoring specific, granular access over broad access, this enables user groups and use cases to be addressed directly, as opposed to broad policies that cover multiple use cases and expose functionality unnecessarily. + +##### Deny by Default + +Favor use of [allow lists](/api-management/traffic-transformation/allow-list#api-definition) to explicitly allow endpoints access, rather than [block lists](/api-management/traffic-transformation/block-list#api-designer) to explicitly deny. This approach prevents new API endpoints from being accessible by default, as the presence of other, allowed endpoints means that access to them is implicitly denied. + +##### Validate and Control All User Input + +Protect APIs from erroneous or malicious data by validating all input before it’s processed by the API. Bad data, whether malicious or not, can cause many problems for APIs, from basic errors and bad user experience, to data leaks and downtime. The standard mitigation approach is to validate all user input, for which there are various solutions depending on the type of API: + +For REST APIs, use [schema validation](/api-management/graphql#schema-validation) to control acceptable input data values. + +For GraphQL APIs, use [GraphQL schema](/api-management/graphql#managing-gql-schema) definitions to limit what data can be queried and mutated. Additionally, [complexity limiting](/api-management/graphql#complexity-limiting-1) can be used to block resource-intensive queries. + +#### Track Anomalies + +Use [log aggregation](/api-management/logs#log-output) and [event triggers](/api-management/gateway-events#event-categories) to push data generated by application logs and events into centralised monitoring and reporting systems. This real-time data stream can be used to highlight application issues and security-related events, such as authentication and authorization failures. + +##### Understand System State + +Perform application performance monitoring by capturing gateway [instrumentation data](/api-management/logs-metrics#statsd-instrumentation). This enables the current system state, such as requests per second and response time, to be monitored and alerted upon. + +##### Manage Cross-Origin Resource Sharing + +Use [CORS filtering](/api-management/gateway-config-tyk-classic#cross-origin-resource-sharing-cors) to control the resources accessible by browser-based clients. This is a necessity for APIs that expect to be consumed by external websites. + + +### Managing Authorization Levels + +This section provides basic examples of where different authorization levels occur in the API management stack. The accompanying diagrams use color-coding to show links between request element and the associated authorization locations and methods. + +This is how OWASP describe the attack vectors for the three authorization levels: + +**Object Level Authorization**: “Attackers can exploit API endpoints that are vulnerable to broken object-level authorization by manipulating the ID of an object that is sent within the request. Object IDs can be anything from sequential integers, UUIDs, or generic strings. Regardless of the data type, they are easy to identify in the request target (path or query string parameters), request headers, or even as part of the request payload.” (source: [OWASP Github](https://github.com/OWASP/API-Security/blob/9c9a808215fcbebda9f657c12f3e572371697eb2/editions/2023/en/0xa1-broken-object-level-authorization.md)) + +**Object Property Level Authorization**: “APIs tend to expose endpoints that return all object’s properties. This is particularly valid for REST APIs. For other protocols such as GraphQL, it may require crafted requests to specify which properties should be returned. Identifying these additional properties that can be manipulated requires more effort, but there are a few automated tools available to assist in this task.” (source: [OWASP Github](https://github.com/OWASP/API-Security/blob/9c9a808215fcbebda9f657c12f3e572371697eb2/editions/2023/en/0xa3-broken-object-property-level-authorization.md)) + +**Function Level Authorization**: “Exploitation requires the attacker to send legitimate API calls to an API endpoint that they should not have access to as anonymous users or regular, non-privileged users. Exposed endpoints will be easily exploited.” (source: [OWASP Github](https://github.com/OWASP/API-Security/blob/9c9a808215fcbebda9f657c12f3e572371697eb2/editions/2023/en/0xa3-broken-object-property-level-authorization.md)) + + +#### REST API - Reading Data + +Rest API - Read Data + +The client sends a `GET` request using the path `/profile/1`. This path has two parts: + +1. `/profile/`: The resource type, which is static for all requests related to profile objects. This requires function level authorization. + +2. `1`: The resource reference, which is dynamic and depends on the profile is being requested. This requires object level authorization. + +Next, the gateway handles function level authorization by checking that the static part of the path, in this case `/profile/`, is authorized for access. It does this by cross referencing the security policies connected to the API key provided in the `authorization` header. + +The gateway ignores the dynamic part of the part of the path, in this case `1`, as it doesn't have access to the necessary object-level data to make an authorization decision for this. + +Lastly, the API handles object level authorization by using custom logic. This typically involves using the value of the `authorization` header in combination with the ownership and authorization model specific to the API to determine if the client is authorized to read is requested record. + +#### REST API - Writing Data + +Rest API - Write Data + +The client sends a `POST` request using the path `/profile` and body data containing the object to write. The path `/profile` is static and requires function level authorization. The body data contains a JSON object that has two fields: + +1. `name`: A standard object field. This requires object property authorization. + +2. `id`: An object identifier field that refers to the identity of an object, so needs to be treated differently. As such, it requires both object property authorization, like name, and also object authorization. + +Next, the gateway handles function level authorization, by checking that the path, in the case `/profile`, is authorized for access. It does this by cross referencing the security policies connected to the API key provided in the `authorization` header. + +The gateway can also perform object property level authorization, by validating that the values of the body data fields, `name` and `id`, conform to a schema. + +Lastly, the API handles object level authorization by using custom logic. This typically involves using the value of the `authorization` header in combination with the ownership and authorization model specific to the API to determine if the client is authorized to write the requested data. + +#### GraphQL API - Querying Data + +Rest API - Write Data + +The client sends a `POST` request using the path `/graphql` and body data containing a GraphQL query. The path `/graphql` is static and requires function level authorization. The GraphQL query contains several elements: + +- `profile`: An object type, referring to the type of object being requested. This requires object property authorization. +- `id`: An object identifier field that refers to the identity of an object, so needs to be treated differently. As such, it requires both object property authorization, like name, and also object authorization. +- `name`: A standard object field, referring to a property of the profile object type. This requires object property authorization. + +Next, the Gateway handles function level authorization, by checking that the path, in the case `/graphql`, is authorized for access. It does this by cross referencing the security policies connected to the API key provided in the `authorization` header. Due to the nature of GraphQL using just a single endpoint, there is no need for additional path-based authorization features, only a basic security policy is required. + +Another difference between this and the REST examples is in the way that the body data is authorized: + +- All object types and fields contained in the query are checked against the API’s GraphQL schema, to ensure they are valid. In this case, the object type is `profile`, and the fields are `id` and `name`. The schema defined in the gateway configuration can differ from that in the upstream API, which enables fields to be restricted by default. +- Field-based permissions can also be used, to authorize client access of individual fields available in the schema. In this case, `id` and `name`. + +Lastly, the API handles object level authorization by using custom logic. This typically involves using the value of the `authorization` header in combination with the ownership and authorization model specific to the API to determine if the client is authorized to access the requested data. This can be more complicated for GraphQL APIs, as the data presented by the schema may actually come from several different data sources. + +## Managing API Resources + +Excessive resource consumption poses a risk to APIs. As the number of concurrent requests handled by a server increases, so too does its consumption of CPU, RAM and storage resources. Should any of these become depleted, then the quality of service offered by applications running on the server will rapidly decline, and may even lead to their complete failure. + +This issue can be caused by both legitimate consumers and malicious attackers, but they are different situations that require different solutions. For legitimate consumers, solutions should be focused on controlling API utilization through the gateway, to keep usage within agreed or desired limits. But malicious attackers require a different approach, as denial of service attacks must be blocked as far as possible from the core API infrastructure. + +**Restrict Request Flows**: Use [rate limits](/api-management/rate-limit#rate-limiting-layers) and [quotas](/api-management/request-quotas) to prevent excessive API usage. Rate limits are best used for short term control, in the range of seconds. Whereas quotas are more suited to longer terms, in the range of days, weeks or beyond. [Throttling](/api-management/request-throttling) can also be used as a type of enhanced rate limiter that queues and retries requests on the clients behalf, rather than immediately rejecting them. + +**Block Excessively Large Requests**: Place reasonable [limitations on payload sizes](/api-management/traffic-transformation/request-size-limits) to prevent oversized requests from reaching upstream servers, thereby avoiding the unnecessary consumption of resources. + +**Avoid Unnecessary Resource Usage**: Appropriate use of [caching](/api-management/response-caching) can reduce server resource consumption by simply returning cached responses instead of generating new ones. The extent to which caching can be used depends on the purpose of the endpoint, as it’s generally unsuitable for requests that modify data or responses that frequently change. Caching can be applied to [particular requests](/api-management/response-caching#endpoint-caching) or enabled for an [entire API](/api-management/response-caching#basic-caching), and can also be [controlled by the upstream API](/api-management/response-caching#upstream-cache-control-1) or [invalidated programmatically](/api-management/troubleshooting-debugging#how-to-clear-%2F-invalidate-api-cache). + +**Limit Complex Long-Running Tasks**: Use [GraphQL complexity limiting](/api-management/graphql#complexity-limiting-1) to prevent convoluted queries from being processed. Alternatively, [timeouts](/planning-for-production/ensure-high-availability/enforced-timeouts) can be used to terminate long-running requests that exceed a given time limit. + +**Protect Failing Services**: Defend struggling endpoints by using a [circuit breaker](/planning-for-production/ensure-high-availability/circuit-breakers). This feature protects endpoints by detecting error responses, then blocking requests for a short duration to allow them to recover. The same principle can be applied in a wider sense by using [uptime tests](/api-management/gateway-config-tyk-classic#uptime-tests), though this works on a host level instead, by removing failed hosts from the gateway load balancer. + +**Enforce Network-Level Security**: Problematic clients can be prevented from accessing the API by [blocking their address](/api-management/gateway-config-tyk-classic#ip-access-control). Conversely, for APIs with a known set of clients, [allow lists](/api-management/gateway-config-tyk-classic#ip-access-control) can be used to create a list of allowed addresses, thereby implicitly blocking every other address from the API. + +**Mitigate DoS Attacks**: Increase the chance of maintaining API availability during a denial of service attack by using [specialist mitigation services](https://www.cloudflare.com). These have the infrastructure capacity needed to handle [large scale distributed attacks](https://www.cloudflare.com/en-gb/learning/ddos/what-is-a-ddos-attack), with the purpose of preventing attacks from reaching the API infrastructure, thereby enabling the API to continue operating normally. + + +## Configuration Best Practices + +Modern APIs are often backed by large technology stacks composed of numerous components and libraries. Each of these is a potential weak link in the security chain, so efforts must be made to ensure that security measures are implemented throughout. The API gateway plays a critical part in an overall security strategy, by utilizing its ability to process requests in a secure manner. + +**Secure Connections** + + +Use [transport layer security](/api-management/certificates) where possible. Most importantly, on inbound connections to the gateway and outbound connection from the gateway to the upstream API and other services. TLS can also be used as a form of authentication, using [Mutual TLS](/api-management/implement-tls#secure-hosted-apis-with-mtls). + +**Limit Functionality** + + +Use [Policies](/api-management/policies) to specify which paths, methods and schemas are accessible, whilst blocking all others. + +**Mitigate Server-Side Request Forgery** + + +Restrict any URL-based input data to specific schemas, hosts and paths by using [schema validation](/api-management/graphql#schema-validation). When data is fetched server-side, it should be validated and not returned to the client in raw format. + +**Protect Secrets** + + +Prevent sensitive data, such as usernames, passwords, license keys and other secrets, from being stored as plain text in application configuration files. Use [key value secret storage](/tyk-configuration-reference/kv-store) to dynamically load sensitive data from a secure secret manager. + +**Sanitise Responses** + + +Modify or remove sensitive data from responses by using [transforms](/api-management/traffic-transformation) to alter the [response headers](/api-management/traffic-transformation/response-headers) and [body](/api-management/traffic-transformation/response-body). + + + +**Sign payloads between the Dashboard and Gateway** + + +Using payload signatures for the communication between Tyk Gateway and Tyk Dashboard is strongly recommended as an additional security measure, particularly in production environments. + +Enable payload signatures in the Gateway configuration (`tyk.conf` or environment variable) by setting `allow_insecure_configs` to `false` and then provide the public key (certificate) to the Gateway in the `public_key_path`. + +You'll need to provide the private key to the Dashboard using the `private_key_path` option in the appropriate configuration (`tyk_analytics.conf` or environment variable). This will allow your Dashboard to sign all of its payloads using the private key. + +You can easily create a public / private keypair with: + +```{.copyWrapper} +# private key +openssl genrsa -out privkey.pem 2048 + +# public key +openssl rsa -in privkey.pem -pubout -out pubkey.pem +``` + +Make sure to keep your private key safe! + +
+ +## Governing APIs Effectively + +APIs need to be managed and governed just like any other resource, otherwise organizations risk losing track of their API estate and becoming unaware of potentially vulnerable APIs running within their infrastructure. This risk is magnified as the number of teams, environments and APIs increases. Use API management as part of overarching business processes to control how APIs are accessed, managed and deployed. + +**Restrict Version Availability**: Enforce the expiry of [API versions](/api-management/api-versioning) that are planned for deprecation, by setting a sunset date, beyond which they will not be accessible. + +**Enforce Key Expiry**: In many situations it’s best to issue API keys that have a short, finite lifetime, especially when serving anonymous, external consumers. Set [expiry dates](/api-management/access-control/sessions-and-keys/session-lifecycle) for API keys, or use ephemeral credentials with complementary authentication techniques that support key renewal, such as [OAuth 2.0 refresh tokens](/api-management/authentication/oauth-2#using-refresh-tokens) and [dynamic client registration](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration). Then, should an API key fall into the wrong hands, there’s a chance that it has already expired. + +**Use Standardized Specifications**: Use the [OpenAPI Specification](https://en.wikipedia.org/wiki/OpenAPI_Specification) standard to design APIs. These specification documents act as a source of truth that can generate [API configuration](/api-management/gateway-config-tyk-oas) and [portal documentation](/tyk-apis/tyk-portal-api/portal-documentation#create-documentation). + +**Understand API Usage**: Use [API analytics](/api-management/dashboard-analytics#traffic-analytics) to report on usage. This captured data generates useful, actionable insights across a variety of metrics, such as API popularity, performance and trends. + +**Control API Distribution**: Use [sharding](/api-management/api-sharding#what-is-api-sharding-) to control availability of APIs across multi-gateway, multi-environment deployments. This ensures that specific APIs are only available through specific gateways, which helps to prevent undesirable situations, such as internal APIs being published to externally accessible gateways, or test API configurations reaching the production environment. +
+ +## Securing APIs with Tyk + +Securing your APIs is one of the primary uses of Tyk API management solution. Out of the box, the Gateway offers a lot of functionality for securing your APIs and the Gateway itself. + +This section outlines all of the security configurations and components that are available to you when securing your Tyk stack. + +This section outlines some of the key security concepts that Tyk uses and that you should be familiar with before setting up and using a Tyk stack to secure your API. + +**Key Hashing** + + +See [Key Hashing](/api-management/access-control/sessions-and-keys/key-hashing) for details on how Tyk obfuscates keys in Redis. + +**TLS and SSL** + + +Tyk supports TLS connections and Mutual TLS. All TLS connections also support HTTP/2. Tyk also supports Let's Encrypt. See [TLS and SSL](/api-management/certificates) for more details. + +**Trusted Certificates** + + +As part of using Mutual TLS, you can create a list of [trusted certificates](/api-management/implement-tls#using-a-static-client-certificate-allow-list). + +**Certificate Pinning** + + +Introduced in Tyk Gateway 2.6.0, [certificate pinning](/api-management/upstream-authentication/mtls#certificate-pinning) is a feature which allows you to allow only specified public keys used to generate certificates, so you will be protected in case an upstream certificate is compromised. + +**API Security** + +Tyk supports various ways to secure your APIs, including: + +* Bearer Tokens +* HMAC +* JSON Web Tokens (JWT) +* Multi Chained Authentication +* OAuth 2.0 +* OpenID Connect + +See [Authentication and Authorization](/api-management/client-authentication) for more details. + +**Security Policies** + + +A Tyk security policy incorporates several security options that can be applied to an API key. These include [Partioned Policies](/api-management/access-control/policies/applying-policies#partitioned-policies) and securing by [Method and Path](/api-management/access-control/sessions-and-keys/access-rights#granular-endpoint-access). + +See [Access Control](/api-management/access-control/overview) for more details. diff --git a/api-management/security-features.mdx b/api-management/security-features.mdx new file mode 100644 index 0000000000..3993d25768 --- /dev/null +++ b/api-management/security-features.mdx @@ -0,0 +1,86 @@ +--- +title: "Security Features" +description: "Learn how to configure security features in Tyk Gateway, including Cross-Origin Resource Sharing (CORS) for browser-based requests" +keywords: "Security, security features, CORS, API Security, Cross-Origin Resource Sharing, Security, Configuration" +sidebarTitle: "Security Features" +--- + +## Cross-Origin Resource Sharing (CORS) + +CORS (Cross-Origin Resource Sharing) is a security feature that controls how web pages from one domain (origin) can make requests to resources hosted on a different domain. With Tyk Gateway, it is possible to enable and configure CORS per-API so that users can make browser-based requests. + +The `CORS` section is added to an API definition as listed in the examples below for Tyk Gateway and Tyk Operator. + +### Examples + + + +```json +"CORS": { + "enable": true, + "allowed_origins": [ + "http://foo.com" + ], + "allowed_methods": [], + "allowed_headers": [], + "exposed_headers": [], + "allow_credentials": false, + "max_age": 24, + "options_passthrough": false, + "debug": false +} +``` + + +```yaml {linenos=true, linenostart=1, hl_lines=["14-24"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-cors-sample +spec: + name: httpbin-cors-sample + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /cors + strip_listen_path: true + CORS: + enable: true + allowed_origins: + - "http://foo.com" + allowed_methods: null + allowed_headers: null + exposed_headers: null + allow_credentials: false + max_age: 24 + options_passthrough: false + debug: false +``` + + + +--- + +### Configuration + +The CORS middleware has the following options: + +* `CORS.allowed_origins`: A list of origin domains to allow access from. Wildcards are also supported, e.g. `http://*.foo.com`. Default value is `["*"]` + +* `CORS.allowed_methods`: A list of methods to allow access via. Default value is `["GET", "POST", "HEAD"]` + +* `CORS.allowed_headers`: A list of headers that are allowed within a request. Default value is `["Origin", "Accept", "Content-Type", "X-Requested-With"]` + +* `CORS.exposed_headers`: A list of headers that are exposed back in the response. + +* `CORS.allow_credentials`: Whether credentials (cookies) should be allowed. + +* `CORS.max_age`: Maximum age of credentials. + +* `CORS.options_passthrough`: allow CORS OPTIONS preflight request to be proxied directly to upstream, without authentication and rest of checks. This means that pre-flight requests generated by web-clients such as SwaggerUI or +the Tyk Portal documentation system will be able to test the API using trial keys. If your service handles CORS natively, then enable this option. + +* `debug`: If set to `true`, this option produces log files for the CORS middleware. + diff --git a/api-management/single-sign-on-ldap.mdx b/api-management/single-sign-on-ldap.mdx new file mode 100644 index 0000000000..de005c09de --- /dev/null +++ b/api-management/single-sign-on-ldap.mdx @@ -0,0 +1,222 @@ +--- +title: "SSO with LDAP" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard or Tyk Developer Portal using LDAP or Active Directory." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, LDAP, Active Directory, Authentication" +sidebarTitle: "LDAP / Active Directory" +--- + +## Introduction + +TIB supports Lightweight Directory Access Protocol (LDAP) and Active Directory using the `ADProvider` method, which uses a passthrough flow; user credentials are submitted directly to TIB, which validates them against your LDAP server. No browser redirect to an external IdP is involved. + +Because LDAP is a passthrough flow, you must provide a login page that submits credentials to TIB. Tyk Dashboard and Tyk Developer Portal do not include a built-in LDAP login page. + +Before configuring your TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +## TIB Profile + +The LDAP-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `ADProvider` and `Type` to `passthrough`. + +```json expandable +{ + "ProviderName": "ADProvider", + "Type": "passthrough", + "ProviderConfig": { + "LDAPServer": "{ldap-server-hostname}", + "LDAPPort": "389", + "LDAPUserDN": "cn=*USERNAME*,dc=example,dc=com", + "LDAPBaseDN": "dc=example,dc=com", + "LDAPFilter": "(objectClass=person)", + "LDAPEmailAttribute": "mail", + "LDAPFirstNameAttribute": "givenName", + "LDAPLastNameAttribute": "sn", + "LDAPAttributes": [], + "FailureRedirect": "http://{failure-redirect-url}", + "GetAuthFromBAHeader": true + } +} +``` + +The LDAP-specific `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `LDAPServer` | Hostname or IP address of your LDAP server. | +| `LDAPPort` | Port of your LDAP server. Use `389` for standard LDAP or `636` for LDAPS. | +| `LDAPUserDN` | Distinguished Name template used to bind as the authenticating user. The literal string `*USERNAME*` is replaced at runtime with the submitted username. | +| `LDAPBaseDN` | Base DN from which LDAP searches are performed. | +| `LDAPFilter` | LDAP search filter applied when looking up users. | +| `LDAPEmailAttribute` | LDAP attribute containing the user's email address. Defaults to `mail`. | +| `LDAPFirstNameAttribute` | LDAP attribute containing the user's first name. Defaults to `givenName`. | +| `LDAPLastNameAttribute` | LDAP attribute containing the user's last name. Defaults to `sn`. | +| `LDAPAttributes` | Additional LDAP attributes to retrieve. Can be an empty list. | +| `LDAPUseSSL` | Set to `true` to connect using LDAPS. | +| `LDAPAdminUser` | DN of an admin user for performing user-lookup searches, if required. | +| `LDAPAdminPassword` | Password for the admin user. | +| `LDAPSearchScope` | Depth of the LDAP search: `0` for base object only, `1` for single level below the base DN, `2` for the entire subtree. Defaults to `2`. | +| `DefaultDomain` | Domain appended to the username when building the full user identifier. Used to construct the username but not for performing LDAP requests. | +| `FailureRedirect` | URL to redirect the user to on authentication failure. | +| `GetAuthFromBAHeader` | Set to `true` to read the username and password from the HTTP Basic Auth header. Recommended for form-based login pages. | +| `SlugifyUserName` | Set to `true` to normalize the username to a URL-safe slug. | + +## Login Page + +Since LDAP is a passthrough flow, users submit credentials directly to TIB via a form `POST`. Create a login page with a form that posts to the TIB authentication endpoint: + +```html +
+ + + +
+``` + +The form must use `POST` method and include `username` and `password` fields. TIB reads these field names exactly. + +For embedded TIB, `{tib-host}` is the same as your Dashboard host. For Portal, the embedded TIB is accessible under the `/tib` path prefix, so use `{portal-host}/tib` as the base. + +## Worked Examples + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso#unregistered-user-login) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "ldap-dashboard", + "Name": "LDAP Dashboard SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "passthrough", + "ProviderName": "ADProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "LDAPServer": "ldap.example.com", + "LDAPPort": "389", + "LDAPUserDN": "cn=*USERNAME*,dc=example,dc=com", + "LDAPBaseDN": "dc=example,dc=com", + "LDAPFilter": "(objectClass=person)", + "LDAPEmailAttribute": "mail", + "LDAPFirstNameAttribute": "givenName", + "LDAPLastNameAttribute": "sn", + "LDAPAttributes": [], + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "GetAuthFromBAHeader": true + } +} +``` + +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials +- update `LDAPUserDN` to match your LDAP directory structure, keeping `*USERNAME*` as a literal placeholder + +**Login page form action** + +Your login page form should `POST` to: + +``` +http://dashboard.example.com:3000/auth/ldap-dashboard/ADProvider +``` + +**Redirect to login page** + +To redirect users to your custom login page instead of the default Dashboard login, set `sso_custom_login_url` in the Tyk Dashboard configuration: + +```json +{ + "sso_custom_login_url": "http://{your-login-page-url}" +} +``` + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "ldap-portal", + "Name": "LDAP Portal SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "passthrough", + "ProviderName": "ADProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "LDAPServer": "ldap.example.com", + "LDAPPort": "389", + "LDAPUserDN": "cn=*USERNAME*,dc=example,dc=com", + "LDAPBaseDN": "dc=example,dc=com", + "LDAPFilter": "(objectClass=person)", + "LDAPEmailAttribute": "mail", + "LDAPFirstNameAttribute": "givenName", + "LDAPLastNameAttribute": "sn", + "LDAPAttributes": [], + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "GetAuthFromBAHeader": true + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `DashboardCredential` to the [`PortalAPISecret`](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API +- update `LDAPUserDN` to match your LDAP directory structure, keeping `*USERNAME*` as a literal placeholder + +**Login page form action** + +Your login page form should `POST` to: + +``` +http://portal.example.com:3001/tib/auth/ldap-portal/ADProvider +``` + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + diff --git a/api-management/single-sign-on-social-idp.mdx b/api-management/single-sign-on-social-idp.mdx new file mode 100644 index 0000000000..9efa961376 --- /dev/null +++ b/api-management/single-sign-on-social-idp.mdx @@ -0,0 +1,454 @@ +--- +title: "SSO with Social Identity Providers" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard or Tyk Developer Portal using OAuth-based social identity providers such as Google, GitHub, and LinkedIn." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, Google, GitHub, LinkedIn, OAuth, Social Login, Authentication" +sidebarTitle: "Social Login" +--- + +## Introduction + +TIB supports OAuth 2.0-based social identity providers using `SocialProvider`. Each supported provider requires an OAuth application registered with that provider, from which you obtain a Client ID and Client Secret. + +The following provider names are supported in `UseProviders[].Name`: + +| Provider | `Name` value | +|---|---| +| GitHub | `github` | +| LinkedIn | `linkedin` | +| Twitter / X | `twitter` | +| Bitbucket | `bitbucket` | +| DigitalOcean | `digitalocean` | +| Dropbox | `dropbox` | +| Salesforce | `salesforce` | + + +The legacy `gplus` provider for Google no longer works following the Google+ shutdown in 2019. Use the `openid-connect` provider name instead, as shown in the [Google example](#worked-example-google) below. + + +Before configuring your TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +## TIB Profile + +The social provider configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SocialProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + "CallbackBaseURL": "http://{tib-host}", + "FailureRedirect": "http://{failure-redirect-url}", + "UseProviders": [ + { + "Name": "{provider-name}", + "Key": "{client-id}", + "Secret": "{client-secret}" + } + ] + } +} +``` + +| Field | Description | +|---|---| +| `CallbackBaseURL` | The base URL of your TIB instance. TIB appends the callback path automatically. | +| `FailureRedirect` | URL to redirect the user to on authentication failure. | +| `UseProviders.Name` | The provider name (for example, `github`, `linkedin`). See the table above. | +| `UseProviders.Key` | The OAuth Client ID from your social provider application. | +| `UseProviders.Secret` | The OAuth Client Secret from your social provider application. | + +### Domain Constraint + +For providers that return the user's email address (such as Google), you can restrict access to users from a specific email domain by adding a `ProviderConstraints` block to the profile: + +```json +{ + "ProviderConstraints": { + "Domain": "your-company.com", + "Group": "" + } +} +``` + +Users whose email address does not match the configured domain will be redirected to `FailureRedirect`. + +### JSON Web Encryption (JWE) + +`SocialProvider` supports JSON Web Encryption (JWE), which allows TIB to decrypt encrypted ID tokens returned by the IdP. This is useful when your IdP is configured to encrypt tokens for additional security. + +JWE requires Tyk Identity Broker v1.6.1+ and Tyk Dashboard v5.7.0+. + +To enable JWE, add a `JWE` block to `ProviderConfig`: + +```json +{ + "ProviderConfig": { + "UseProviders": [...], + "JWE": { + "Enabled": true, + "PrivateKeyLocation": "{certificate-id-or-path}" + } + } +} +``` + +| Field | Description | +|---|---| +| `Enabled` | Set to `true` to enable JWE decryption. | +| `PrivateKeyLocation` | For embedded TIB in Tyk Dashboard, use the certificate ID from the Tyk Dashboard certificate manager. For standalone TIB, use the file path to a PEM file containing the private key. | + +The private key must correspond to the public key registered with your IdP for token encryption. Configure your IdP to encrypt ID tokens using the matching public key before enabling this setting. + + +## Configure Your Provider + +Register an OAuth application with your chosen social provider and note the Client ID and Client Secret. The callback URL to register with the provider is shown below. The `{profile-id}` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://{tib-host}/auth/{profile-id}/{provider-name}/callback +``` + +For example, for GitHub with a profile ID of `github-dashboard` and TIB running at `http://dashboard.example.com:3000`: + +``` +http://dashboard.example.com:3000/auth/github-dashboard/github/callback +``` + +## Worked Example: GitHub + +This example configures GitHub OAuth for Dashboard SSO. The same pattern applies to all other social providers; only the `Name`, `Key`, `Secret`, and the callback URL registered with the provider differ. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**GitHub OAuth application** + +Register an OAuth application at [github.com/settings/applications/new](https://github.com/settings/applications/new). Set the **Authorization callback URL** to: + +``` +http://dashboard.example.com:3000/auth/github-dashboard/github/callback +``` + +Note the **Client ID** and **Client Secret**. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "github-dashboard", + "Name": "GitHub Dashboard SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://dashboard.example.com:3000", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "UseProviders": [ + { + "Name": "github", + "Key": "{github-client-id}", + "Secret": "{github-client-secret}" + } + ] + } +} +``` + +- set `Key` to the GitHub **Client ID** +- set `Secret` to the GitHub **Client Secret** +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/github-dashboard/github +``` + +In production, present this as a "Log in with GitHub" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**GitHub OAuth application** + +Register an OAuth application at [github.com/settings/applications/new](https://github.com/settings/applications/new). Set the **Authorization callback URL** to: + +``` +http://portal.example.com:3001/tib/auth/github-portal/github/callback +``` + +Note the **Client ID** and **Client Secret**. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "github-portal", + "Name": "GitHub Portal SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://portal.example.com:3001", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "UseProviders": [ + { + "Name": "github", + "Key": "{github-client-id}", + "Secret": "{github-client-secret}" + } + ] + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `Key` to the GitHub **Client ID** +- set `Secret` to the GitHub **Client Secret** +- set `DashboardCredential` to the [`PortalAPISecret`](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/tib/auth/github-portal/github +``` + +In production, present this as a "Log in with GitHub" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + + +## Worked Example: Google + +Google authentication uses the `openid-connect` provider name rather than a named OAuth provider, since the `gplus` provider was retired in 2019. The setup follows the same pattern as any OIDC provider. + +**Configure Google** + +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) and navigate to **APIs and Services > Credentials**. +2. Click **Create Credentials** and select **OAuth client ID**. +3. Select **Web application** as the application type. +4. Under **Authorized redirect URIs**, add the TIB callback URL: + ``` + http://{tib-host}/auth/{profile-id}/openid-connect/callback + ``` +5. Click **Create** and note the **Client ID** and **Client Secret**. + +Google's OIDC discovery URL is: +``` +https://accounts.google.com/.well-known/openid-configuration +``` + + +These examples use embedded TIB, so the `CallbackBaseURL` is the same as the Dashboard or Portal respectively; TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "google-dashboard-oidc", + "Name": "Google Dashboard SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://dashboard.example.com:3000", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{google-client-id}", + "Secret": "{google-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://accounts.google.com/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `Key` to the Google **Client ID** +- set `Secret` to the Google **Client Secret** +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Google redirect URI** + +Ensure the following URL is listed in **Authorized redirect URIs** in your Google Cloud Console credentials. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://dashboard.example.com:3000/auth/google-dashboard-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/google-dashboard-oidc/openid-connect +``` + +In production, present this as a "Log in with Google" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "google-portal-oidc", + "Name": "Google Portal SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://portal.example.com:3001", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{google-client-id}", + "Secret": "{google-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://accounts.google.com/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `Key` to the Google **Client ID** +- set `Secret` to the Google **Client Secret** +- set `DashboardCredential` to the [`PortalAPISecret`](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Google redirect URI** + +Ensure the following URL is listed in **Authorized redirect URIs** in your Google Cloud Console credentials. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://portal.example.com:3001/tib/auth/google-portal-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/tib/auth/google-portal-oidc/openid-connect +``` + +In production, present this as a "Log in with Google" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + diff --git a/api-management/stream-config.mdx b/api-management/stream-config.mdx new file mode 100644 index 0000000000..2f59121e60 --- /dev/null +++ b/api-management/stream-config.mdx @@ -0,0 +1,6720 @@ +--- +title: "Tyk Streams Configuration" +description: "Learn how to configure Tyk Streams for event-driven API management" +keywords: "Broker, Input, Output, HTTP Client, HTTP Server, Processors, Scanners, CSV, Lines, Regular Expression, Switch, Avro, Kafka" +sidebarTitle: "Tyk Streams Reference" +--- + +## Overview + +Tyk streams configuration is specified using YAML. The configuration consists of several main sections: *input*, *pipeline*, *output* and optionally *logger*. + +### Input + +The input section defines the publisher source of the data stream. Tyk Streams supports various input types such as Kafka, HTTP, MQTT etc. Each input type has specific configuration parameters. + +```yaml +input: + kafka: + addresses: + - localhost:9092 + topics: + - example_topic + consumer_group: example_group + client_id: example_client +``` + +### Pipeline + +The pipeline section defines the processing steps applied to the data. It includes processors for filtering, mapping, enriching and transforming the data. Processors can be chained together. + +```yaml +pipeline: + processors: + - mapping: | + root = this + root.foo = this.bar.uppercase() + - json_schema: + schema_path: "./schemas/example_schema.json" +``` + +### Output + +The output section specifies the destination of the processed data. Similar to inputs, Tyk Streams supports various output types like Kafka, HTTP etc. + +```yaml +output: + kafka: + addresses: + - localhost:9092 + topic: output_topic + client_id: example_output_client +``` + +### Logger (Optional) + +The logger section is used to configure logging options, such as log level and output format. + +```yaml +logger: + level: INFO + format: json +``` + +## Inputs + +### Overview + +An input is a source of data piped through an array of optional [processors](/api-management/stream-config#overview-3): + +```yaml +input: + label: my_kafka_input + + kafka: + addresses: [ localhost:9092 ] + topics: [ foo, bar ] + consumer_group: foogroup + + # Optional list of processing steps + processors: + - avro: + operator: to_json +``` + +#### Brokering + +Only one input is configured at the root of a Tyk Streams config. However, the root input can be a [broker](/api-management/stream-config#broker) which combines multiple inputs and merges the streams: + +```yaml +input: + broker: + inputs: + - kafka: + addresses: [ localhost:9092 ] + topics: [ foo, bar ] + consumer_group: foogroup + + - http_client: + url: https://localhost:8085 + verb: GET + stream: + enabled: true +``` + +#### Labels + +Inputs have an optional field `label` that can uniquely identify them in observability data such as logs. + +{/* TODO + +When know if Tyk Streams will support metrics then link to metrics + +Inputs have an optional field `label` that can uniquely identify them in observability data such as metrics and logs. This can be useful when running configs with multiple inputs, otherwise their metrics labels will be generated based on their composition. For more information check out the [metrics documentation][metrics.about]. */} + +### Broker + +Allows you to combine multiple inputs into a single stream of data, where each input will be read in parallel. + +#### Common + +```yml +# Common config fields, showing default values +input: + label: "" + broker: + inputs: [] # No default (required) + batching: + count: 0 + byte_size: 0 + period: "" + check: "" +``` + +#### Advanced + +```yml +# All config fields, showing default values +input: + label: "" + broker: + copies: 1 + inputs: [] # No default (required) + batching: + count: 0 + byte_size: 0 + period: "" + check: "" + processors: [] # No default (optional) +``` + +A broker type is configured with its own list of input configurations and a field to specify how many copies of the list of inputs should be created. + +Adding more input types allows you to combine streams from multiple sources into one. For example, reading from both RabbitMQ and Kafka: + +```yaml +input: + broker: + copies: 1 + inputs: + - amqp_0_9: + urls: + - amqp://guest:guest@localhost:5672/ + consumer_tag: tyk-consumer + queue: tyk-queue + + # Optional list of input specific processing steps + processors: + - mapping: | + root.message = this + root.meta.link_count = this.links.length() + root.user.age = this.user.age.number() + + - kafka: + addresses: + - localhost:9092 + client_id: tyk_kafka_input + consumer_group: tyk_consumer_group + topics: [ tyk_stream:0 ] +``` + +If the number of copies is greater than zero the list will be copied that number of times. For example, if your inputs were of type foo and bar, with 'copies' set to '2', you would end up with two 'foo' inputs and two 'bar' inputs. + +##### Batching + +It's possible to configure a [batch policy](/api-management/stream-config#batch-policy) with a broker using the `batching` fields. When doing this the feeds from all child inputs are combined. Some inputs do not support broker based batching and specify this in their documentation. + +##### Processors + +It is possible to configure processors at the broker level, where they will be applied to *all* child inputs, as well as on the individual child inputs. If you have processors at both the broker level *and* on child inputs then the broker processors will be applied *after* the child nodes processors. + +#### Fields + +##### copies + +Whatever is specified within `inputs` will be created this many times. + + +Type: `int` +Default: `1` + +##### inputs + +A list of inputs to create. + + +Type: `array` + +##### batching + +Allows you to configure a [batching policy](/api-management/stream-config#batch-policy). + + +Type: `object` + +```yml +# Examples + +batching: + byte_size: 5000 + count: 0 + period: 1s + +batching: + count: 10 + period: 1s + +batching: + check: this.contains("END BATCH") + count: 0 + period: 1m +``` + +##### batching.count + +A number of messages at which the batch should be flushed. If `0` disables count based batching. + + +Type: `int` +Default: `0` + +##### batching.byte_size + +An amount of bytes at which the batch should be flushed. If `0` disables size based batching. + + +Type: `int` +Default: `0` + +##### batching.period + +A period in which an incomplete batch should be flushed regardless of its size. + + +Type: `string` +Default: `""` + +```yml +# Examples + +period: 1s + +period: 1m + +period: 500ms +``` + +##### batching.check + +A Bloblang query that should return a boolean value indicating whether a message should end a batch. + + +Type: `string` +Default: `""` + +```yml +# Examples + +check: this.type == "end_of_transaction" +``` + +##### batching.processors + +A list of processors to apply to a batch as it is flushed. This allows you to aggregate and archive the batch however you see fit. Please note that all resulting messages are flushed as a single batch, therefore splitting the batch into smaller batches using these processors is a no-op. + + +Type: `array` + +```yml +# Examples + +processors: + - archive: + format: concatenate + +processors: + - archive: + format: lines + +processors: + - archive: + format: json_array +``` + +### Http Client + +Connects to a server and continuously performs requests for a single message. + +#### Common + +```yml +# Common config fields, showing default values +input: + label: "" + http_client: + url: "" # No default (required) + verb: GET + headers: {} + timeout: 5s + payload: "" # No default (optional) + stream: + enabled: false + reconnect: true + auto_replay_nacks: true +``` + +#### Advanced + +```yml +# All config fields, showing default values +input: + label: "" + http_client: + url: "" # No default (required) + verb: GET + headers: {} + metadata: + include_prefixes: [] + include_patterns: [] + dump_request_log_level: "" + oauth: + enabled: false + consumer_key: "" + consumer_secret: "" + access_token: "" + access_token_secret: "" + oauth2: + enabled: false + client_key: "" + client_secret: "" + token_url: "" + scopes: [] + endpoint_params: {} + basic_auth: + enabled: false + username: "" + password: "" + jwt: + enabled: false + private_key_file: "" + signing_method: "" + claims: {} + headers: {} + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + extract_headers: + include_prefixes: [] + include_patterns: [] + timeout: 5s + retry_period: 1s + max_retry_backoff: 300s + retries: 3 + backoff_on: + - 429 + drop_on: [] + successful_on: [] + proxy_url: "" # No default (optional) + payload: "" # No default (optional) + drop_empty_bodies: true + stream: + enabled: false + reconnect: true + auto_replay_nacks: true +``` + +##### Streaming + +If you enable streaming then Tyk Streams will consume the body of the response as a continuous stream of data. This allows you to consume APIs that provide long lived streamed data feeds (such as Twitter). + +##### Pagination + +This input supports interpolation functions in the `url` and `headers` fields where data from the previous successfully consumed message (if there was one) can be referenced. This can be used in order to support basic levels of pagination. + +#### Examples + +##### Basic Pagination + +Interpolation functions within the `url` and `headers` fields can be used to reference the previously consumed message, which allows simple pagination. + +```yaml +input: + http_client: + url: >- + http://api.example.com/search?query=allmyfoos&start_time=${! ( + (timestamp_unix()-300).ts_format("2006-01-02T15:04:05Z","UTC").escape_url_query() + ) }${! ("&next_token="+this.meta.next_token.not_null()) | "" } + verb: GET +``` + +{/* Update example when Tyk secrets Stream release has been performed + +```yaml +input: + http_client: + url: >- + http://api.example.com/search?query=allmyfoos&start_time=${! ( + (timestamp_unix()-300).ts_format("2006-01-02T15:04:05Z","UTC").escape_url_query() + ) }${! ("&next_token="+this.meta.next_token.not_null()) | "" } + verb: GET + rate_limit: foo_searches + # oauth2: + # enabled: true + # token_url: https://api.example.com/oauth2/token + # client_key: "${EXAMPLE_KEY}" + # client_secret: "${EXAMPLE_SECRET}" + +rate_limit_resources: + - label: foo_searches + local: + count: 1 + interval: 30s +``` */} + +#### Fields + +##### url + +The URL to connect to. + + +Type: `string` + +##### verb + +A verb to connect with + + +Type: `string` +Default: `"GET"` + +```yml +# Examples + +verb: POST + +verb: GET + +verb: DELETE +``` + +##### headers + +A map of headers to add to the request. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `object` +Default: `{}` + +```yml +# Examples + +headers: + Content-Type: application/octet-stream + traceparent: ${! tracing_span().traceparent } +``` + +##### metadata + +Specify optional matching rules to determine which metadata keys should be added to the HTTP request as headers. + + +Type: `object` + +##### metadata.include_prefixes + +Provide a list of explicit metadata key prefixes to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_prefixes: + - foo_ + - bar_ + +include_prefixes: + - kafka_ + +include_prefixes: + - content- +``` + +##### metadata.include_patterns + +Provide a list of explicit metadata key regular expression (re2) patterns to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_patterns: + - .* + +include_patterns: + - _timestamp_unix$ +``` + +##### dump_request_log_level + +Optionally set a level at which the request and response payload of each request made will be logged. + + +Type: `string` +Default: `""` +Options: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, ``. + +##### oauth + +Allows you to specify open authentication via OAuth version 1. + + +Type: `object` + +##### oauth.enabled + +Whether to use OAuth version 1 in requests. + + +Type: `bool` +Default: `false` + +##### oauth.consumer_key + +A value used to identify the client to the service provider. + + +Type: `string` +Default: `""` + +##### oauth.consumer_secret + +A secret used to establish ownership of the consumer key. + + +Type: `string` +Default: `""` + +##### oauth.access_token + +A value used to gain access to the protected resources on behalf of the user. + + +Type: `string` +Default: `""` + +##### oauth.access_token_secret + +A secret provided in order to establish ownership of a given access token. + + +Type: `string` +Default: `""` + +##### oauth2 + +Allows you to specify open authentication via OAuth version 2 using the client credentials token flow. + + +Type: `object` + +##### oauth2.enabled + +Whether to use OAuth version 2 in requests. + + +Type: `bool` +Default: `false` + +##### oauth2.client_key + +A value used to identify the client to the token provider. + + +Type: `string` +Default: `""` + +##### oauth2.client_secret + +A secret used to establish ownership of the client key. + + +Type: `string` +Default: `""` + +##### oauth2.token_url + +The URL of the token provider. + + +Type: `string` +Default: `""` + +##### oauth2.scopes + +A list of optional requested permissions. + + +Type: `array` +Default: `[]` + +##### oauth2.endpoint_params + +A list of optional endpoint parameters, values should be arrays of strings. + + +Type: `object` +Default: `{}` + +```yml +# Examples + +endpoint_params: + bar: + - woof + foo: + - meow + - quack +``` + +##### basic_auth + +Allows you to specify basic authentication. + + +Type: `object` + +##### basic_auth.enabled + +Whether to use basic authentication in requests. + + +Type: `bool` +Default: `false` + +##### basic_auth.username + +A username to authenticate as. + + +Type: `string` +Default: `""` + +##### basic_auth.password + +A password to authenticate with. + + +Type: `string` +Default: `""` + +##### jwt + +Allows you to specify JWT authentication. + + +Type: `object` + +##### jwt.enabled + +Whether to use JWT authentication in requests. + + +Type: `bool` +Default: `false` + +##### jwt.private_key_file + +A file with the PEM encoded via PKCS1 or PKCS8 as private key. + + +Type: `string` +Default: `""` + +##### jwt.signing_method + +A method used to sign the token such as RS256, RS384, RS512 or EdDSA. + + +Type: `string` +Default: `""` + +##### jwt.claims + +A value used to identify the claims that issued the JWT. + + +Type: `object` +Default: `{}` + +##### jwt.headers + +Add optional key/value headers to the JWT. + + +Type: `object` +Default: `{}` + +##### tls + +Custom TLS settings can be used to override system defaults. + + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're seeing the error message `local error: tls: no renegotiation`. + + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, but not both. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +client_certs: + - cert: foo + key: bar + +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in PKCS#1 or PKCS#8 format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. + +Type: `string` +Default: `""` + +```yml +# Examples + +password: foo +``` + +##### extract_headers + +Specify which response headers should be added to resulting messages as metadata. Header keys are lowercased before matching, so ensure that your patterns target lowercased versions of the header keys that you expect. + + +Type: `object` + +##### extract_headers.include_prefixes + +Provide a list of explicit metadata key prefixes to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_prefixes: + - foo_ + - bar_ + +include_prefixes: + - kafka_ + +include_prefixes: + - content- +``` + +##### extract_headers.include_patterns + +Provide a list of explicit metadata key regular expression (re2) patterns to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_patterns: + - .* + +include_patterns: + - _timestamp_unix$ +``` + +##### timeout + +A static timeout to apply to requests. + + +Type: `string` +Default: `"5s"` + +##### retry_period + +The base period to wait between failed requests. + + +Type: `string` +Default: `"1s"` + +##### max_retry_backoff + +The maximum period to wait between failed requests. + + +Type: `string` +Default: `"300s"` + +##### retries + +The maximum number of retry attempts to make. + + +Type: `int` +Default: `3` + +##### backoff_on + +A list of status codes whereby the request should be considered to have failed and retries should be attempted, but the period between them should be increased gradually. + + +Type: `array` +Default: `[429]` + +##### drop_on + +A list of status codes whereby the request should be considered to have failed but retries should not be attempted. This is useful for preventing wasted retries for requests that will never succeed. Note that with these status codes the *request* is dropped, but *message* that caused the request will not be dropped. + + +Type: `array` +Default: `[]` + +##### successful_on + +A list of status codes whereby the attempt should be considered successful, this is useful for dropping requests that return non-2XX codes indicating that the message has been dealt with, such as a 303 See Other or a 409 Conflict. All 2XX codes are considered successful unless they are present within `backoff_on` or `drop_on`, regardless of this field. + + +Type: `array` +Default: `[]` + +##### proxy_url + +An optional HTTP proxy URL. + + +Type: `string` + +##### payload + +An optional payload to deliver for each request. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + + +Type: `string` + +##### drop_empty_bodies + +Whether empty payloads received from the target server should be dropped. + + +Type: `bool` +Default: `true` + +##### stream + +Allows you to set streaming mode, where requests are kept open and messages are processed line-by-line. + + +Type: `object` + +##### stream.enabled + +Enables streaming mode. + + +Type: `bool` +Default: `false` + +##### stream.reconnect + +Sets whether to re-establish the connection once it is lost. + + +Type: `bool` +Default: `true` + + +##### auto_replay_nacks + +Whether messages that are rejected (nacked) at the output level should be automatically replayed indefinitely, eventually resulting in back pressure if the cause of the rejections is persistent. If set to `false` these messages will instead be deleted. Disabling auto replays can greatly improve memory efficiency of high throughput streams as the original shape of the data can be discarded immediately upon consumption and mutation. + + +Type: `bool` +Default: `true` + +### HTTP Server + +Receive messages POSTed over HTTP(S). HTTP 2.0 is supported when using TLS, which is enabled when key and cert files are specified. + +#### Common + +```yml +# Common config fields, showing default values +input: + label: "" + http_server: + address: "" + path: /post + ws_path: /post/ws + allowed_verbs: + - POST + timeout: 5s +``` + +#### Advanced + +```yml +# All config fields, showing default values +input: + label: "" + http_server: + address: "" + path: /post + ws_path: /post/ws + ws_welcome_message: "" + allowed_verbs: + - POST + timeout: 5s + cert_file: "" + key_file: "" + cors: + enabled: false + allowed_origins: [] + sync_response: + status: "200" + headers: + Content-Type: application/octet-stream + metadata_headers: + include_prefixes: [] + include_patterns: [] +``` + +{/* TODO add link to service wide HTTP server If the `address` config field is left blank the [service-wide HTTP server](/docs/components/http/about) will be used. */} + +{/* TODO add rate limit The field `rate_limit` allows you to specify an optional [`rate_limit` resource](/docs/components/rate_limits/about), which will be applied to each HTTP request made and each websocket payload received. + +When the rate limit is breached HTTP requests will have a 429 response returned with a Retry-After header. Websocket payloads will be dropped and an optional response payload will be sent as per `ws_rate_limit_message`. */} + +##### Responses + +{/* TODO describe how to use synchronous responses when avail: + +It's possible to return a response for each message received using synchronous responses. When doing so you can customise headers with the `sync_response` field `headers`, which can also use function interpolation in the value based on the response message contents. */} + + +##### Endpoints + +The following fields specify endpoints that are registered for sending messages, and support path parameters of the form `/{foo}`, which are added to ingested messages as metadata. A path ending in `/` will match against all extensions of that path: + +###### path (defaults to `/post`) + +This endpoint expects POST requests where the entire request body is consumed as a single message. + +If the request contains a multipart `content-type` header as per [rfc1341](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html) then the multiple parts are consumed as a batch of messages, where each body part is a message of the batch. + +###### ws_path (defaults to `/post/ws`) + +Creates a websocket connection, where payloads received on the socket are passed through the pipeline as a batch of one message. + +Please note that components within a Tyk Streams config will register their respective endpoints in a non-deterministic order. This means that establishing precedence of endpoints that are registered via multiple `http_server` inputs or outputs (either within brokers or from cohabiting streams) is not possible in a predictable way. + +This ambiguity makes it difficult to ensure that paths which are both a subset of a path registered by a separate component, and end in a slash (`/`) and will therefore match against all extensions of that path, do not prevent the more specific path from matching against requests. + +It is therefore recommended that you ensure paths of separate components do not collide unless they are explicitly non-competing. + +For example, if you were to deploy two separate `http_server` inputs, one with a path `/foo/` and the other with a path `/foo/bar`, it would not be possible to ensure that the path `/foo/` does not swallow requests made to `/foo/bar`. + +You may specify an optional `ws_welcome_message`, which is a static payload to be sent to all clients once a websocket connection is first established. + +##### Metadata + +This input adds the following metadata fields to each message: + +``` text +- http_server_user_agent +- http_server_request_path +- http_server_verb +- http_server_remote_ip +- All headers (only first values are taken) +- All query parameters +- All path parameters +- All cookies +``` + +If HTTPS is enabled, the following fields are added as well: +``` text +- http_server_tls_version +- http_server_tls_subject +- http_server_tls_cipher_suite +``` + +{/* TODO: when interpolaion supported +You can access these metadata fields using interpolation functions. */} + +#### Examples + + +##### Path Switching + +This example shows an `http_server` input that captures all requests and processes them by switching on that path: + +```yaml +input: + http_server: + path: / + allowed_verbs: [ GET, POST ] + sync_response: + headers: + Content-Type: application/json + + processors: + - switch: + - check: '@http_server_request_path == "/foo"' + processors: + - mapping: | + root.title = "You Got Fooed!" + root.result = content().string().uppercase() + + - check: '@http_server_request_path == "/bar"' + processors: + - mapping: 'root.title = "Bar Is Slow"' + - sleep: # Simulate a slow endpoint + duration: 1s +``` + +##### Mock OAuth 2.0 Server + +This example shows an `http_server` input that mocks an OAuth 2.0 Client Credentials flow server at the endpoint `/oauth2_test`: + +```yaml +input: + http_server: + path: /oauth2_test + allowed_verbs: [ GET, POST ] + sync_response: + headers: + Content-Type: application/json + + processors: + - log: + message: "Received request" + level: INFO + fields_mapping: | + root = @ + root.body = content().string() + + - mapping: | + root.access_token = "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3" + root.token_type = "Bearer" + root.expires_in = 3600 + + - sync_response: {} + - mapping: 'root = deleted()' +``` + +#### Fields + +##### address + +An alternative address to host from. If left empty the service wide address is used. + + +Type: `string` +Default: `""` + +##### path + +The endpoint path to listen for POST requests. + + +Type: `string` +Default: `"/post"` + +##### ws_path + +The endpoint path to create websocket connections from. + + +Type: `string` +Default: `"/post/ws"` + +##### ws_welcome_message + +An optional message to deliver to fresh websocket connections. + + +Type: `string` +Default: `""` + +##### allowed_verbs + +An array of verbs that are allowed for the `path` endpoint. + + +Type: `array` +Default: `["POST"]` +Requires version 3.33.0 or newer + +##### timeout + +Timeout for requests. If a consumed messages takes longer than this to be delivered the connection is closed, but the message may still be delivered. + + +Type: `string` +Default: `"5s"` + +{/* TODO add rate limit ##### rate_limit + +An optional [rate limit](/docs/components/rate_limits/about) to throttle requests by. */} + + +Type: `string` +Default: `""` + +##### cert_file + +Enable TLS by specifying a certificate and key file. Only valid with a custom `address`. + + +Type: `string` +Default: `""` + +##### key_file + +Enable TLS by specifying a certificate and key file. Only valid with a custom `address`. + + +Type: `string` +Default: `""` + +##### cors + +Adds Cross-Origin Resource Sharing headers. Only valid with a custom `address`. + + +Type: `object` +Requires version 3.63.0 or newer + +##### cors.enabled + +Whether to allow CORS requests. + + +Type: `bool` +Default: `false` + +##### cors.allowed_origins + +An explicit list of origins that are allowed for CORS requests. + + +Type: `array` +Default: `[]` + +##### sync_response + +{/* TODO add links to synchronous responses */} +Customize messages returned via synchronous responses. + + +Type: `object` + +##### sync_response.status + +Specify the status code to return with synchronous responses. This is a string value, which allows you to customize it based on resulting payloads and their metadata. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + + +Type: `string` +Default: `"200"` + +```yml +# Examples + +status: ${! json("status") } + +status: ${! meta("status") } +``` + +##### sync_response.headers + +Specify headers to return with synchronous responses. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + + +Type: `object` +Default: `{"Content-Type":"application/octet-stream"}` + +##### sync_response.metadata_headers + +Specify criteria for which metadata values are added to the response as headers. + + +Type: `object` + +##### sync_response.metadata_headers.include_prefixes + +Provide a list of explicit metadata key prefixes to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_prefixes: + - foo_ + - bar_ + +include_prefixes: + - kafka_ + +include_prefixes: + - content- +``` + +##### sync_response.metadata_headers.include_patterns + +Provide a list of explicit metadata key regular expression (re2) patterns to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_patterns: + - .* + +include_patterns: + - _timestamp_unix$ +``` + +### Kafka + +Connects to Kafka brokers and consumes one or more topics. + +#### Common + +```yml +# Common config fields, showing default values +input: + label: "" + kafka: + addresses: [] # No default (required) + topics: [] # No default (required) + target_version: 2.1.0 # No default (optional) + consumer_group: "" + checkpoint_limit: 1024 + auto_replay_nacks: true +``` + +#### Advanced + +```yml +# All config fields, showing default values +input: + label: "" + kafka: + addresses: [] # No default (required) + topics: [] # No default (required) + target_version: 2.1.0 # No default (optional) + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + sasl: + mechanism: none + user: "" + password: "" + access_token: "" + token_cache: "" + token_key: "" + consumer_group: "" + client_id: tyk + rack_id: "" + start_from_oldest: true + checkpoint_limit: 1024 + auto_replay_nacks: true + commit_period: 1s + max_processing_period: 100ms + extract_tracing_map: root = @ # No default (optional) + group: + session_timeout: 10s + heartbeat_interval: 3s + rebalance_timeout: 60s + fetch_buffer_cap: 256 + multi_header: false + batching: + count: 0 + byte_size: 0 + period: "" + check: "" + processors: [] # No default (optional) +``` + +Offsets are managed within Kafka under the specified consumer group, and partitions for each topic are automatically balanced across members of the consumer group. + +The Kafka input allows parallel processing of messages from different topic partitions, and messages of the same topic partition are processed with a maximum parallelism determined by the field [checkpoint_limit](#checkpoint_limit). + +In order to enforce ordered processing of partition messages set the [checkpoint_limit](#checkpoint_limit) to `1` and this will force partitions to be processed in lock-step, where a message will only be processed once the prior message is delivered. + +Batching messages before processing can be enabled using the [batching](#batching) field, and this batching is performed per-partition such that messages of a batch will always originate from the same partition. This batching mechanism is capable of creating batches of greater size than the [checkpoint_limit](#checkpoint_limit), in which case the next batch will only be created upon delivery of the current one. + +##### Metadata + +This input adds the following metadata fields to each message: + +``` text +- kafka_key +- kafka_topic +- kafka_partition +- kafka_offset +- kafka_lag +- kafka_timestamp_unix +- kafka_tombstone_message +- All existing message headers (version 0.11+) +``` + +The field `kafka_lag` is the calculated difference between the high water mark offset of the partition at the time of ingestion and the current message offset. + +{/* TODO: when interpolation supported +You can access these metadata fields using function interpolation. */} + +##### Ordering + +By default messages of a topic partition can be processed in parallel, up to a limit determined by the field `checkpoint_limit`. However, if strict ordered processing is required then this value must be set to 1 in order to process shard messages in lock-step. When doing so it is recommended that you perform batching at this component for performance as it will not be possible to batch lock-stepped messages at the output level. + +##### Troubleshooting + +- I'm seeing logs that report `Failed to connect to kafka: kafka: client has run out of available brokers to talk to (Is your cluster reachable?)`, but the brokers are definitely reachable. + +Unfortunately this error message will appear for a wide range of connection problems even when the broker endpoint can be reached. Double check your authentication configuration and also ensure that you have [enabled TLS](#tlsenabled) if applicable. + +#### Fields + +##### addresses + +A list of broker addresses to connect to. If an item of the list contains commas it will be expanded into multiple addresses. + + +Type: `array` + +```yml +# Examples + +addresses: + - localhost:9092 + +addresses: + - localhost:9041,localhost:9042 + +addresses: + - localhost:9041 + - localhost:9042 +``` + +##### topics + +A list of topics to consume from. Multiple comma separated topics can be listed in a single element. Partitions are automatically distributed across consumers of a topic. Alternatively, it's possible to specify explicit partitions to consume from with a colon after the topic name, e.g. `foo:0` would consume the partition 0 of the topic foo. This syntax supports ranges, e.g. `foo:0-10` would consume partitions 0 through to 10 inclusive. + + +Type: `array` +Requires version 3.33.0 or newer + +```yml +# Examples + +topics: + - foo + - bar + +topics: + - foo,bar + +topics: + - foo:0 + - bar:1 + - bar:3 + +topics: + - foo:0,bar:1,bar:3 + +topics: + - foo:0-5 +``` + +##### target_version + +The version of the Kafka protocol to use. This limits the capabilities used by the client and should ideally match the version of your brokers. Defaults to the oldest supported stable version. + + +Type: `string` + +```yml +# Examples + +target_version: 2.1.0 + +target_version: 3.1.0 +``` + +##### tls + +Custom TLS settings can be used to override system defaults. + + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're seeing the error message `local error: tls: no renegotiation`. + + +Type: `bool` +Default: `false` +Requires version 3.45.0 or newer + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, but not both. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +client_certs: + - cert: foo + key: bar + +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in PKCS#1 or PKCS#8 format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. + + +Type: `string` +Default: `""` + +```yml +# Example + +password: foo +``` + +{/* When Tyk streams with secrets released include this in above example => password: ${KEY_PASSWORD} */} + +##### sasl + +Enables SASL authentication. + + +Type: `object` + +##### sasl.mechanism + +The SASL authentication mechanism, if left empty SASL authentication is not used. + + +Type: `string` +Default: `"none"` + +| Option | Summary | +| :--- | :--- | +| `OAUTHBEARER` | OAuth Bearer based authentication. | +| `PLAIN` | Plain text authentication. NOTE: When using plain text auth it is extremely likely that you'll also need to [enable TLS](#tlsenabled). | +| `SCRAM-SHA-256` | Authentication using the SCRAM-SHA-256 mechanism. | +| `SCRAM-SHA-512` | Authentication using the SCRAM-SHA-512 mechanism. | +| `none` | Default, no SASL authentication. | + + +##### sasl.user + +A PLAIN username. It is recommended that you use environment variables to populate this field. + + +Type: `string` +Default: `""` + +```yml +# Examples + +user: ${USER} +``` + +##### sasl.password + +A PLAIN password. It is recommended that you use environment variables to populate this field. + + +Type: `string` +Default: `""` + +```yml +# Examples + +password: ${PASSWORD} +``` + +##### sasl.access_token + +A static OAUTHBEARER access token + + +Type: `string` +Default: `""` + +{/* TODO add ##### sasl.token_cache + +Instead of using a static `access_token` allows you to query a [`cache`](/docs/components/caches/about) resource to fetch OAUTHBEARER tokens from */} + + +Type: `string` +Default: `""` + +##### sasl.token_key + +Required when using a `token_cache`, the key to query the cache with for tokens. + + +Type: `string` +Default: `""` + +##### consumer_group + +An identifier for the consumer group of the connection. This field can be explicitly made empty in order to disable stored offsets for the consumed topic partitions. + + +Type: `string` +Default: `""` + +##### client_id + +An identifier for the client connection. + + +Type: `string` +Default: `"tyk"` + +##### rack_id + +A rack identifier for this client. + + +Type: `string` +Default: `""` + +##### start_from_oldest + +Determines whether to consume from the oldest available offset, otherwise messages are consumed from the latest offset. The setting is applied when creating a new consumer group or the saved offset no longer exists. + + +Type: `bool` +Default: `true` + +##### checkpoint_limit + +The maximum number of messages of the same topic and partition that can be processed at a given time. Increasing this limit enables parallel processing and batching at the output level to work on individual partitions. Any given offset will not be committed unless all messages under that offset are delivered in order to preserve at least once delivery guarantees. + + +Type: `int` +Default: `1024` +Requires version 3.33.0 or newer + +##### auto_replay_nacks + +Whether messages that are rejected (nacked) at the output level should be automatically replayed indefinitely, eventually resulting in back pressure if the cause of the rejections is persistent. If set to `false` these messages will instead be deleted. Disabling auto replays can greatly improve memory efficiency of high throughput streams as the original shape of the data can be discarded immediately upon consumption and mutation. + + +Type: `bool` +Default: `true` + +##### commit_period + +The period of time between each commit of the current partition offsets. Offsets are always committed during shutdown. + + +Type: `string` +Default: `"1s"` + +##### max_processing_period + +A maximum estimate for the time taken to process a message, this is used for tuning consumer group synchronization. + + +Type: `string` +Default: `"100ms"` + +##### extract_tracing_map + +A Bloblang mapping that attempts to extract an object containing tracing propagation information, which will then be used as the root tracing span for the message. The specification of the extracted fields must match the format used by the service wide tracer. + + +Type: `string` +Requires version 3.45.0 or newer + +```yml +# Examples + +extract_tracing_map: root = @ + +extract_tracing_map: root = this.meta.span +``` + +##### group + +Tuning parameters for consumer group synchronization. + + +Type: `object` + +##### group.session_timeout + +A period after which a consumer of the group is kicked after no heartbeats. + + +Type: `string` +Default: `"10s"` + +##### group.heartbeat_interval + +A period in which heartbeats should be sent out. + + +Type: `string` +Default: `"3s"` + +##### group.rebalance_timeout + +A period after which rebalancing is abandoned if unresolved. + + +Type: `string` +Default: `"60s"` + +##### fetch_buffer_cap + +The maximum number of unprocessed messages to fetch at a given time. + + +Type: `int` +Default: `256` + +##### multi_header + +Decode headers into lists to allow handling of multiple values with the same key + + +Type: `bool` +Default: `false` + +##### batching + +Allows you to configure a [batching policy](/api-management/stream-config#batch-policy). + +Type: `object` + +```yml +# Examples + +batching: + byte_size: 5000 + count: 0 + period: 1s + +batching: + count: 10 + period: 1s + +batching: + check: this.contains("END BATCH") + count: 0 + period: 1m +``` + +##### batching.count + +A number of messages at which the batch should be flushed. If `0` disables count based batching. + + +Type: `int` +Default: `0` + +##### batching.byte_size + +An amount of bytes at which the batch should be flushed. If `0` disables size based batching. + + +Type: `int` +Default: `0` + +##### batching.period + +A period in which an incomplete batch should be flushed regardless of its size. + + +Type: `string` +Default: `""` + +```yml +# Examples + +period: 1s + +period: 1m + +period: 500ms +``` + +##### batching.check + +A Bloblang query that should return a boolean value indicating whether a message should end a batch. + +Type: `string` +Default: `""` + +```yml +# Examples + +check: this.type == "end_of_transaction" +``` + +##### batching.processors + +A list of processors to apply to a batch as it is flushed. This allows you to aggregate and archive the batch however you see fit. Please note that all resulting messages are flushed as a single batch, therefore splitting the batch into smaller batches using these processors is a no-op. + + +Type: `array` + +```yml +# Examples + +processors: + - archive: + format: concatenate + +processors: + - archive: + format: lines + +processors: + - archive: + format: json_array +``` + +### MQTT +Subscribe to topics on MQTT brokers. + +#### Common +```yml +# Common config fields, showing default values +input: + label: "" + mqtt: + urls: [] # No default (required) + client_id: "" + connect_timeout: 30s + topics: [] # No default (required) + auto_replay_nacks: true +``` + +#### Advanced +```yml +# All config fields, showing default values +input: + label: "" + mqtt: + urls: [] # No default (required) + client_id: "" + dynamic_client_id_suffix: "" # No default (optional) + connect_timeout: 30s + will: + enabled: false + qos: 0 + retained: false + topic: "" + payload: "" + user: "" + password: "" + keepalive: 30 + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + topics: [] # No default (required) + qos: 1 + clean_session: true + auto_replay_nacks: true +``` + +#### Metadata + +This input adds the following metadata fields to each message: + +``` text +- mqtt_duplicate +- mqtt_qos +- mqtt_retained +- mqtt_topic +- mqtt_message_id +``` + +You can access these metadata fields using function interpolation. + +#### Fields + +##### urls + +A list of URLs to connect to. If an item of the list contains commas it will be expanded into multiple URLs. + + +Type: `array` + +```yml +# Examples + +urls: + - tcp://localhost:1883 +``` + +##### client_id + +An identifier for the client connection. + + +Type: `string` +Default: `""` + +##### dynamic_client_id_suffix + +Append a dynamically generated suffix to the specified `client_id` on each run of the pipeline. This can be useful when clustering Streams producers. + + +Type: `string` + +| Option | Summary | +| :--- | :--- | +| `nanoid` | append a nanoid of length 21 characters | + + +##### connect_timeout + +The maximum amount of time to wait in order to establish a connection before the attempt is abandoned. + + +Type: `string` +Default: `"30s"` +Requires version 1.0.0 or newer + +```yml +# Examples + +connect_timeout: 1s + +connect_timeout: 500ms +``` + +##### will + +Set last will message in case of Streams failure + + +Type: `object` + +##### will.enabled + +Whether to enable last will messages. + + +Type: `bool` +Default: `false` + +##### will.qos + +Set QoS for last will message. Valid values are: 0, 1, 2. + + +Type: `int` +Default: `0` + +##### will.retained + +Set retained for last will message. + + +Type: `bool` +Default: `false` + +##### will.topic + +Set topic for last will message. + + +Type: `string` +Default: `""` + +##### will.payload + +Set payload for last will message. + + +Type: `string` +Default: `""` + +##### user + +A username to connect with. + + +Type: `string` +Default: `""` + +##### password + +A password to connect with. + + +Type: `string` +Default: `""` + +##### keepalive + +Max seconds of inactivity before a keepalive message is sent. + + +Type: `int` +Default: `30` + +##### tls + +Custom TLS settings can be used to override system defaults. + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're seeing the error message `local error: tls: no renegotiation`. + + +Type: `bool` +Default: `false` +Requires version 1.0.0 or newer + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, but not both. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +client_certs: + - cert: foo + key: bar + +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in PKCS#1 or PKCS#8 format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. + + +Type: `string` +Default: `""` + +```yml +# Examples + +password: foo + +password: ${KEY_PASSWORD} +``` + +##### topics + +A list of topics to consume from. + + +Type: `array` + +##### qos + +The level of delivery guarantee to enforce. Has options 0, 1, 2. + + +Type: `int` +Default: `1` + +##### clean_session + +Set whether the connection is non-persistent. + + +Type: `bool` +Default: `true` + +##### auto_replay_nacks + +Whether messages that are rejected (nacked) at the output level should be automatically replayed indefinitely, eventually resulting in back pressure if the cause of the rejections is persistent. If set to `false` these messages will instead be deleted. Disabling auto replays can greatly improve memory efficiency of high throughput streams as the original shape of the data can be discarded immediately upon consumption and mutation. + +Type: `bool` +Default: `true` + + +### amqp_0_9 + +Connects to an AMQP (0.91) queue. AMQP is a messaging protocol used by various message brokers, including RabbitMQ. + +#### Common + +```yaml +# Common config fields, showing default values +input: + label: "" + amqp_0_9: + urls: [] # No default (required) + queue: "" # No default (required) + consumer_tag: "" + prefetch_count: 10 +``` + +#### Advanced + +```yaml +# All config fields, showing default values +input: + label: "" + amqp_0_9: + urls: [] # No default (required) + queue: "" # No default (required) + queue_declare: + enabled: false + durable: true + auto_delete: false + bindings_declare: [] # No default (optional) + consumer_tag: "" + auto_ack: false + nack_reject_patterns: [] + prefetch_count: 10 + prefetch_size: 0 + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] +``` + +TLS is automatic when connecting to an `amqps` URL, but custom settings can be enabled in the `tls` section. + +#### Metadata + +This input adds the following metadata fields to each message: + +``` +- amqp_content_type +- amqp_content_encoding +- amqp_delivery_mode +- amqp_priority +- amqp_correlation_id +- amqp_reply_to +- amqp_expiration +- amqp_message_id +- amqp_timestamp +- amqp_type +- amqp_user_id +- amqp_app_id +- amqp_consumer_tag +- amqp_delivery_tag +- amqp_redelivered +- amqp_exchange +- amqp_routing_key +``` + +All existing message headers, including nested headers prefixed with the key of their respective parent, can be added. + +#### Fields + +##### urls + +A list of URLs to connect to. The first URL to successfully establish a connection will be used until the connection is closed. +If an item of the list contains commas, it will be expanded into multiple URLs. + +Type: `array` + +```yaml +# Examples +urls: + - amqp://guest:guest@127.0.0.1:5672/ +urls: + - amqp://127.0.0.1:5672/,amqp://127.0.0.2:5672/ +urls: + - amqp://127.0.0.1:5672/ + - amqp://127.0.0.2:5672/ +``` + +##### queue + +An AMQP queue to consume from. + +Type: `string` + +##### queue_declare + +Allows you to passively declare the target queue. If the queue already exists, then the declaration passively verifies that +they match the target fields. + +type: `object` + +##### queue_declare.enabled + +Whether to enable queue declaration. + +Type: `bool` +Default: `false` + +##### queue_declare.durable + +Whether the declared queue is durable. + +Type: `bool` +Default: `true` + +##### queue_declare.auto_delete + +Whether the declared queue will auto-delete. + +Type: `bool` +Default: `false` + +##### bindings_declare + +Allows you to passively declare bindings for the target queue. + +Type: `array` + +```yaml +# Examples +bindings_declare: + - exchange: foo + key: bar +``` + +##### bindings_declare[].exchange + +The exchange of the declared binding. + +Type: `string` +Default: `""` + +##### bindings_declare[].key + +The key of the declared binding. + +Type: `string` +Default: `""` + +##### consumer_tag + +A consumer tag. + +Type: `string` +Default: `""` + +##### auto_ack + +Acknowledge messages automatically as they are consumed rather than waiting for acknowledgments from downstream. +This can improve throughput and prevent the pipeline from blocking but at the cost of eliminating delivery guarantees. + +Type: `bool` +Default: `false` + +##### nack_reject_patterns + +A list of regular expression patterns whereby if a message that has failed to be delivered by Bento has an error that matches +it will be dropped (or delivered to a dead-letter queue if one exists). By default, failed messages are nacked with requeue enabled. + +Type: `array` +Default: `[]` + +```yaml +# Examples +nack_reject_patterns: + - ^reject me please:.+$ +``` + +##### prefetch_count + +The maximum number of pending messages to have consumed at a time. + +Type: `int` +Default: `10` + +##### prefetch_size + +The maximum number of pending messages measured in bytes to have consumed at a time. + +Type: `int` +Default: `0` + +##### tls + +Custom TLS settings can be used to override system defaults. + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're getting the error message +`local error: tls: no renegotiation.` + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, +to possible intermediate signing certificates, to the host certificate. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing +a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host +certificate. + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, +but not both. + +Type: `array` +Default: `[]` + +```yaml +# Examples +client_certs: + - cert: foo + key: bar +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: "" + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in *PKCS#1* or *PKCS#8* format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not +supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an +attacker recover the plaintext. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +password: foo +``` + + + +### amqp_1 + +Reads messages from an AMQP (1.0) server. + +#### Common + +```yaml +# Common config fields, showing default values +input: + label: "" + amqp_1: + urls: [] # No default (optional) + source_address: /foo # No default (required) +``` + +#### Advanced + +```yaml +# All config fields, showing default values +input: + label: "" + amqp_1: + urls: [] # No default (optional) + source_address: /foo # No default (required) + azure_renew_lock: false + read_header: false + credit: 64 + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + sasl: + mechanism: none + user: "" + password: "" +``` + +#### Metadata + +This input adds the following metadata fields to each message: + +``` +- amqp_content_type +- amqp_content_encoding +- amqp_creation_time +- All string typed message annotations +``` + +You can access these metadata fields using function interpolation. + +By setting `read_header` to `true`, additional message header properties will be added to each message: + +``` +- amqp_durable +- amqp_priority +- amqp_ttl +- amqp_first_acquirer +- amqp_delivery_count +``` + +#### Performance + +This input benefits from receiving multiple messages in flight in parallel for improved performance. You can tune the max +number of in flight messages with the field `credit`. + +#### Fields + +##### urls + +A list of URLs to connect to. The first URL to successfully establish a connection will be used until the connection is closed. +If an item of the list contains commas it will be expanded into multiple URLs. + +Type: `array` + +```yaml +# Examples +urls: + - amqp://guest:guest@127.0.0.1:5672/ +urls: + - amqp://127.0.0.1:5672/,amqp://127.0.0.2:5672/ +urls: + - amqp://127.0.0.1:5672/ + - amqp://127.0.0.2:5672/ +``` + +##### source_address + +The source address to consume from. + +Type: `string` + +```yaml +# Examples +source_address: /foo +source_address: queue:/bar +source_address: topic:/baz +``` + +##### azure_renew_lock + +**Experimental:** Azure service bus specific option to renew lock if processing takes more then configured lock time. + +Type: `bool` +Default: `false` + +##### read_header + +Read additional message header fields into `amqp_*` metadata properties. + +Type: `bool` +Default: `false` + +##### credit + +Specifies the maximum number of unacknowledged messages the sender can transmit. Once this limit is reached, no more messages +will arrive until messages are acknowledged and settled. + +Type: `int` +Default: `64` + + +##### tls + +Custom TLS settings can be used to override system defaults. + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're getting the error message +`local error: tls: no renegotiation.` + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, +to possible intermediate signing certificates, to the host certificate. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing +a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host +certificate. + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, +but not both. + +Type: `array` +Default: `[]` + +```yaml +# Examples +client_certs: + - cert: foo + key: bar +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: "" + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in *PKCS#1* or *PKCS#8* format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not +supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an +attacker recover the plaintext. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +password: foo +``` + +##### sasl + +Enables SASL authentication. + +Type: `object` + +##### sasl.mechanism + +The SASL authentication mechanism to use. + +Type: `string` +Default: `"none"` + +| Option | Summary | +| :----------- | :-------------------------------------- | +| anonymous | Anonymous SASL authentication. | +| none | No SASL based authentication. | +| plain | Plain text SASL authentication. | + + +##### sasl.user + +A SASL plain text username. It is recommended that you use environment variables to populate this field. + +Type: `string` +Default: `""` + +```yaml +# Examples +user: ${USER} +``` + +##### sasl.password + +A SASL plain text password. It is recommended that you use environment variables to populate this field. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + + +```yaml +# Examples +password: ${PASSWORD} +``` + +## Outputs + +### Overview + +An output is a sink where we wish to send our consumed data after applying an optional array of [processors](/api-management/stream-config#overview-3). Only one output is configured at the root of a Tyk Streams config. However, the output can be a [broker](/api-management/stream-config#broker-1) which combines multiple outputs under a chosen brokering pattern. + +An output config section looks like this: + +```yaml +outout: + label: my_kafka_output + + kafka: + addresses: [ localhost:9092 ] + topic: "foobar" + + # Optional list of processing steps + processors: + - avro: + operator: from_json +``` + +#### Labels + +Outputs have an optional field `label` that can uniquely identify them in observability data such as logs. + +{/* TODO replace with this paragraph when determine if product supports metrics + +Outputs have an optional field `label` that can uniquely identify them in observability data such as metrics and logs. This can be useful when running configs with multiple outputs, otherwise their metrics labels will be generated based on their composition. For more information check out the [metrics documentation][metrics.about]. */} + +### Broker + +Allows you to route messages to multiple child outputs using a range of brokering [patterns](#patterns). + +#### Common + +```yml +# Common config fields, showing default values +output: + label: "" + broker: + pattern: fan_out + outputs: [] # No default (required) + batching: + count: 0 + byte_size: 0 + period: "" + check: "" +``` + +#### Advanced + +```yml +# All config fields, showing default values +output: + label: "" + broker: + copies: 1 + pattern: fan_out + outputs: [] # No default (required) + batching: + count: 0 + byte_size: 0 + period: "" + check: "" + processors: [] # No default (optional) +``` + +Processors can be listed to apply across individual outputs or all outputs: + +```yaml +output: + broker: + pattern: fan_out + outputs: + - resource: foo + - resource: bar + # Processors only applied to messages sent to bar. + processors: + - resource: bar_processor + + # Processors applied to messages sent to all brokered outputs. + processors: + - resource: general_processor +``` + +#### Fields + +##### copies + +The number of copies of each configured output to spawn. + + +Type: `int` +Default: `1` + +##### pattern + +The brokering pattern to use. + + +Type: `string` +Default: `"fan_out"` +Options: `fan_out`, `fan_out_fail_fast`, `fan_out_sequential`, `fan_out_sequential_fail_fast`, `round_robin`, `greedy`. + +##### outputs + +A list of child outputs to broker. + + +Type: `array` + +##### batching + +Allows you to configure a [batching policy](/api-management/stream-config#batch-policy). + + +Type: `object` + +```yml +# Examples + +batching: + byte_size: 5000 + count: 0 + period: 1s + +batching: + count: 10 + period: 1s + +batching: + check: this.contains("END BATCH") + count: 0 + period: 1m +``` + +##### batching.count + +A number of messages at which the batch should be flushed. If `0` disables count based batching. + + +Type: `int` +Default: `0` + +##### batching.byte_size + +An amount of bytes at which the batch should be flushed. If `0` disables size based batching. + + +Type: `int` +Default: `0` + +##### batching.period + +A period in which an incomplete batch should be flushed regardless of its size. + + +Type: `string` +Default: `""` + +```yml +# Examples + +period: 1s + +period: 1m + +period: 500ms +``` + +##### batching.check + +A Bloblang query that should return a boolean value indicating whether a message should end a batch. + + +Type: `string` +Default: `""` + +```yml +# Examples + +check: this.type == "end_of_transaction" +``` + +##### batching.processors + +A list of processors to apply to a batch as it is flushed. This allows you to aggregate and archive the batch however you see fit. Please note that all resulting messages are flushed as a single batch, therefore splitting the batch into smaller batches using these processors is a no-op. + + +Type: `array` + +```yml +# Examples + +processors: + - archive: + format: concatenate + +processors: + - archive: + format: lines + +processors: + - archive: + format: json_array +``` + +#### Patterns + +The broker pattern determines the way in which messages are allocated and can be chosen from the following: + +##### fan_out + +With the fan out pattern all outputs will be sent every message that passes through Tyk Streams in parallel. + +If an output applies back pressure it will block all subsequent messages, and if an output fails to send a message it will be retried continuously until completion or service shut down. This mechanism is in place in order to prevent one bad output from causing a larger retry loop that results in a good output from receiving unbounded message duplicates. + +##### fan_out_fail_fast + +The same as the `fan_out` pattern, except that output failures will not be automatically retried. This pattern should be used with caution as busy retry loops could result in unlimited duplicates being introduced into the non-failure outputs. + +##### fan_out_sequential + +Similar to the fan out pattern except outputs are written to sequentially, meaning an output is only written to once the preceding output has confirmed receipt of the same message. + +If an output applies back pressure it will block all subsequent messages, and if an output fails to send a message it will be retried continuously until completion or service shut down. This mechanism is in place in order to prevent one bad output from causing a larger retry loop that results in a good output from receiving unbounded message duplicates. + +##### fan_out_sequential_fail_fast + +The same as the `fan_out_sequential` pattern, except that output failures will not be automatically retried. This pattern should be used with caution as busy retry loops could result in unlimited duplicates being introduced into the non-failure outputs. + +##### round_robin + +With the round robin pattern each message will be assigned a single output following their order. If an output applies back pressure it will block all subsequent messages. If an output fails to send a message then the message will be re-attempted with the next input, and so on. + +##### greedy + +The greedy pattern results in higher output throughput at the cost of potentially disproportionate message allocations to those outputs. Each message is sent to a single output, which is determined by allowing outputs to claim messages as soon as they are able to process them. This results in certain faster outputs potentially processing more messages at the cost of slower outputs. + + +### HTTP Client + +Sends messages to an HTTP server. + +#### Common + +```yml +# Common config fields, showing default values +output: + label: "" + http_client: + url: "" # No default (required) + verb: POST + headers: {} + timeout: 5s + max_in_flight: 64 + batching: + count: 0 + byte_size: 0 + period: "" + check: "" +``` + +#### Advanced + +```yml +# All config fields, showing default values +output: + label: "" + http_client: + url: "" # No default (required) + verb: POST + headers: {} + metadata: + include_prefixes: [] + include_patterns: [] + dump_request_log_level: "" + oauth: + enabled: false + consumer_key: "" + consumer_secret: "" + access_token: "" + access_token_secret: "" + oauth2: + enabled: false + client_key: "" + client_secret: "" + token_url: "" + scopes: [] + endpoint_params: {} + basic_auth: + enabled: false + username: "" + password: "" + jwt: + enabled: false + private_key_file: "" + signing_method: "" + claims: {} + headers: {} + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + extract_headers: + include_prefixes: [] + include_patterns: [] + timeout: 5s + retry_period: 1s + max_retry_backoff: 300s + retries: 3 + backoff_on: + - 429 + drop_on: [] + successful_on: [] + proxy_url: "" # No default (optional) + batch_as_multipart: false + propagate_response: false + max_in_flight: 64 + batching: + count: 0 + byte_size: 0 + period: "" + check: "" + processors: [] # No default (optional) + multipart: [] +``` + +When the number of retries expires the output will reject the message, the behavior after this will depend on the pipeline but usually this simply means the send is attempted again until successful whilst applying back pressure. + +{/* TODO: when interpolation supported +The URL and header values of this type can be dynamically set using function interpolations. */} + +The body of the HTTP request is the raw contents of the message payload. If the message has multiple parts (is a batch) the request will be sent according to [RFC1341](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html). This behavior can be disabled by setting the field [batch_as_multipart](#batch_as_multipart) to `false`. + +##### Propagating Responses + +It's possible to propagate the response from each HTTP request back to the input source by setting `propagate_response` to `true`. Only inputs that support synchronous responses are able to make use of these propagated responses. + +#### Performance + +This output benefits from sending multiple messages in flight in parallel for improved performance. You can tune the max number of in flight messages (or message batches) with the field `max_in_flight`. + +This output benefits from sending messages as a [batch](/api-management/stream-config#batching-6) for improved performance. Batches can be formed at both the input and output level. + +#### Fields + +##### url + +The URL to connect to. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + +Type: `string` + +##### verb + +A verb to connect with + + +Type: `string` +Default: `"POST"` + +```yml +# Examples + +verb: POST + +verb: GET + +verb: DELETE +``` + +##### headers + +A map of headers to add to the request. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `object` +Default: `{}` + +```yml +# Examples + +headers: + Content-Type: application/octet-stream + traceparent: ${! tracing_span().traceparent } +``` + +##### metadata + +Specify optional matching rules to determine which metadata keys should be added to the HTTP request as headers. + + +Type: `object` + +##### metadata.include_prefixes + +Provide a list of explicit metadata key prefixes to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_prefixes: + - foo_ + - bar_ + +include_prefixes: + - kafka_ + +include_prefixes: + - content- +``` + +##### metadata.include_patterns + +Provide a list of explicit metadata key regular expression (re2) patterns to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_patterns: + - .* + +include_patterns: + - _timestamp_unix$ +``` + +##### dump_request_log_level + +Optionally set a level at which the request and response payload of each request made will be logged. + + +Type: `string` +Default: `""` +Options: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, ``. + +##### oauth + +Allows you to specify open authentication via OAuth version 1. + + +Type: `object` + +##### oauth.enabled + +Whether to use OAuth version 1 in requests. + + +Type: `bool` +Default: `false` + +##### oauth.consumer_key + +A value used to identify the client to the service provider. + + +Type: `string` +Default: `""` + +##### oauth.consumer_secret + +A secret used to establish ownership of the consumer key. + + +Type: `string` +Default: `""` + +##### oauth.access_token + +A value used to gain access to the protected resources on behalf of the user. + + +Type: `string` +Default: `""` + +##### oauth.access_token_secret + +A secret provided in order to establish ownership of a given access token. + + +Type: `string` +Default: `""` + +##### oauth2 + +Allows you to specify open authentication via OAuth version 2 using the client credentials token flow. + + +Type: `object` + +##### oauth2.enabled + +Whether to use OAuth version 2 in requests. + + +Type: `bool` +Default: `false` + +##### oauth2.client_key + +A value used to identify the client to the token provider. + + +Type: `string` +Default: `""` + +##### oauth2.client_secret + +A secret used to establish ownership of the client key. + + +Type: `string` +Default: `""` + +##### oauth2.token_url + +The URL of the token provider. + + +Type: `string` +Default: `""` + +##### oauth2.scopes + +A list of optional requested permissions. + + +Type: `array` +Default: `[]` + +##### oauth2.endpoint_params + +A list of optional endpoint parameters, values should be arrays of strings. + + +Type: `object` +Default: `{}` + +```yml +# Examples + +endpoint_params: + bar: + - woof + foo: + - meow + - quack +``` + +##### basic_auth + +Allows you to specify basic authentication. + + +Type: `object` + +##### basic_auth.enabled + +Whether to use basic authentication in requests. + + +Type: `bool` +Default: `false` + +##### basic_auth.username + +A username to authenticate as. + + +Type: `string` +Default: `""` + +##### basic_auth.password + +A password to authenticate with. + + +Type: `string` +Default: `""` + +##### jwt + +Allows you to specify JWT authentication. + + +Type: `object` + +##### jwt.enabled + +Whether to use JWT authentication in requests. + + +Type: `bool` +Default: `false` + +##### jwt.private_key_file + +A file with the PEM encoded via PKCS1 or PKCS8 as private key. + + +Type: `string` +Default: `""` + +##### jwt.signing_method + +A method used to sign the token such as RS256, RS384, RS512 or EdDSA. + + +Type: `string` +Default: `""` + +##### jwt.claims + +A value used to identify the claims that issued the JWT. + + +Type: `object` +Default: `{}` + +##### jwt.headers + +Add optional key/value headers to the JWT. + + +Type: `object` +Default: `{}` + +##### tls + +Custom TLS settings can be used to override system defaults. + + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're seeing the error message `local error: tls: no renegotiation`. + + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, but not both. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +client_certs: + - cert: foo + key: bar + +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in PKCS#1 or PKCS#8 format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. + + +Type: `string` +Default: `""` + +```yml +# Examples + +password: foo +``` + +##### extract_headers + +Specify which response headers should be added to resulting synchronous response messages as metadata. Header keys are lowercased before matching, so ensure that your patterns target lowercased versions of the header keys that you expect. This field is not applicable unless `propagate_response` is set to `true`. + + +Type: `object` + +##### extract_headers.include_prefixes + +Provide a list of explicit metadata key prefixes to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_prefixes: + - foo_ + - bar_ + +include_prefixes: + - kafka_ + +include_prefixes: + - content- +``` + +##### extract_headers.include_patterns + +Provide a list of explicit metadata key regular expression (re2) patterns to match against. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +include_patterns: + - .* + +include_patterns: + - _timestamp_unix$ +``` + +##### timeout + +A static timeout to apply to requests. + + +Type: `string` +Default: `"5s"` + +##### retry_period + +The base period to wait between failed requests. + + +Type: `string` +Default: `"1s"` + +##### max_retry_backoff + +The maximum period to wait between failed requests. + + +Type: `string` +Default: `"300s"` + +##### retries + +The maximum number of retry attempts to make. + + +Type: `int` +Default: `3` + +##### backoff_on + +A list of status codes whereby the request should be considered to have failed and retries should be attempted, but the period between them should be increased gradually. + + +Type: `array` +Default: `[429]` + +##### drop_on + +A list of status codes whereby the request should be considered to have failed but retries should not be attempted. This is useful for preventing wasted retries for requests that will never succeed. Note that with these status codes the _request_ is dropped, but _message_ that caused the request will not be dropped. + + +Type: `array` +Default: `[]` + +##### successful_on + +A list of status codes whereby the attempt should be considered successful, this is useful for dropping requests that return non-2XX codes indicating that the message has been dealt with, such as a 303 See Other or a 409 Conflict. All 2XX codes are considered successful unless they are present within `backoff_on` or `drop_on`, regardless of this field. + + +Type: `array` +Default: `[]` + +##### proxy_url + +An optional HTTP proxy URL. + + +Type: `string` + +##### batch_as_multipart + +Send message batches as a single request using [RFC1341](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html). If disabled messages in batches will be sent as individual requests. + + +Type: `bool` +Default: `false` + +##### propagate_response + +Whether responses from the server should be propagated back to the input. + + +Type: `bool` +Default: `false` + +##### max_in_flight + +The maximum number of parallel message batches to have in flight at any given time. + + +Type: `int` +Default: `64` + +##### batching + +Allows you to configure a [batching policy](/api-management/stream-config#batching-6). + + +Type: `object` + +```yml +# Examples + +batching: + byte_size: 5000 + count: 0 + period: 1s + +batching: + count: 10 + period: 1s + +batching: + check: this.contains("END BATCH") + count: 0 + period: 1m +``` + +##### batching.count + +A number of messages at which the batch should be flushed. If `0` disables count based batching. + + +Type: `int` +Default: `0` + +##### batching.byte_size + +An amount of bytes at which the batch should be flushed. If `0` disables size based batching. + + +Type: `int` +Default: `0` + +##### batching.period + +A period in which an incomplete batch should be flushed regardless of its size. + + +Type: `string` +Default: `""` + +```yml +# Examples + +period: 1s + +period: 1m + +period: 500ms +``` + +{/* TODO: when bloblang supported +##### batching.check + +A Bloblang query that should return a boolean value indicating whether a message should end a batch. + + +Type: `string` +Default: `""` + +```yml +# Examples + +check: this.type == "end_of_transaction" +``` */} + +##### batching.processors + +A list of processors to apply to a batch as it is flushed. This allows you to aggregate and archive the batch however you see fit. Please note that all resulting messages are flushed as a single batch, therefore splitting the batch into smaller batches using these processors is a no-op. + + +Type: `array` + +```yml +# Examples + +processors: + - archive: + format: concatenate + +processors: + - archive: + format: lines + +processors: + - archive: + format: json_array +``` + +##### multipart + +Create explicit multipart HTTP requests by specifying an array of parts to add to the request, each part specified consists of content headers and a data field that can be populated dynamically. If this field is populated it will override the default request creation behavior. + + +Type: `array` +Default: `[]` + +##### multipart[].content_type + +The content type of the individual message part. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `string` +Default: `""` + +```yml +# Examples + +content_type: application/bin +``` + +##### multipart[].content_disposition + +The content disposition of the individual message part. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `string` +Default: `""` + +```yml +# Examples + +content_disposition: form-data; name="bin"; filename='${! @AttachmentName } +``` + +##### multipart[].body + +The body of the individual message part. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `string` +Default: `""` + +```yml +# Examples + +body: ${! this.data.part1 } +``` + +### HTTP Server + +Sets up an HTTP server that will send messages over HTTP(S) GET requests. HTTP 2.0 is supported when using TLS, which is enabled when key and cert files are specified. + +#### Common + +```yml +# Common config fields, showing default values +output: + label: "" + http_server: + address: "" + path: /get + stream_path: /get/stream + ws_path: /get/ws + allowed_verbs: + - GET +``` + +#### Advanced + +```yml +# All config fields, showing default values +output: + label: "" + http_server: + address: "" + path: /get + stream_path: /get/stream + ws_path: /get/ws + allowed_verbs: + - GET + timeout: 5s + cert_file: "" + key_file: "" + cors: + enabled: false + allowed_origins: [] +``` + +Sets up an HTTP server that will send messages over HTTP(S) GET requests. + +{/* TODO add link here If the `address` config field is left blank the [service-wide HTTP server](/docs/components/http/about) will be used. */} + +Three endpoints will be registered at the paths specified by the fields `path`, `stream_path` and `ws_path`. Which allow you to consume a single message batch, a continuous stream of line delimited messages, or a websocket of messages for each request respectively. + +When messages are batched the `path` endpoint encodes the batch according to [RFC1341](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html). + +{/* TODO add link here - This behavior can be overridden by [archiving your batches](/docs/configuration/batching#post-batch-processing). */} + +Please note, messages are considered delivered as soon as the data is written to the client. There is no concept of at least once delivery on this output. + +Please note that components within a Tyk config will register their respective endpoints in a non-deterministic order. This means that establishing precedence of endpoints that are registered via multiple `http_server` inputs or outputs (either within brokers or from cohabiting streams) is not possible in a predictable way. + +This ambiguity makes it difficult to ensure that paths which are both a subset of a path registered by a separate component, and end in a slash (`/`) and will therefore match against all extensions of that path, do not prevent the more specific path from matching against requests. + +It is therefore recommended that you ensure paths of separate components do not collide unless they are explicitly non-competing. + +For example, if you were to deploy two separate `http_server` inputs, one with a path `/foo/` and the other with a path `/foo/bar`, it would not be possible to ensure that the path `/foo/` does not swallow requests made to `/foo/bar`. + + +#### Fields + +##### address + +An alternative address to host from. If left empty the service wide address is used. + + +Type: `string` +Default: `""` + +##### path + +The path from which discrete messages can be consumed. + + +Type: `string` +Default: `"/get"` + +##### stream_path + +The path from which a continuous stream of messages can be consumed. + + +Type: `string` +Default: `"/get/stream"` + +##### ws_path + +The path from which websocket connections can be established. + + +Type: `string` +Default: `"/get/ws"` + +##### allowed_verbs + +An array of verbs that are allowed for the `path` and `stream_path` HTTP endpoint. + + +Type: `array` +Default: `["GET"]` + +##### timeout + +The maximum time to wait before a blocking, inactive connection is dropped (only applies to the `path` endpoint). + + +Type: `string` +Default: `"5s"` + +##### cert_file + +Enable TLS by specifying a certificate and key file. Only valid with a custom `address`. + + +Type: `string` +Default: `""` + +##### key_file + +Enable TLS by specifying a certificate and key file. Only valid with a custom `address`. + + +Type: `string` +Default: `""` + +##### cors + +Adds Cross-Origin Resource Sharing headers. Only valid with a custom `address`. + + +Type: `object` + +##### cors.enabled + +Whether to allow CORS requests. + + +Type: `bool` +Default: `false` + +##### cors.allowed_origins + +An explicit list of origins that are allowed for CORS requests. + + +Type: `array` +Default: `[]` + + + +### Kafka + +The kafka output type writes a batch of messages to Kafka brokers and waits for acknowledgment before propagating it back to the input. + +#### Common + +```yml +# Common config fields, showing default values +output: + label: "" + kafka: + addresses: [] # No default (required) + topic: "" # No default (required) + target_version: 2.1.0 # No default (optional) + key: "" + partitioner: fnv1a_hash + compression: none + static_headers: {} # No default (optional) + metadata: + exclude_prefixes: [] + max_in_flight: 64 + batching: + count: 0 + byte_size: 0 + period: "" + check: "" +``` + +#### Advanced + +```yml +# All config fields, showing default values +output: + label: "" + kafka: + addresses: [] # No default (required) + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + sasl: + mechanism: none + user: "" + password: "" + access_token: "" + token_cache: "" + token_key: "" + topic: "" # No default (required) + client_id: tyk + target_version: 2.1.0 # No default (optional) + rack_id: "" + key: "" + partitioner: fnv1a_hash + partition: "" + custom_topic_creation: + enabled: false + partitions: -1 + replication_factor: -1 + compression: none + static_headers: {} # No default (optional) + metadata: + exclude_prefixes: [] + inject_tracing_map: meta = @.merge(this) # No default (optional) + max_in_flight: 64 + idempotent_write: false + ack_replicas: false + max_msg_bytes: 1000000 + timeout: 5s + retry_as_batch: false + batching: + count: 0 + byte_size: 0 + period: "" + check: "" + processors: [] # No default (optional) + max_retries: 0 + backoff: + initial_interval: 3s + max_interval: 10s + max_elapsed_time: 30s +``` + +The config field `ack_replicas` determines whether we wait for acknowledgment from all replicas or just a single broker. + +{/* Add links to bloblang queries : Both the `key` and `topic` fields can be dynamically set using function interpolations. */} + +Metadata will be added to each message sent as headers (version 0.11+), but can be restricted using the field [metadata](#metadata). + +##### Strict Ordering and Retries + +When strict ordering is required for messages written to topic partitions it is important to ensure that both the field `max_in_flight` is set to `1` and that the field `retry_as_batch` is set to `true`. + +You must also ensure that failed batches are never rerouted back to the same output. This can be done by setting the field `max_retries` to `0` and `backoff.max_elapsed_time` to empty, which will apply back pressure indefinitely until the batch is sent successfully. + +{/* TODO: Add link to fallback broker */} +However, this also means that manual intervention will eventually be required in cases where the batch cannot be sent due to configuration problems such as an incorrect `max_msg_bytes` estimate. A less strict but automated alternative would be to route failed batches to a dead letter queue using a `fallback` broker, but this would allow subsequent batches to be delivered in the meantime whilst those failed batches are dealt with. + +##### Troubleshooting + +- I'm seeing logs that report `Failed to connect to kafka: kafka: client has run out of available brokers to talk to (Is your cluster reachable?)`, but the brokers are definitely reachable. + +Unfortunately this error message will appear for a wide range of connection problems even when the broker endpoint can be reached. Double check your authentication configuration and also ensure that you have [enabled TLS](#tlsenabled) if applicable. + +#### Performance + +This output benefits from sending multiple messages in flight in parallel for improved performance. You can tune the max number of in flight messages (or message batches) with the field `max_in_flight`. + +This output benefits from sending messages as a [batch](/api-management/stream-config#batching-6) for improved performance. Batches can be formed at both the input and output level. + +#### Fields + +##### addresses + +A list of broker addresses to connect to. If an item of the list contains commas it will be expanded into multiple addresses. + + +Type: `array` + +```yml +# Examples + +addresses: + - localhost:9092 + +addresses: + - localhost:9041,localhost:9042 + +addresses: + - localhost:9041 + - localhost:9042 +``` + +##### tls + +Custom TLS settings can be used to override system defaults. + + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're seeing the error message `local error: tls: no renegotiation`. + + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, but not both. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +client_certs: + - cert: foo + key: bar + +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in PKCS#1 or PKCS#8 format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. + + +Type: `string` +Default: `""` + +```yml +# Example + +password: foo +``` + +{/* When Tyk streams with secrets released include this in above example => password: ${KEY_PASSWORD} */} + +##### sasl + +Enables SASL authentication. + + +Type: `object` + +##### sasl.mechanism + +The SASL authentication mechanism, if left empty SASL authentication is not used. + + +Type: `string` +Default: `"none"` + +| Option | Summary | +| :--- | :--- | +| `OAUTHBEARER` | OAuth Bearer based authentication. | +| `PLAIN` | Plain text authentication. NOTE: When using plain text auth it is extremely likely that you'll also need to [enable TLS](#tlsenabled). | +| `SCRAM-SHA-256` | Authentication using the SCRAM-SHA-256 mechanism. | +| `SCRAM-SHA-512` | Authentication using the SCRAM-SHA-512 mechanism. | +| `none` | Default, no SASL authentication. | + + +##### sasl.user + +A PLAIN username. It is recommended that you use environment variables to populate this field. + + +Type: `string` +Default: `""` + +```yml +# Examples + +user: ${USER} +``` + +##### sasl.password + +A PLAIN password. It is recommended that you use environment variables to populate this field. + + +Type: `string` +Default: `""` + +```yml +# Examples + +password: ${PASSWORD} +``` + +##### sasl.access_token + +A static OAUTHBEARER access token + + +Type: `string` +Default: `""` + +##### sasl.token_cache + +Instead of using a static `access_token` allows you to query a `cache` resource to fetch OAUTHBEARER tokens from +{/* TODO: add cache resource link */} + +Type: `string` +Default: `""` + +##### sasl.token_key + +Required when using a `token_cache`, the key to query the cache with for tokens. + + +Type: `string` +Default: `""` + +##### topic + +The topic to publish messages to. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `string` + +##### client_id + +An identifier for the client connection. + + +Type: `string` +Default: `"tyk"` + +##### target_version + +The version of the Kafka protocol to use. This limits the capabilities used by the client and should ideally match the version of your brokers. Defaults to the oldest supported stable version. + + +Type: `string` + +```yml +# Examples + +target_version: 2.1.0 + +target_version: 3.1.0 +``` + +##### rack_id + +A rack identifier for this client. + + +Type: `string` +Default: `""` + +##### key + +The key to publish messages with. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `string` +Default: `""` + +##### partitioner + +The partitioning algorithm to use. + + +Type: `string` +Default: `"fnv1a_hash"` +Options: `fnv1a_hash`, `murmur2_hash`, `random`, `round_robin`, `manual`. + +##### partition + +The manually-specified partition to publish messages to, relevant only when the field `partitioner` is set to `manual`. Must be able to parse as a 32-bit integer. +{/* TODO: when interpolation supported +This field supports interpolation functions. */} + + +Type: `string` +Default: `""` + +##### custom_topic_creation + +If enabled, topics will be created with the specified number of partitions and replication factor if they do not already exist. + + +Type: `object` + +##### custom_topic_creation.enabled + +Whether to enable custom topic creation. + + +Type: `bool` +Default: `false` + +##### custom_topic_creation.partitions + +The number of partitions to create for new topics. Leave at -1 to use the broker configured default. Must `be >= 1`. + + +Type: `int` +Default: `-1` + +##### custom_topic_creation.replication_factor + +The replication factor to use for new topics. Leave at -1 to use the broker configured default. Must be an odd number, and less then or equal to the number of brokers. + + +Type: `int` +Default: `-1` + +##### compression + +The compression algorithm to use. + + +Type: `string` +Default: `"none"` +Options: `none`, `snappy`, `lz4`, `gzip`, `zstd`. + +##### static_headers + +An optional map of static headers that should be added to messages in addition to metadata. + + +Type: `object` + +```yml +# Examples + +static_headers: + first-static-header: value-1 + second-static-header: value-2 +``` + +##### metadata + +Specify criteria for which metadata values are sent with messages as headers. + + +Type: `object` + +##### metadata.exclude_prefixes + +Provide a list of explicit metadata key prefixes to be excluded when adding metadata to sent messages. + + +Type: `array` +Default: `[]` + +##### inject_tracing_map + +A Bloblang mapping used to inject an object containing tracing propagation information into outbound messages. The specification of the injected fields will match the format used by the service wide tracer. + + +Type: `string` +Requires version 3.45.0 or newer + +```yml +# Examples + +inject_tracing_map: meta = @.merge(this) + +inject_tracing_map: root.meta.span = this +``` + +##### max_in_flight + +The maximum number of messages to have in flight at a given time. Increase this to improve throughput. + + +Type: `int` +Default: `64` + +##### idempotent_write + +Enable the idempotent write producer option. This requires the `IDEMPOTENT_WRITE` permission on `CLUSTER` and can be disabled if this permission is not available. + + +Type: `bool` +Default: `false` + +##### ack_replicas + +Ensure that messages have been copied across all replicas before acknowledging receipt. + + +Type: `bool` +Default: `false` + +##### max_msg_bytes + +The maximum size in bytes of messages sent to the target topic. + + +Type: `int` +Default: `1000000` + +##### timeout + +The maximum period of time to wait for message sends before abandoning the request and retrying. + + +Type: `string` +Default: `"5s"` + +##### retry_as_batch + +When enabled forces an entire batch of messages to be retried if any individual message fails on a send, otherwise only the individual messages that failed are retried. Disabling this helps to reduce message duplicates during intermittent errors, but also makes it impossible to guarantee strict ordering of messages. + + +Type: `bool` +Default: `false` + +##### batching + +Allows you to configure a [batching policy](/api-management/stream-config#batch-policy). + + +Type: `object` + +```yml +# Examples + +batching: + byte_size: 5000 + count: 0 + period: 1s + +batching: + count: 10 + period: 1s + +batching: + check: this.contains("END BATCH") + count: 0 + period: 1m +``` + +##### batching.count + +A number of messages at which the batch should be flushed. If `0` disables count based batching. + + +Type: `int` +Default: `0` + +##### batching.byte_size + +An amount of bytes at which the batch should be flushed. If `0` disables size based batching. + + +Type: `int` +Default: `0` + +##### batching.period + +A period in which an incomplete batch should be flushed regardless of its size. + + +Type: `string` +Default: `""` + +```yml +# Examples + +period: 1s + +period: 1m + +period: 500ms +``` + +##### batching.check + +A Bloblang query that should return a boolean value indicating whether a message should end a batch. + + +Type: `string` +Default: `""` + +```yml +# Examples + +check: this.type == "end_of_transaction" +``` + +##### batching.processors + +{/* TODO: add list of processors link */} + +A list of processors to apply to a batch as it is flushed. This allows you to aggregate and archive the batch however you see fit. Please note that all resulting messages are flushed as a single batch, therefore splitting the batch into smaller batches using these processors is a no-op. + + +Type: `array` + +```yml +# Examples + +processors: + - archive: + format: concatenate + +processors: + - archive: + format: lines + +processors: + - archive: + format: json_array +``` + +##### max_retries + +The maximum number of retries before giving up on the request. If set to zero there is no discrete limit. + + +Type: `int` +Default: `0` + +##### backoff + +Control time intervals between retry attempts. + + +Type: `object` + +##### backoff.initial_interval + +The initial period to wait between retry attempts. + + +Type: `string` +Default: `"3s"` + +```yml +# Examples + +initial_interval: 50ms + +initial_interval: 1s +``` + +##### backoff.max_interval + +The maximum period to wait between retry attempts + + +Type: `string` +Default: `"10s"` + +```yml +# Examples + +max_interval: 5s + +max_interval: 1m +``` + +##### backoff.max_elapsed_time + +The maximum overall period of time to spend on retry attempts before the request is aborted. Setting this value to a zeroed duration (such as `0s`) will result in unbounded retries. + + +Type: `string` +Default: `"30s"` + +```yml +# Examples + +max_elapsed_time: 1m + +max_elapsed_time: 1h +``` + +### MQTT +Pushes messages to an MQTT broker. + +The topic field can be dynamically set using function interpolations described here. When sending batched messages these interpolations are performed per message part. + +#### Common +```yml +# Common config fields, showing default values +output: + label: "" + mqtt: + urls: [] # No default (required) + client_id: "" + connect_timeout: 30s + topic: "" # No default (required) + qos: 1 + write_timeout: 3s + retained: false + max_in_flight: 64 +``` + +#### Advanced +```yml +# All config fields, showing default values +output: + label: "" + mqtt: + urls: [] # No default (required) + client_id: "" + dynamic_client_id_suffix: "" # No default (optional) + connect_timeout: 30s + will: + enabled: false + qos: 0 + retained: false + topic: "" + payload: "" + user: "" + password: "" + keepalive: 30 + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + topic: "" # No default (required) + qos: 1 + write_timeout: 3s + retained: false + retained_interpolated: "" # No default (optional) + max_in_flight: 64 +``` + +#### Performance + +This output benefits from sending multiple messages in flight in parallel for improved performance. You can tune the max number of in flight messages (or message batches) with the field `max_in_flight`. + +#### Fields + +##### urls + +A list of URLs to connect to. If an item of the list contains commas it will be expanded into multiple URLs. + + +Type: `array` + +```yml +# Examples + +urls: + - tcp://localhost:1883 +``` + +##### client_id + +An identifier for the client connection. + + +Type: `string` +Default: `""` + +##### dynamic_client_id_suffix + +Append a dynamically generated suffix to the specified `client_id` on each run of the pipeline. This can be useful when clustering Streams producers. + + +Type: `string` + +| Option | Summary | +| :--- | :--- | +| `nanoid` | append a nanoid of length 21 characters | + + +##### connect_timeout + +The maximum amount of time to wait in order to establish a connection before the attempt is abandoned. + + +Type: `string` +Default: `"30s"` +Requires version 1.0.0 or newer + +```yml +# Examples + +connect_timeout: 1s + +connect_timeout: 500ms +``` + +##### will + +Set last will message in case of Streams failure + + +Type: `object` + +##### will.enabled + +Whether to enable last will messages. + + +Type: `bool` +Default: `false` + +##### will.qos + +Set QoS for last will message. Valid values are: 0, 1, 2. + + +Type: `int` +Default: `0` + +##### will.retained + +Set retained for last will message. + + +Type: `bool` +Default: `false` + +##### will.topic + +Set topic for last will message. + + +Type: `string` +Default: `""` + +##### will.payload + +Set payload for last will message. + + +Type: `string` +Default: `""` + +##### user + +A username to connect with. + + +Type: `string` +Default: `""` + +##### password + +A password to connect with. + + +Type: `string` +Default: `""` + +##### keepalive + +Max seconds of inactivity before a keepalive message is sent. + + +Type: `int` +Default: `30` + +##### tls + +Custom TLS settings can be used to override system defaults. + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're seeing the error message `local error: tls: no renegotiation`. + + +Type: `bool` +Default: `false` +Requires version 1.0.0 or newer + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host certificate. + + +Type: `string` +Default: `""` + +```yml +# Examples + +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, but not both. + + +Type: `array` +Default: `[]` + +```yml +# Examples + +client_certs: + - cert: foo + key: bar + +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in PKCS#1 or PKCS#8 format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an attacker recover the plaintext. + + +Type: `string` +Default: `""` + +```yml +# Examples + +password: foo + +password: ${KEY_PASSWORD} +``` + +##### topic + +The topic to publish messages to. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + + +Type: `string` + +##### qos + +The QoS value to set for each message. Has options 0, 1, 2. + + +Type: `int` +Default: `1` + +##### write_timeout + +The maximum amount of time to wait to write data before the attempt is abandoned. + + +Type: `string` +Default: `"3s"` +Requires version 1.0.0 or newer + +```yml +# Examples + +write_timeout: 1s + +write_timeout: 500ms +``` + +##### retained + +Set message as retained on the topic. + + +Type: `bool` +Default: `false` + +##### retained_interpolated + +Override the value of `retained` with an interpolable value, this allows it to be dynamically set based on message contents. The value must resolve to either `true` or `false`. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + + +Type: `string` +Requires version 1.0.0 or newer + +##### max_in_flight + +The maximum number of messages to have in flight at a given time. Increase this to improve throughput. + + +Type: `int` +Default: `64` + + +### amqp_0_9 + +Sends messages to an AMQP (0.91) exchange. AMQP is a messaging protocol used by various message brokers, +including RabbitMQ. + +#### Common + +```yaml +# Common config fields, showing default values +output: + label: "" + amqp_0_9: + urls: [] # No default (required) + exchange: "" # No default (required) + key: "" + type: "" + metadata: + exclude_prefixes: [] + max_in_flight: 64 +``` + +#### Advanced + +```yaml +# All config fields, showing default values +output: + label: "" + amqp_0_9: + urls: [] # No default (required) + exchange: "" # No default (required) + exchange_declare: + enabled: false + type: direct + durable: true + key: "" + type: "" + content_type: application/octet-stream + content_encoding: "" + correlation_id: "" + reply_to: "" + expiration: "" + message_id: "" + user_id: "" + app_id: "" + metadata: + exclude_prefixes: [] + priority: "" + max_in_flight: 64 + persistent: false + mandatory: false + immediate: false + timeout: "" + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] +``` + +#### Metadata + +The metadata from each message is delivered as headers. + +It's possible for this output type to create the target exchange by setting `exchange_declare.enabled` to `true`, if the exchange +already exists then the declaration passively verifies that the settings match. + +TLS is automatic when connecting to an `amqps` URL, but custom settings can be enabled in the `tls` section. + +#### Fields + +##### urls + +A list of URLs to connect to. The first URL to successfully establish a connection will be used until the connection is closed. +If an item of the list contains commas, it will be expanded into multiple URLs. + +Type: `array` + +```yaml +# Examples +urls: + - amqp://guest:guest@127.0.0.1:5672/ +urls: + - amqp://127.0.0.1:5672/,amqp://127.0.0.2:5672/ +urls: + - amqp://127.0.0.1:5672/ + - amqp://127.0.0.2:5672/ +``` + +##### exchange + +An AMQP exchange to publish to. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` + +##### exchange_declare + +Optionally declare the target exchange (passive). + +Type: `object` + +##### exchange_declare.enabled + +Whether to declare the exchange. + +Type: `bool` +Default: `false` + +##### exchange_declare.type + +The type of the exchange. + +Type: `string` +Default: `"direct"` +Options: `direct`, `fanout`, `topic`, `x-custom` + +##### exchange_declare.durable + +Whether the exchange should be durable. + +Type: `bool` +Default: `true` + + +##### key + +The binding key to set for each message. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### type + +The type property to set for each message. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### content_type + +The content type attribute to set for each message. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `application/octet-stream` + +##### content_encoding + +The content encoding attribute to set for each message. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### correlation_id + +Set the correlation ID of each message with a dynamic interpolated expression. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### reply_to + +Carries response queue name - set with a dynamic interpolated expression. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### expiration + +Set the per-message TTL. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### message_id + +Set the message ID of each message with a dynamic interpolated expression. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### user_id + +Set the user ID to the name of the publisher. If this property is set by a publisher, its value must be equal to the name +of the user used to open the connection. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### app_id + +Set the application ID of each message with a dynamic interpolated expression. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +##### metadata + +Specify criteria for which metadata values are attached to messages as headers. + +Type: `object` + +##### metadata.exclude_prefixes + +Provide a list of explicit metadata key prefixes to be excluded when adding metadata to sent messages. + +Type: `array` +Default: `[]` + +##### priority + +Set the priority of each message with a dynamic interpolated expression. +{/* TODO: when interpolation supported: +This field supports interpolation functions. */} + +Type: `string` +Default: `""` + +```yaml +# Examples +priority: "0" +priority: ${! metadata("amqp_priority") } +priority: ${! json("doc.priority") } +``` + +##### max_in_flight + +The maximum number of messages to have in flight at a given time. Increase this to improve throughput. + +Type: `int` +Default: `64` + +##### persistent + +Whether message delivery should be persistent (transient by default). + +Type: `bool` +Default: `false` + +##### mandatory + +Whether to set the mandatory flag on published messages. When set if a published message is routed to zero queues, it is returned. + +Type: `bool` +Default: `false` + +##### immediate + +Whether to set the immediate flag on published messages. When set if there are no ready consumers of a queue, then the message is dropped instead of waiting. + +Type: `bool` +Default: `false` + +##### timeout + +The maximum period to wait before abandoning it and reattempting. If not set, wait indefinitely. + +Type: `string` +Default: `""` + +##### tls + +Custom TLS settings can be used to override system defaults. + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're getting the error message +`local error: tls: no renegotiation.` + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, +to possible intermediate signing certificates, to the host certificate. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing +a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host +certificate. + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, +but not both. + +Type: `array` +Default: `[]` + +```yaml +# Examples +client_certs: + - cert: foo + key: bar +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in *PKCS#1* or *PKCS#8* format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not +supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an +attacker recover the plaintext. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +password: foo +``` + +### amqp_1 + +Sends messages to an AMQP (1.0) server. + +#### Common + +```yaml +# Common config fields, showing default values +output: + label: "" + amqp_1: + urls: [] # No default (optional) + target_address: /foo # No default (required) + max_in_flight: 64 + metadata: + exclude_prefixes: [] +``` + +#### Advanced + +```yaml +# All config fields, showing default values +output: + label: "" + amqp_1: + urls: [] # No default (optional) + target_address: /foo # No default (required) + max_in_flight: 64 + tls: + enabled: false + skip_cert_verify: false + enable_renegotiation: false + root_cas: "" + root_cas_file: "" + client_certs: [] + application_properties_map: "" # No default (optional) + sasl: + mechanism: none + user: "" + password: "" + metadata: + exclude_prefixes: [] +``` + +#### Metadata + +Message metadata is added to each AMQP message as string annotations. To control which metadata keys are added, use the `metadata` config field. + +#### Performance + +This output benefits from sending multiple messages in flight in parallel for improved performance. You can tune the max number of in flight +messages (or message batches) with the field `max_in_flight`. + + +#### Fields + +##### urls + +A list of URLs to connect to. The first URL to successfully establish a connection will be used until the connection is closed. +If an item of the list contains commas it will be expanded into multiple URLs. + +Type: `array` + +```yaml +# Examples +urls: + - amqp://guest:guest@127.0.0.1:5672/ +urls: + - amqp://127.0.0.1:5672/,amqp://127.0.0.2:5672/ +urls: + - amqp://127.0.0.1:5672/ + - amqp://127.0.0.2:5672/ +``` + +##### target_address + +The target address to write to. + +Type: `string` + +```yaml +# Examples +target_address: /foo +target_address: queue:/bar +target_address: topic:/baz +``` + +##### max_in_flight + +The maximum number of messages to have in flight at a given time. Increase this to improve throughput. + +Type: `int` +Default: `64` + +##### tls + +Custom TLS settings can be used to override system defaults. + +Type: `object` + +##### tls.enabled + +Whether custom TLS settings are enabled. + +Type: `bool` +Default: `false` + +##### tls.skip_cert_verify + +Whether to skip server side certificate verification. + +Type: `bool` +Default: `false` + +##### tls.enable_renegotiation + +Whether to allow the remote server to repeatedly request renegotiation. Enable this option if you're getting the error message +`local error: tls: no renegotiation.` + +Type: `bool` +Default: `false` + +##### tls.root_cas + +An optional root certificate authority to use. This is a string, representing a certificate chain from the parent trusted root certificate, +to possible intermediate signing certificates, to the host certificate. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas: |- + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- +``` + +##### tls.root_cas_file + +An optional path of a root certificate authority file to use. This is a file, often with a .pem extension, containing +a certificate chain from the parent trusted root certificate, to possible intermediate signing certificates, to the host +certificate. + +Type: `string` +Default: `""` + +```yaml +# Examples +root_cas_file: ./root_cas.pem +``` + +##### tls.client_certs + +A list of client certificates to use. For each certificate either the fields `cert` and `key`, or `cert_file` and `key_file` should be specified, +but not both. + +Type: `array` +Default: `[]` + +```yaml +# Examples +client_certs: + - cert: foo + key: bar +client_certs: + - cert_file: ./example.pem + key_file: ./example.key +``` + +##### tls.client_certs[].cert + +A plain text certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key + +A plain text certificate key to use. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +##### tls.client_certs[].cert_file + +The path of a certificate to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].key_file + +The path of a certificate key to use. + +Type: `string` +Default: `""` + +##### tls.client_certs[].password + +A plain text password for when the private key is password encrypted in *PKCS#1* or *PKCS#8* format. The obsolete `pbeWithMD5AndDES-CBC` algorithm is not +supported for the PKCS#8 format. Warning: Since it does not authenticate the ciphertext, it is vulnerable to padding oracle attacks that can let an +attacker recover the plaintext. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + +```yaml +# Examples +password: foo +``` + +##### application_properties_map + +An optional Bloblang mapping that can be defined to set the `application-properties` on output messages. + +Type: `string` + +##### sasl + +Enables SASL authentication. + +Type: `object` + +##### sasl.mechanism + +The SASL authentication mechanism to use. + +Type: `string` +Default: `"none"` + +| Option | Summary | +| :----------- | :-------------------------------------- | +| anonymous | Anonymous SASL authentication. | +| none | No SASL based authentication. | +| plain | Plain text SASL authentication. | + + +##### sasl.user + +A SASL plain text username. It is recommended that you use environment variables to populate this field. + +Type: `string` +Default: `""` + +```yaml +# Examples +user: ${USER} +``` + +##### sasl.password + +A SASL plain text password. It is recommended that you use environment variables to populate this field. +{/* TODO add secrets link :::warning Secret +This field contains sensitive information that usually shouldn't be added to a config directly, read our [secrets page for more info](/docs/configuration/secrets). +::: */} + +Type: `string` +Default: `""` + + +```yaml +# Examples +password: ${PASSWORD} +``` + +##### metadata + +Specify criteria for which metadata values are attached to messages as headers. + +Type: `object` + +##### metadata.exclude_prefixes + +Provide a list of explicit metadata key prefixes to be excluded when adding metadata to sent messages. + +Type: `array` +Default: `[]` + +## Processors + +### Overview + +Tyk Streams processors are functions applied to messages passing through a pipeline. + +Processors are set via config, and depending on where in the config they are placed they will be run either immediately after a specific input (set in the input section), on all messages (set in the pipeline section) or before a specific output (set in the output section). Most processors apply to all messages and can be placed in the pipeline section: + +```yaml +pipeline: + threads: 1 + processors: + - label: my_avro + avro: + operator: "to_json" + encoding: textual +``` + +The `threads` field in the pipeline section determines how many parallel processing threads are created. You can read more about parallel processing in the [pipeline guide](/api-management/stream-config#processing-pipelines). + +#### Labels + +{/* TODO: Replace paragraph below in subsequent iteration when know if metrics supported from product + +Processors have an optional field `label` that can uniquely identify them in observability data such as metrics and logs. This can be useful when running configs with multiple nested processors, otherwise their metrics labels will be generated based on their composition. For more information check out the [metrics documentation]. */} + +Processors have an optional field `label` that can uniquely identify them in observability data such as logs. + +### Avro + +```yml +# Config fields, with default values +label: "" +avro: + operator: "" # No default (required) + encoding: textual + schema: "" + schema_path: "" +``` + + + +**Note** + +If you are consuming or generating messages using a schema registry service then it is likely this processor will fail as those services require messages to be prefixed with the identifier of the schema version being used. + + + +#### Operators + +##### to_json + +Converts Avro documents into a JSON structure. This makes it easier to +manipulate the contents of the document within Tyk Streams. The encoding field +specifies how the source documents are encoded. + + +##### from_json + +Attempts to convert JSON documents into Avro documents according to the +specified encoding. + +#### Fields + +##### operator + +The [operator](#operators) to execute + + +Type: `string` +Options: `to_json`, `from_json`. + +##### encoding + +An Avro encoding format to use for conversions to and from a schema. + + +Type: `string` +Default: `"textual"` +Options: `textual`, `binary`, `single`. + +##### schema + +A full Avro schema to use. + + +Type: `string` +Default: `""` + +##### schema_path + +The path of a schema document to apply. Use either this or the `schema` field. + + +Type: `string` +Default: `""` + +```yml +# Examples + +schema_path: file://path/to/spec.avsc + +schema_path: http://localhost:8081/path/to/spec/versions/1 +``` + +### Mapping + +Executes a Bloblang mapping on messages, creating a new document that replaces (or filters) the original message. + +Bloblang is a powerful language that enables various mapping, transformation, and filtering tasks. For more information, check out the [Bloblang docs](https://warpstreamlabs.github.io/bento/docs/guides/bloblang/about/). + +```yml +label: "" +mapping: "" # No default (required) +``` + +#### Example + +Given a JSON document with US location names and the states they are located in: +```json +{ + "locations": [ + {"name": "Seattle", "state": "WA"}, + {"name": "New York", "state": "NY"}, + {"name": "Bellevue", "state": "WA"}, + {"name": "Olympia", "state": "WA"} + ] +} +``` + +If we want to collapse the location names from the state of Washington into a field `Cities`: + +```json +{"Cities": "Bellevue, Olympia, Seattle"} +``` + +We could use the following bloblang mapping: + +```yml +pipeline: + processors: + - mapping: | + root.Cities = this.locations. + filter(loc -> loc.state == "WA"). + map_each(loc -> loc.name). + sort().join(", ") +``` + +#### Considerations + + - If a mapping fails, the message remains unchanged. However, Bloblang provides powerful ways to ensure your mappings do not fail by specifying desired fallback behaviour. See [this section of the Bloblang docs](https://warpstreamlabs.github.io/bento/docs/configuration/error_handling/). + - Mapping operates by creating an entirely new object during assignments. This has the advantage of treating the original referenced document as immutable and, therefore, queryable at any stage of your mapping. As a result, the `Cities` JSON document in the above example is a new, separate copy of the original document, which remains unchanged. + +## Tracers + +### Overview + +A tracer type represents a destination for Tyk Streams to send tracing events to such as [Jaeger](https://www.jaegertracing.io/). + +When a tracer is configured all messages will be allocated a root span during ingestion that represents their journey through a Streams pipeline. Many Streams processors create spans, and so tracing is a great way to analyse the pathways of individual messages as they progress through a Streams instance. + +Some inputs, such as `http_server` and `http_client`, are capable of extracting a root span from the source of the message (HTTP headers). This is +a work in progress and should eventually expand so that all inputs have a way of doing so. + +Other inputs, such as `kafka` can be configured to extract a root span by using the `extract_tracing_map` field. + +A tracer config section looks like this: + +```yaml +tracer: + jaeger: + agent_address: localhost:6831 + sampler_type: const + sampler_param: 1 +``` + + + +**Note** + +Although the configuration spec of this component is stable the format of spans, tags and logs created by Streams is subject to change as it is tuned for improvement. + + + +### Jaeger + +```yml +# Common config fields, showing default values +tracer: + jaeger: + agent_address: "" + collector_url: "" + sampler_type: const + flush_interval: "" # No default (optional) +``` + +#### Advanced + +```yml +# All config fields, showing default values +tracer: + jaeger: + agent_address: "" + collector_url: "" + sampler_type: const + sampler_param: 1 + tags: {} + flush_interval: "" # No default (optional) +``` + +Send tracing events to a [Jaeger](https://www.jaegertracing.io/) agent or collector. + +#### Fields + +##### agent_address + +The address of a Jaeger agent to send tracing events to. + +Type: `string` +Default: `""` + +```yml +# Examples + +agent_address: jaeger-agent:6831 +``` + +##### collector_url + +The URL of a Jaeger collector to send tracing events to. If set, this will override `agent_address`. + +Type: `string` +Default: `""` + +```yml +# Examples + +collector_url: https://jaeger-collector:14268/api/traces +``` + +##### sampler_type + +The sampler type to use. + +Type: `string` +Default: `"const"` + +| Option | Summary | +| :--- | :--- | +| `const` | Sample a percentage of traces. 1 or more means all traces are sampled, 0 means no traces are sampled and anything in between means a percentage of traces are sampled. Tuning the sampling rate is recommended for high-volume production workloads. | + +##### sampler_param + +A parameter to use for sampling. This field is unused for some sampling types. + +Type: `float` +Default: `1` + +##### tags + +A map of tags to add to tracing spans. + +Type: `object` +Default: `{}` + +##### flush_interval + +The period of time between each flush of tracing spans. + +Type: `string` + +### OpenTelemetry Collector + +```yml +# Common config fields, showing default values +tracer: + open_telemetry_collector: + http: [] # No default (required) + grpc: [] # No default (required) + sampling: + enabled: false + ratio: 0.85 # No default (optional) +``` + +#### Advanced + +```yml +# All config fields, showing default values +tracer: + open_telemetry_collector: + http: [] # No default (required) + grpc: [] # No default (required) + tags: {} + sampling: + enabled: false + ratio: 0.85 # No default (optional) +``` + + + +**Note** + +This component is experimental and therefore subject to change or removal outside of major version releases. + + + +Send tracing events to an [Open Telemetry collector](https://opentelemetry.io/docs/collector/). + +#### Fields + +##### http + +A list of http collectors. + +Type: `array` + +##### http[].address + +The endpoint of a collector to send tracing events to. + +Type: `string` + +```yml +# Examples + +address: localhost:4318 +``` + +##### http[].secure + +Connect to the collector over HTTPS + +Type: `bool` +Default: `false` + +##### grpc + +A list of grpc collectors. + +Type: `array` + +##### grpc[].address + +The endpoint of a collector to send tracing events to. + +Type: `string` + +```yml +# Examples + +address: localhost:4317 +``` + +##### grpc[].secure + +Connect to the collector with client transport security + +Type: `bool` +Default: `false` + +##### tags + +A map of tags to add to all tracing spans. + +Type: `object` +Default: `{}` + +##### sampling + +Settings for trace sampling. Sampling is recommended for high-volume production workloads. + +Type: `object` + +##### sampling.enabled + +Whether to enable sampling. + +Type: `bool` +Default: `false` + +##### sampling.ratio + +Sets the ratio of traces to sample. + +Type: `float` + +```yml +# Examples + +ratio: 0.85 + +ratio: 0.5 +``` + +## Metrics + +### Overview + +Streams emits lots of metrics in order to expose how components configured within your pipeline are behaving. You can configure exactly where these metrics end up with the config field `metrics`, which describes a metrics format and destination. For example, if you wished to push them via the Prometheus protocol you could use this configuration: + +```yaml +metrics: + prometheus: + push_interval: 1s + push_job_name: in + push_url: http://localhost:9091 +``` + +### Metric Names + +Metrics are emitted with a prefix that can be configured with the field `prefix`. The default prefix is `bento`. The following metrics are emitted with the respective types: + +#### Gauges + +- `{prefix}_input_count` Number of inputs currently active. +- `{prefix}_output_count` Number of outputs currently active. +- `{prefix}_processor_count` Number of processors currently active. +- `{prefix}_cache_count` Number of caches currently active. +- `{prefix}_condition_count` Number of conditions currently active. +- `{prefix}_input_connection_up` 1 if a particular input is connected, 0 if it is not. +- `{prefix}_output_connection_up` 1 if a particular output is connected, 0 if it is not. +- `{prefix}_input_running` 1 if a particular input is running, 0 if it is not. +- `{prefix}_output_running` 1 if a particular output is running, 0 if it is not. +- `{prefix}_processor_running` 1 if a particular processor is running, 0 if it is not. +- `{prefix}_cache_running` 1 if a particular cache is running, 0 if it is not. +- `{prefix}_condition_running` 1 if a particular condition is running, 0 if it is not. +- `{prefix}_buffer_running` 1 if a particular buffer is running, 0 if it is not. +- `{prefix}_buffer_available` The number of messages that can be read from a buffer. +- `{prefix}_input_retry` The number of active retry attempts for a particular input. +- `{prefix}_output_retry` The number of active retry attempts for a particular output. +- `{prefix}_processor_retry` The number of active retry attempts for a particular processor. +- `{prefix}_cache_retry` The number of active retry attempts for a particular cache. +- `{prefix}_condition_retry` The number of active retry attempts for a particular condition. +- `{prefix}_buffer_retry` The number of active retry attempts for a particular buffer. +- `{prefix}_threads_active` The number of processing threads currently active. + +#### Counters + +- `{prefix}_input_received` Count of messages received by a particular input. +- `{prefix}_input_batch_received` Count of batches received by a particular input. +- `{prefix}_output_sent` Count of messages sent by a particular output. +- `{prefix}_output_batch_sent` Count of batches sent by a particular output. +- `{prefix}_processor_processed` Count of messages processed by a particular processor. +- `{prefix}_processor_batch_processed` Count of batches processed by a particular processor. +- `{prefix}_processor_dropped` Count of messages dropped by a particular processor. +- `{prefix}_processor_batch_dropped` Count of batches dropped by a particular processor. +- `{prefix}_processor_error` Count of errors returned by a particular processor. +- `{prefix}_processor_batch_error` Count of batch errors returned by a particular processor. +- `{prefix}_cache_hit` Count of cache key lookups that found a value. +- `{prefix}_cache_miss` Count of cache key lookups that did not find a value. +- `{prefix}_cache_added` Count of new cache entries. +- `{prefix}_cache_err` Count of errors that occurred during a cache operation. +- `{prefix}_condition_hit` Count of condition checks that passed. +- `{prefix}_condition_miss` Count of condition checks that failed. +- `{prefix}_condition_error` Count of errors that occurred during a condition check. +- `{prefix}_buffer_added` Count of messages added to a particular buffer. +- `{prefix}_buffer_batch_added` Count of batches added to a particular buffer. +- `{prefix}_buffer_read` Count of messages read from a particular buffer. +- `{prefix}_buffer_batch_read` Count of batches read from a particular buffer. +- `{prefix}_buffer_ack` Count of messages removed from a particular buffer. +- `{prefix}_buffer_batch_ack` Count of batches removed from a particular buffer. +- `{prefix}_buffer_nack` Count of messages that failed to be removed from a particular buffer. +- `{prefix}_buffer_batch_nack` Count of batches that failed to be removed from a particular buffer. +- `{prefix}_buffer_err` Count of errors that occurred during a buffer operation. +- `{prefix}_buffer_batch_err` Count of batch errors that occurred during a buffer operation. +- `{prefix}_input_error` Count of errors that occurred during an input operation. +- `{prefix}_input_batch_error` Count of batch errors that occurred during an input operation. +- `{prefix}_output_error` Count of errors that occurred during an output operation. +- `{prefix}_output_batch_error` Count of batch errors that occurred during an output operation. +- `{prefix}_resource_cache_error` Count of errors that occurred during a resource cache operation. +- `{prefix}_resource_condition_error` Count of errors that occurred during a resource condition operation. +- `{prefix}_resource_input_error` Count of errors that occurred during a resource input operation. +- `{prefix}_resource_processor_error` Count of errors that occurred during a resource processor operation. +- `{prefix}_resource_output_error` Count of errors that occurred during a resource output operation. +- `{prefix}_resource_rate_limit_error` Count of errors that occurred during a resource rate limit operation. + +#### Timers + +- `{prefix}_input_latency` Latency of a particular input. +- `{prefix}_input_batch_latency` Latency of a particular input at the batch level. +- `{prefix}_output_latency` Latency of a particular output. +- `{prefix}_output_batch_latency` Latency of a particular output at the batch level. +- `{prefix}_processor_latency` Latency of a particular processor. +- `{prefix}_processor_batch_latency` Latency of a particular processor at the batch level. +- `{prefix}_condition_latency` Latency of a particular condition. +- `{prefix}_condition_batch_latency` Latency of a particular condition at the batch level. +- `{prefix}_cache_latency` Latency of a particular cache. +- `{prefix}_buffer_latency` Latency of a particular buffer. +- `{prefix}_buffer_batch_latency` Latency of a particular buffer at the batch level. + +### Metric Labels + +All metrics are emitted with the following labels: + +- `path` The path of the component within the config. +- `label` A custom label for the component, which is optional and falls back to the component type. + +### Prometheus + +```yml +# Common config fields, showing default values +metrics: + prometheus: + prefix: tyk + push_interval: "" + push_job_name: kafka_out + push_url: "" +``` + +#### Advanced + +```yml +# All config fields, showing default values +metrics: + prometheus: + prefix: tyk + push_interval: "" + push_job_name: my_stream + push_url: "" + push_basic_auth: + enabled: false + username: "" + password: "" + file_path: "" + use_histogram_timing: false + histogram_buckets: [0.000001, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1.0] +``` + +Send metrics to a Prometheus push gateway, or expose them via HTTP endpoints. + +#### Fields + +##### prefix + +A string prefix for all metrics. + +Type: `string` +Default: `"bento"` + +##### push_interval + +The interval between pushing metrics to the push gateway. + +Type: `string` +Default: `""` + +```yml +# Examples + +push_interval: 1s + +push_interval: 1m +``` + +##### push_job_name + +A job name to attach to metrics pushed to the push gateway. + +Type: `string` +Default: `"bento_push"` + +##### push_url + +The URL to push metrics to. + +Type: `string` +Default: `""` + +```yml +# Examples + +push_url: http://localhost:9091 +``` + +##### push_basic_auth + +Basic authentication configuration for the push gateway. + +Type: `object` + +##### push_basic_auth.enabled + +Whether to use basic authentication when pushing metrics. + +Type: `bool` +Default: `false` + +##### push_basic_auth.username + +The username to authenticate with. + +Type: `string` +Default: `""` + +##### push_basic_auth.password + +The password to authenticate with. + +Type: `string` +Default: `""` + +##### file_path + +The file path to write metrics to. + +Type: `string` +Default: `""` + +```yml +# Examples + +file_path: /tmp/metrics.txt +``` + +##### use_histogram_timing + +Whether to use histogram metrics for timing values. When set to false, summary metrics are used instead. + +Type: `bool` +Default: `false` + +##### histogram_buckets + +A list of duration buckets to track when use_histogram_timing is set to true. + +Type: `array` +Default: `[0.000001, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1.0]` + +## Common Configuration + +### Batching + +Tyk Streams is able to join sources and sinks with sometimes conflicting batching behaviours without sacrificing its strong delivery guarantees. Therefore, batching within Tyk Streams is a mechanism that serves multiple purposes: + +1. [Performance (throughput)](#performance) +2. [Compatibility (mixing multi and single part message protocols)](#compatibility) + +#### Performance + +For most users the only benefit of batching messages is improving throughput over your output protocol. For some protocols this can happen in the background and requires no configuration from you. However, if an output has a `batching` configuration block this means it benefits from batching and requires you to specify how you'd like your batches to be formed by configuring a [batching policy](#batch-policy): + +```yaml +output: + kafka: + addresses: [ todo:9092 ] + topic: tyk_stream + + # Either send batches when they reach 10 messages or when 100ms has passed + # since the last batch. + batching: + count: 10 + period: 100ms +``` + +However, a small number of inputs such as [kafka](/api-management/stream-config#kafka) must be consumed sequentially (in this case by partition) and therefore benefit from specifying your batch policy at the input level instead: + +```yaml +input: + kafka: + addresses: [ todo:9092 ] + topics: [ tyk_input_stream ] + batching: + count: 10 + period: 100ms + +output: + kafka: + addresses: [ todo:9092 ] + topic: tyk_stream +``` + +Inputs that behave this way are documented as such and have a `batching` configuration block. + +Sometimes you may prefer to create your batches before processing, in which case if your input doesn't already support [a batch policy](#batch-policy) you can instead use a [broker](/api-management/stream-config#broker), which also allows you to combine inputs with a single batch policy: + +```yaml +input: + broker: + inputs: + - resource: foo + - resource: bar + batching: + count: 50 + period: 500ms +``` + +This also works the same with [output brokers](/api-management/stream-config#broker-1). + +#### Compatibility + +Tyk Streams is able to read and write over protocols that support multiple part messages, and all payloads travelling through Tyk Streams are represented as a multiple part message. Therefore, all components within Tyk Streams are able to work with multiple parts in a message as standard. + +When messages reach an output that *doesn't* support multiple parts the message is broken down into an individual message per part, and then one of two behaviours happen depending on the output. If the output supports batch sending messages then the collection of messages are sent as a single batch. Otherwise, Tyk Streams falls back to sending the messages sequentially in multiple, individual requests. + +This behaviour means that not only can multiple part message protocols be easily matched with single part protocols, but also the concept of multiple part messages and message batches are interchangeable within Tyk Streams. + +#### Batch Policy + +When an input or output component has a config field `batching` that means it supports a batch policy. This is a mechanism that allows you to configure exactly how your batching should work on messages before they are routed to the input or output it's associated with. Batches are considered complete and will be flushed downstream when either of the following conditions are met: + + +- The `byte_size` field is non-zero and the total size of the batch in bytes matches or exceeds it (disregarding metadata.) +- The `count` field is non-zero and the total number of messages in the batch matches or exceeds it. +- The `period` field is non-empty and the time since the last batch exceeds its value. + +This allows you to combine conditions: + +```yaml +output: + kafka: + addresses: [ todo:9092 ] + topic: tyk_stream + + # Either send batches when they reach 10 messages or when 100ms has passed + # since the last batch. + batching: + count: 10 + period: 100ms +``` + + + +A batch policy has the capability to *create* batches, but not to break them down. + + + +If your configured pipeline is processing messages that are batched *before* they reach the batch policy then they may circumvent the conditions you've specified here, resulting in sizes you aren't expecting. + +### Field Paths + +Many components within Tyk Streams allow you to target certain fields using a JSON dot path. The syntax of a path within Tyk Streams is similar to [JSON Pointers](https://tools.ietf.org/html/rfc6901), except with dot separators instead of slashes (and no leading dot.) When a path is used to set a value any path segment that does not yet exist in the structure is created as an object. + +For example, if we had the following JSON structure: + +```json +{ + "foo": { + "bar": 21 + } +} +``` + +The query path `foo.bar` would return `21`. + +The characters `~` (%x7E) and `.` (%x2E) have special meaning in Tyk Streams paths. Therefore `~` needs to be encoded as `~0` and `.` needs to be encoded as `~1` when these characters appear within a key. + +For example, if we had the following JSON structure: + +```json +{ + "foo.foo": { + "bar~bo": { + "": { + "baz": 22 + } + } + } +} +``` + +The query path `foo~1foo.bar~0bo..baz` would return `22`. + +#### Arrays + +When Tyk Streams encounters an array whilst traversing a JSON structure it requires the next path segment to be either an integer of an existing index, or, depending on whether the path is used to query or set the target value, the character `*` or `-` respectively. + +For example, if we had the following JSON structure: + +```json +{ + "foo": [ + 0, 1, { "bar": 23 } + ] +} +``` + +The query path `foo.2.bar` would return `23`. + +##### Querying + +When a query reaches an array the character `*` indicates that the query should return the value of the remaining path from each element of the array (within an array.) + +##### Setting + +When an array is reached the character `-` indicates that a new element should be appended to the end of the existing elements, if this character is not the final segment of the path then an object is created. + +### Processing Pipelines + +Within a Tyk Streams configuration, in between `input` and `output`, is a `pipeline` section. This section describes an array of processors that are to be applied to *all* messages, and are not bound to any particular input or output. + +If you have processors that are heavy on CPU and aren't specific to a certain input or output they are best suited for the pipeline section. It is advantageous to use the pipeline section as it allows you to set an explicit number of parallel threads of execution: + +```yaml +input: + resource: foo + +pipeline: + threads: 4 + processors: + - avro: + operator: "to_json" + +output: + resource: bar +``` + +If the field `threads` is set to `-1` (the default) it will automatically match the number of logical CPUs available. By default almost all Tyk Streams sources will utilize as many processing threads as have been configured, which makes horizontal scaling easy. diff --git a/api-management/streams-end-to-end-example.mdx b/api-management/streams-end-to-end-example.mdx new file mode 100644 index 0000000000..e3dddfcfbd --- /dev/null +++ b/api-management/streams-end-to-end-example.mdx @@ -0,0 +1,230 @@ +--- +title: "Tyk Streams End-to-End Example" +description: "A comprehensive end-to-end example of Tyk Streams implementation" +keywords: "Tyk Streams, Event-Driven APIs, Kafka, WebSockets, SSE, Correlation IDs" +sidebarTitle: "Tyk Streams End-to-End Example" +--- + +
+ +## Why Tyk Streams? + +Tyk Streams adds a **declarative event layer** on top of the Tyk Gateway, letting you expose or consume broker topics (Kafka, NATS, RabbitMQ…) through normal HTTP channels—REST, WebSocket, Server-Sent Events—without glue code. + +You can manage stream definitions in three interchangeable ways: + +| Method | When to use | +| :-------- | :------------- | +| **Tyk Dashboard UI** | Rapid prototyping and PoCs | +| **OpenAPI + `x-tyk-streaming`** | “Everything-as-code”, safe for Git | +| **Tyk Operator (Kubernetes CRD)** | GitOps & CI/CD pipelines | + +--- + +## Requirements + +* **Tyk Gateway ≥ 5.8** with **Streams** feature enabled +* **Apache Kafka** reachable on `localhost:9093` +* *(Optional)* **Prometheus** and **Jaeger** if you enable the commented observability blocks + +--- + +## Architecture + +The demo shows a classic pattern: a user request becomes an event on a bus, a worker processes it asynchronously, and the result is delivered back to the same user—without leaking data across tenants. + +```mermaid +sequenceDiagram + autonumber + participant Client + participant GatewayIn as Gateway
in stream + participant KafkaJobs as Kafka
topic **jobs** + participant Worker as Worker
Worker stream + participant KafkaCompleted as Kafka
topic **completed** + participant GatewayOut as Gateway
out stream + + %% synchronous request from client + Client ->> GatewayIn: POST /push-event **(sync)** + GatewayIn -->> Client: 200 OK + echo **(sync)** + + %% asynchronous event flow + GatewayIn -->> KafkaJobs: publish event **(async)** + KafkaJobs -->> Worker: consume job **(async)** + Worker -->> KafkaCompleted: publish result **(async)** + KafkaCompleted -->> GatewayOut: consume result **(async)** + + %% synchronous delivery back to the same user + GatewayOut ->> Client: GET /get-event / WS / SSE **(sync)** +``` + +### Stream-per-responsibility pattern + +| Stream | Role | Input | Output | +| :-------- | :------ | :------- | :-------- | +| **`in`** | Edge entrypoint: accepts HTTP, enriches payload (`user_id`, `job_id`), publishes to **`jobs`** and echoes to caller | HTTP | Kafka + sync response | +| **`Worker`** | Background micro-service: listens to **`jobs`**, attaches `result: "bar"`, publishes to **`completed`** | Kafka | Kafka | +| **`out`** | Edge exit point: listens to **`completed`**, drops messages not owned by caller, delivers via REST/WS/SSE | Kafka | HTTP | + +--- + +## Processor mapping (built-in scripting) + +Streams pipelines include **processors**. The *mapping* processor embeds [Bloblang](https://www.benthos.dev/docs/guides/bloblang/about/) so you can transform or filter messages inline: + +* **Enrich** – `in` adds `user_id` & `job_id` +* **Augment** – `Worker` adds a static field `{ "result": "bar" }` +* **Filter** – `out` calls `deleted()` for non-matching users + +Dynamic placeholders (`$tyk_context.…`) can reference query params, headers, JWT claims, or any other context variable—usable anywhere in the Streams config. + +--- + +## Observability (optional) + +Uncomment the `metrics:` and `tracer:` blocks to push per-stream Prometheus metrics and Jaeger traces. Tags like `stream: Worker` make end-to-end tracing trivial. + +--- + +## Full OpenAPI definition + +Copy/paste into `streams-demo.yaml`, import via Dashboard UI, or apply with Tyk Operator: + +```yaml +info: + title: streams-demo + version: 1.0.0 +openapi: 3.0.3 +servers: + - url: http://tyk-gateway:8282/stream-demo/ +x-tyk-streaming: + streams: + Worker: + input: + kafka: + addresses: + - localhost:9093 + consumer_group: worker + topics: + - jobs + output: + kafka: + addresses: + - localhost:9093 + topic: completed + pipeline: + processors: + - mapping: | + root = this.merge({ "result": "bar" }) +# metrics: +# prometheus: +# push_interval: 1s +# push_job_name: Worker +# push_url: http://localhost:9091 +# tracer: +# jaeger: +# collector_url: http://localhost:14268/api/traces +# tags: +# stream: Worker + + in: + input: + http_server: + path: /push-event + ws_path: /ws-out + output: + broker: + outputs: + - kafka: + addresses: + - localhost:9093 + topic: jobs + - sync_response: {} + pipeline: + processors: + - mapping: | + root = this + root.user_id = "$tyk_context.request_data_user" # or $tyk_context.jwt.claims.sub + root.job_id = uuid_v4() +# tracer: +# jaeger: +# collector_url: http://localhost:14268/api/traces +# tags: +# stream: in +# metrics: +# prometheus: +# push_interval: 1s +# push_job_name: in +# push_url: http://localhost:9091 + + out: + input: + kafka: + addresses: + - localhost:9093 + consumer_group: $tyk_context.request_data_user + topics: + - completed + output: + http_server: + path: /get-event + ws_path: /ws-in + pipeline: + processors: + - mapping: | + root = if this.user_id != "$tyk_context.request_data_user" { + deleted() + } +# tracer: +# jaeger: +# collector_url: http://localhost:14268/api/traces +# tags: +# stream: out +# metrics: +# prometheus: +# push_interval: 1s +# push_job_name: out +# push_url: http://localhost:9091 +security: [] +paths: {} +components: + securitySchemes: {} +x-tyk-api-gateway: + info: + name: stream-demo + state: + active: true + internal: false + middleware: + global: + contextVariables: + enabled: true + trafficLogs: + enabled: true + server: + listenPath: + strip: true + value: /stream-demo/ + upstream: + proxy: + enabled: false + url: "" +``` + +--- + +## Running the demo + +1. **Start Kafka** (e.g. docker-compose). +2. **Launch Tyk Gateway 5.8+** with the YAML above. +3. **Send an event** + ```bash + curl -X POST "http://localhost:8282/stream-demo/push-event?user=alice" \ + -H "Content-Type: application/json" \ + -d '{"message":"hello world"}' + ``` +4. **Receive the result** (only *alice*’s jobs) + ```bash + curl "http://localhost:8282/stream-demo/get-event?user=alice" + ``` +5. **Switch transport** – connect via websocket `wscat -c http://127.0.0.1:8282/stream-demo/ws-in\?user\=alice` +6. *(Optional)* **Enable metrics & tracing** – uncomment blocks, restart Gateway, explore in Grafana & Jaeger. diff --git a/api-management/sync/quick-start.mdx b/api-management/sync/quick-start.mdx new file mode 100644 index 0000000000..ed7424e1ca --- /dev/null +++ b/api-management/sync/quick-start.mdx @@ -0,0 +1,100 @@ +--- +title: "Tyk Sync Quick Start Guide" +description: "Quick start guide for Tyk Sync to synchronize API configurations with Tyk Dashboard" +keywords: "Quick Start, Tyk Sync, API Management, Automations" +sidebarTitle: "Quick Start" +--- + +**Tyk Sync** is a command line tool and library to manage and synchronise a Tyk installation with your version control system (VCS). This guide will help you get started with Tyk Sync to manage your API configurations. + +## What We'll Cover in This Guide + +1. Set up Tyk Demo (gateway with prebuilt APIs) +2. Install Tyk Sync using Docker +3. Use Tyk Sync to dump API configurations from the Tyk Demo +4. Observe the dumped configurations +5. Make changes and sync back to Tyk Demo +6. Verify changes in Tyk Demo + +## Instructions + +### 1. Set Up Tyk Demo + +First, let's set up a Tyk Demo environment with some prebuilt APIs. Follow our Docker [guide](/tyk-self-managed/install/docker). + +This will start a Tyk Gateway and Dashboard with some sample APIs already configured. The Dashboard will be available at `http://localhost:3000`. + +### 2. Install Tyk Sync + +Follow this [guide](/product-stack/tyk-sync/installing-tyk-sync) to install Tyk Sync. You can either use the Docker image or download the binary directly. We will be using the Docker image for this quick start. + +### 3. Dump API Configurations + +Now, let's dump the API configurations from the Tyk Dashboard: + +```bash +# Create a directory to store your API configurations +mkdir -p tyk-sync-data + +# Get your Dashboard API key from the Dashboard UI (User menu > Profile) +# Replace YOUR_DASHBOARD_API_KEY with your actual key + +# Using Docker +docker run --rm -v $(pwd)/tyk-sync-data:/opt/tyk-sync/data --network=host tykio/tyk-sync:v2.1 dump -d="http://localhost:3000" -s="YOUR_DASHBOARD_API_KEY" -t="/opt/tyk-sync/data" + +# Or using the binary directly +tyk-sync dump -d="http://localhost:3000" -s="YOUR_DASHBOARD_API_KEY" -t="./tyk-sync-data" +``` + +This command will: +- Connect to your Tyk Dashboard at `http://localhost:3000` +- Use your Dashboard API key for authentication +- Extract all APIs and policies +- Save them to the `tyk-sync-data` directory + +### 4. Observe the Dumped Configurations + +Let's examine what was dumped: + +```bash +ls -la tyk-sync-data +``` + +You should see: +- A `.tyk.json` file (index file for synchronization) +- A `policies` directory containing policy definitions +- An `apis` directory containing API definitions + +Each API and policy is stored as a separate JSON file, making it easy to track changes in version control. + +### 5. Make Changes and Sync Back + +Now, let's modify an API definition and sync it back to the Dashboard: + +```bash +# Edit one of the API definition files +# For example, change the name of an API +# Then sync the changes back + +# Using Docker +docker run --rm -v $(pwd)/tyk-sync-data:/opt/tyk-sync/data --network=host tykio/tyk-sync:v2.1 update -d="http://localhost:3000" -s="YOUR_DASHBOARD_API_KEY" -p="/opt/tyk-sync/data" + +# Or using the binary directly +tyk-sync update -d="http://localhost:3000" -s="YOUR_DASHBOARD_API_KEY" -p="./tyk-sync-data" +``` + +This will update the API configurations in your Tyk Dashboard based on the local files. + +### 6. Verify Changes in Tyk Demo + +Open your Tyk Dashboard at `http://localhost:3000` and navigate to the APIs section. You should see that your changes have been applied. + +## Conclusion + +Tyk Sync provides a powerful way to manage your API configurations as code. By following this quick start guide, you've learned how to: +- Extract API configurations from a Tyk Dashboard +- Store them as files that can be version-controlled +- Modify and update configurations +- Synchronize configurations between different environments + +This approach helps ensure consistency across environments and enables you to implement Gitops for your API management. diff --git a/api-management/sync/use-cases.mdx b/api-management/sync/use-cases.mdx new file mode 100644 index 0000000000..fbee6f7936 --- /dev/null +++ b/api-management/sync/use-cases.mdx @@ -0,0 +1,268 @@ +--- +title: "Automate API Configuration Management with Tyk Sync" +description: "Learn how to automate API configuration management using Tyk Sync and GitHub Actions." +keywords: "Tyk Sync, GitHub Actions, API Management, Automations" +sidebarTitle: "Use Cases" +--- + +By integrating GitHub Actions, teams can schedule backups to cloud storage, sync configurations from a Git repository, and update local API definitions directly to the Tyk Dashboard. These workflows ensure configurations are securely maintained, aligned across environments, and easily managed within the API lifecycle. + +## Backup API Configurations with Github Actions +API platform teams can automate configuration backups using GitHub Actions. By setting up a scheduled GitHub Action, API configurations can be periodically exported and stored in cloud storage, like AWS S3. This approach ensures backups remain up-to-date, offering a reliable way to safeguard data and simplify restoration if needed. + + +### Create a GitHub Action workflow + +1. In your repository, create a new file `.github/workflows/tyk-backup.yml`. +2. Add the following content to the `tyk-backup.yml` file: + +```yaml +name: Tyk Backup + +on: + schedule: + - cron: '0 0 * * *' # Runs every day at midnight + +jobs: + backup: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Create Backup Directory + run: | + BACKUP_DIR="backup/$(date +%Y-%m-%d)" + mkdir -p $BACKUP_DIR + echo "BACKUP_DIR=$BACKUP_DIR" >> $GITHUB_ENV + + - name: Set Permissions for Backup Directory + run: | + sudo chown -R 1001:1001 ${{ github.workspace }}/backup + + - name: Dump API Configurations + run: | + docker run --user 1001:1001 -v ${{ github.workspace }}:/app/data tykio/tyk-sync:${TYK_SYNC_VERSION} dump --target /app/data/${{ env.BACKUP_DIR }} --dashboard ${TYK_DASHBOARD_URL} --secret ${TYK_DASHBOARD_SECRET} + env: + TYK_SYNC_VERSION: ${{ vars.TYK_SYNC_VERSION }} + TYK_DASHBOARD_URL: ${{ secrets.TYK_DASHBOARD_URL }} + TYK_DASHBOARD_SECRET: ${{ secrets.TYK_DASHBOARD_SECRET }} + + - name: Upload to S3 + uses: jakejarvis/s3-sync-action@v0.5.1 + with: + args: --acl private --follow-symlinks --delete + env: + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: 'us-east-1' # Change to your region + SOURCE_DIR: ${{ env.BACKUP_DIR }} +``` + +### Set up secrets + +1. Go to your GitHub repository. +2. Navigate to Settings > Secrets and variables > Actions. +3. Add the following variable: + - `TYK_SYNC_VERSION`: The version of Tyk Sync you want to use. +4. Add the following secrets: + - `TYK_DASHBOARD_URL`: The URL of your Tyk Dashboard. + - `TYK_DASHBOARD_SECRET`: The secret key for your Tyk Dashboard. + - `AWS_S3_BUCKET`: The name of your AWS S3 bucket. + - `AWS_ACCESS_KEY_ID`: Your AWS access key ID. + - `AWS_SECRET_ACCESS_KEY`: Your AWS secret access key. + +### Commit and push changes + +Commit the `tyk-backup.yml` file and push it to the main branch of your repository. + +### Verify backups + +The GitHub Action will run every day at midnight, dumping API configurations into a backup directory and uploading them to your specified S3 bucket. + + +## Synchronize API configurations with GitHub Actions +API platform teams can use GitHub Actions to sync API configurations, policies, and templates from a Git repository to Tyk. Triggered by repository changes, the action generates a .tyk.json file and applies updates with the sync command, keeping the Tyk setup aligned with the repository. + +### Setup GitHub repository +Organize your repository with the following structure: + +- `/apis/` for API definition files. +- `/policies/` for security policy files. +- `/assets/` for API template files. + +### Create a GitHub Action workflow + +1. In your repository, create a new file `.github/workflows/tyk-sync.yml`. +2. Add the following content to the `tyk-sync.yml` file: + +```yaml +name: Tyk Sync + +on: + push: + branches: + - main + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Create .tyk.json + run: | + echo '{' > .tyk.json + echo ' "type": "apidef",' >> .tyk.json + echo ' "files": [' >> .tyk.json + find . -type f -name '*.json' -path './apis/*' -exec echo ' {"file": "{}"},' \; | sed '$ s/,$//' >> .tyk.json + echo ' ],' >> .tyk.json + echo ' "policies": [' >> .tyk.json + find . -type f -name '*.json' -path './policies/*' -exec echo ' {"file": "{}"},' \; | sed '$ s/,$//' >> .tyk.json + echo ' ],' >> .tyk.json + echo ' "assets": [' >> .tyk.json + find . -type f -name '*.json' -path './assets/*' -exec echo ' {"file": "{}"},' \; | sed '$ s/,$//' >> .tyk.json + echo ' ]' >> .tyk.json + echo '}' >> .tyk.json + cat .tyk.json + + - name: Sync with Tyk + run: | + docker run tykio/tyk-sync:${TYK_SYNC_VERSION} version + docker run -v ${{ github.workspace }}:/app/data tykio/tyk-sync:${TYK_SYNC_VERSION} sync --path /app/data --dashboard ${TYK_DASHBOARD_URL} --secret ${TYK_DASHBOARD_SECRET} + env: + TYK_SYNC_VERSION: ${{ vars.TYK_SYNC_VERSION }} + TYK_DASHBOARD_URL: ${{ secrets.TYK_DASHBOARD_URL }} + TYK_DASHBOARD_SECRET: ${{ secrets.TYK_DASHBOARD_SECRET }} +``` + +### Set up secrets + +1. Go to your GitHub repository. +2. Navigate to Settings > Secrets and variables > Actions. +3. Add the following variable: + - `TYK_SYNC_VERSION`: The version of Tyk Sync you want to use (e.g., v2.0.0). +4. Add the following secrets: + - `TYK_DASHBOARD_URL`: The URL of your Tyk Dashboard. + - `TYK_DASHBOARD_SECRET`: The secret key for your Tyk Dashboard. + +### Commit and push changes + +Commit the `tyk-sync.yml` file and push it to the main branch of your repository. + +### Verify synchronisation + +Each time there is a change in the repository, the GitHub Action will be triggered. It will create the `.tyk.json` file including all JSON files in the repository and use the `sync` command to update the Tyk installation. + + +## Update API Definitions locally +For API developers managing definitions locally, Tyk Sync's publish or update commands can upload local API definitions directly to the Tyk Dashboard, streamlining updates and keeping definitions in sync during development. Follow these steps to update your API definitions locally. + +### Prepare your API Definition + +Create your API definition file and save it locally. For example, save it as *api1.json* in a directory structure of your choice. + +### Create a .tyk.json index file + +In the root directory of your API definitions, create a `.tyk.json` file to list all API definition files that Tyk Sync should process. + +Example `.tyk.json`: +```json +{ + "type": "apidef", + "files": [ + { + "file": "api1.json" + } + ] +} +``` + +### Install Tyk Sync via Docker + +If you haven't installed Tyk Sync, you can do so via Docker: + +```bash +docker pull tykio/tyk-sync:v2.0.0 +``` + +### Publish API Definitions to Tyk + +Use the `publish` command to upload your local API definitions to Tyk. Use Docker bind mounts to access your local files. + +```bash +docker run -v /path/to/your/directory:/app/data tykio/tyk-sync:v2.0.0 publish \ + --path /app/data \ + --dashboard [DASHBOARD_URL] \ + --secret [SECRET] +``` + +### Update API Definitions to Tyk + +Similarly, to update existing API definitions, use the update command. + +```bash +docker run -v /path/to/your/directory:/app/data tykio/tyk-sync:v2.0.0 update \ + --path /app/data \ + --dashboard [DASHBOARD_URL] \ + --secret [SECRET] +``` + +### Verify the update + +Log in to your Tyk Dashboard to verify that the API definitions have been published or updated successfully. + + +## Specify Source API Configurations +For the `sync`, `update`, and `publish` commands, you need to specify where Tyk Sync can get the source API configurations to update the target Tyk installation. You can store the source files either in a Git repository or the local file system. + +### Working with Git +For any Tyk Sync command that requires Git repository access, specify the Git repository as the first argument after the command. By default, Tyk Sync reads from the `master` branch. To specify a different branch, use the `--branch` or `-b` flag. If the Git repository requires connection using Secure Shell Protocol (SSH), you can specify SSH keys with `--key` or `-k` flag. + +```bash +tyk-sync [command] https://github.com/your-repo --branch develop +``` + +### Working with the local file system +To update API configurations from the local file system, use the `--path` or `-p` flag to specify the source directory for your API configuration files. + +```bash +tyk-sync [command] --path /path/to/local/directory +``` + +### Index File Requirement +A `.tyk.json` index file is required at the root of the source Git repository or the specified path. This `.tyk.json` file lists all the files that should be processed by Tyk Sync. + +Example `.tyk.json`: +```json +{ + "type": "apidef", + "files": [ + { + "file": "api1/api1.json" + }, + { + "file": "api2/api2.json" + }, + { + "file": "api3.json" + } + ], + "policies": [ + { + "file": "policy1.json" + } + ], + "assets": [ + { + "file": "template1.json" + } + ] +} +``` + + diff --git a/api-management/traces.mdx b/api-management/traces.mdx new file mode 100644 index 0000000000..4cdacd9a77 --- /dev/null +++ b/api-management/traces.mdx @@ -0,0 +1,463 @@ +--- +title: "Distributed Tracing in Tyk Gateway" +description: "Learn how to configure distributed tracing in Tyk for API observability, including integration with OpenTelemetry and third-party tracing tools." +keywords: "Distributed Tracing, OpenTelemetry, OTel, OTLP, Span Attributes, Context Propagation, Sampling, Jaeger, Datadog, Dynatrace, Observability, Tyk Gateway" +sidebarTitle: "Traces" +--- + +Distributed traces provide a detailed, end-to-end view of a single API request or transaction as it traverses through various services and components. Traces are crucial for understanding the flow of requests and identifying bottlenecks or latency issues. Here’s how you can make use of traces for API observability: + +- **End-to-end request tracing:** Implement distributed tracing across your microservices architecture to track requests across different services and gather data about each service's contribution to the overall request latency. + +- **Transaction Flow:** Visualize the transaction flow by connecting traces to show how requests move through different services, including entry points (e.g., API gateway), middleware and backend services. + +- **Latency Analysis:** Analyze trace data to pinpoint which service or component is causing latency issues, allowing for quick identification and remediation of performance bottlenecks. + +- **Error Correlation:** Use traces to correlate errors across different services to understand the root cause of issues and track how errors propagate through the system. + +## OpenTelemetry Tracing + +Since v5.2, Tyk Gateway supports distributed tracing via [OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/). The gateway exports traces using the [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/), making it compatible with any modern tracing backend: Jaeger, Datadog, Dynatrace, Elasticsearch, New Relic, and others. + +Client request flowing through Tyk Gateway, which exports traces via OTLP to an OpenTelemetry Collector, which forwards to Jaeger, Datadog, Dynatrace, or any OTLP-compatible backend + +Tyk also supports the legacy [OpenTracing](#opentracing-deprecated) approach (now deprecated). Migrate to OpenTelemetry for vendor-neutral, actively maintained tracing. + +## Configuration + +### Enable Tracing + +Enable OpenTelemetry tracing at the Gateway level in `tyk.conf`: + + + +```json +{ + "opentelemetry": { + "traces": { + "enabled": true + } + } +} +``` + + + Set the environment variable `TYK_GW_OPENTELEMETRY_TRACES_ENABLED=true`. + + + +Tyk Gateway will now generate two spans for each request made to your APIs, encapsulating the entire request lifecycle. These spans include attributes and tags but lack fine-grained details. The parent span represents the total time from request reception to response and the child span represent the time spent in the upstream service. + +Two spans generated per request with basic tracing enabled + +### Reference + + +The root-level `opentelemetry.enabled`, `opentelemetry.exporter`, and `opentelemetry.endpoint` fields are deprecated from Tyk 5.13.0. + +They continue to work for backward compatibility but should be replaced with the `opentelemetry.traces.*` equivalents in new deployments. + + +| Field | Description | Default | +|-------|-------------|---------| +| [opentelemetry.traces.enabled](/tyk-oss-gateway/configuration#opentelemetry-traces-enabled) | Enable distributed tracing | `false` | +| [opentelemetry.traces.exporter](/tyk-oss-gateway/configuration#opentelemetry-traces-exporter) | Export protocol: `grpc` or `http` | `grpc` | +| [opentelemetry.traces.endpoint](/tyk-oss-gateway/configuration#opentelemetry-traces-endpoint) | OTLP collector endpoint | `localhost:4317` | +| [opentelemetry.traces.connection_timeout](/tyk-oss-gateway/configuration#opentelemetry-traces-connection_timeout) | Connection timeout in seconds | `1` | +| [opentelemetry.traces.headers](/tyk-oss-gateway/configuration#opentelemetry-traces-headers) | Additional HTTP headers sent with each OTLP export request | — | +| [opentelemetry.traces.tls](/tyk-oss-gateway/configuration#opentelemetry-traces-tls) | TLS configuration for the OTLP connection | — | +| [opentelemetry.traces.context_propagation](/tyk-oss-gateway/configuration#opentelemetry-traces-context_propagation) | Trace context format: `tracecontext`, `b3`, `custom`, or `composite` | `tracecontext` | +| [opentelemetry.traces.custom_trace_header](/tyk-oss-gateway/configuration#opentelemetry-traces-custom_trace_header) | Custom propagation header name (used with `custom` or `composite` modes) | — | +| [opentelemetry.traces.span_processor_type](/tyk-oss-gateway/configuration#opentelemetry-traces-span_processor_type) | Span processing mode: `batch` or `simple` | `batch` | +| [opentelemetry.traces.span_batch_config.max_queue_size](/tyk-oss-gateway/configuration#opentelemetry-traces-span_batch_config-max_queue_size) | Maximum number of spans buffered before export | `2048` | +| [opentelemetry.traces.span_batch_config.max_export_batch_size](/tyk-oss-gateway/configuration#opentelemetry-traces-span_batch_config-max_export_batch_size) | Maximum number of spans per export batch | `512` | +| [opentelemetry.traces.span_batch_config.batch_timeout](/tyk-oss-gateway/configuration#opentelemetry-traces-span_batch_config-batch_timeout) | Seconds to wait before forcing an export | `5` | +| [opentelemetry.traces.sampling.type](/tyk-oss-gateway/configuration#opentelemetry-traces-sampling-type) | Sampling strategy: `AlwaysOn`, `AlwaysOff`, or `TraceIDRatioBased` | `AlwaysOn` | +| [opentelemetry.traces.sampling.rate](/tyk-oss-gateway/configuration#opentelemetry-traces-sampling-rate) | Fraction of traces to sample when using `TraceIDRatioBased` (0.0–1.0) | `0.5` | +| [opentelemetry.traces.sampling.parent_based](/tyk-oss-gateway/configuration#opentelemetry-traces-sampling-parent_based) | Inherit the parent span's sampling decision | `false` | + +### Detailed Tracing + +Enable detailed tracing per API to generate a span for each middleware in the request pipeline. These spans offer detailed insights, including the time taken for each middleware execution and the sequence of invocations. + +Set the [server.detailedTracing](/api-management/gateway-config-tyk-oas#detailedtracing) flag in the Tyk OAS API definition, or toggle **OpenTelemetry Tracing** in the Tyk OAS API Designer. + +OpenTelemetry Tracing toggle in the Tyk OAS API Designer + +Multiple middleware spans generated with detailed tracing enabled + +For Tyk Classic APIs, use the [detailed_tracing](/api-management/gateway-config-tyk-classic#opentelemetry) field in the API definition. + +### Span Processor Configuration + +A span processor controls how completed spans are batched and sent to the tracing backend. This is configured in the Tyk Gateway configuration file under `opentelemetry.traces`. + +When using `span_processor_type: batch` (the default), you can tune the batch processor to avoid span loss under high traffic. Use the `span_batch_config` block to configure the following fields: + +```json +{ + "opentelemetry": { + "traces": { + "span_processor_type": "batch", + "span_batch_config": { + "max_queue_size": 8192, + "max_export_batch_size": 1024, + "batch_timeout": 3 + } + } + } +} +``` + +| Field | Default | Description | +|-------|---------|-------------| +| [`max_queue_size](/tyk-oss-gateway/configuration#opentelemetry-span_batch_config-max_queue_size) | `2048` | maximum number of spans buffered before export | +| [`max_export_batch_size](/tyk-oss-gateway/configuration#opentelemetry-span_batch_config-max_export_batch_size) | `512` | maximum number of spans per export batch | +| [`batch_timeout](/tyk-oss-gateway/configuration#opentelemetry-span_batch_config-batch_timeout) | `5` | seconds to wait before forcing an export | + +Increase `max_queue_size` and `max_export_batch_size` in high-throughput environments where spans are being dropped before they can be exported. + +## Understanding The Traces + +Tyk Gateway exposes a helpful set of *span attributes* and *resource attributes* with the generated spans. These attributes provide useful insights for analyzing your API requests. A clear analysis can be obtained by observing the specific actions and associated context within each request/response. This is where span and resource attributes play a significant role. + +### Span Attributes + +A span is a named, timed operation that represents an operation. Multiple spans represent different parts of the workflow and are pieced together to create a trace. While each span includes a duration indicating how long the operation took, the span attributes provide additional contextual metadata. + +Span attributes are key-value pairs that provide contextual metadata for individual spans. Tyk automatically sets the following span attributes: + +- `tyk.api.name`: API name. +- `tyk.api.orgid`: Organization ID. +- `tyk.api.id`: API ID. +- `tyk.api.path`: API listen path. +- `tyk.api.tags`: If tagging is enabled in the API definition, the tags are added here. +- `tyk.api.apikey.alias`: The identity alias of the authenticated client. Populated for APIs using JWT authentication or multi-auth (compliant mode). + + +### Resource Attributes + +Resource attributes provide contextual information about the entity that produced the telemetry data. Tyk exposes following resource attributes: + +### Service Attributes + +The service attributes supported by Tyk are: + +| Attribute | Type | Description | +| :--------------------- | :-------- | :- | +| `service.name` | String | Service name for Tyk API Gateway: `tyk-gateway` | +| `service.instance.id` and `tyk.gw.id` | String | The Node ID assigned to the gateway. Example `solo-6b71c2de-5a3c-4ad3-4b54-d34d78c1f7a3` | +| `service.version` | String | Represents the service version. Example `v5.2.0` | +| `tyk.gw.dataplane` | Bool | Whether the Tyk Gateway is hybrid (`slave_options.use_rpc=true`) | +| `tyk.gw.group.id` | String | Represents the `slave_options.group_id` of the gateway. Populated only if the gateway is hybrid. | +| `tyk.gw.tags` | []String | Represents the gateway `segment_tags`. Populated only if the gateway is segmented. | + +By understanding and using these resource attributes, you can gain better insights into the performance of your API Gateways. + +### Common HTTP Span Attributes + +Tyk follows the OpenTelemetry semantic conventions for HTTP spans. You can find detailed information on common attributes [here](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#common-attributes). + +Some of these common attributes include: + +- `http.method`: HTTP request method. +- `http.scheme`: URL scheme. +- `http.status_code`: HTTP response status code. +- `http.url`: Full HTTP request URL. + +For the full list and details, refer to the official [OpenTelemetry Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#common-attributes). + +## Advanced Configuration + +### Context Propagation + +This setting allows you to specify the type of context propagator to use for trace data. This is essential for ensuring compatibility and data integrity between different services in your architecture. + + + + +[opentelemetry.traces.context_propagation](/tyk-oss-gateway/configuration#opentelemetry-traces-context_propagation) controls which trace context format the gateway reads from incoming requests and writes to upstream requests. + +```json +{ + "opentelemetry": { + "traces": { + "context_propagation": "" + } + } +} +``` + + + Set the environment variable [TYK_GW_OPENTELEMETRY_TRACES_CONTEXTPROPAGATION](/tyk-oss-gateway/configuration#opentelemetry-context_propagation). + + + +The available options are: + +| Value | Behavior | +|-------|-----------| +| `tracecontext` (default) | [W3C Trace Context](https://www.w3.org/TR/trace-context/) format | +| `b3` | [B3 multi-header](https://github.com/openzipkin/b3-propagation) format | +| `custom` | Reads and writes only the custom header set in `custom_trace_header`. No standard headers written to upstream | +| `composite` | Reads from the custom header (priority) or standard headers (fallback). Writes both the custom header and `traceparent` to upstream | + + +`custom` and `composite` are available from Tyk Gateway v5.12.0 and require [opentelemetry.custom_trace_header](/tyk-oss-gateway/configuration#opentelemetry) to be set. + + +The diagram below shows which headers the Gateway reads from the client and writes to the upstream for each mode: + +```mermaid +sequenceDiagram + participant C as Client + participant G as Tyk Gateway + participant U as Upstream + + rect rgb(230, 240, 255) + Note over C,U: tracecontext (default) + C->>G: Request + traceparent + G->>U: Request + traceparent + end + + rect rgb(230, 255, 235) + Note over C,U: custom + custom_trace_header: X-Correlation-ID + C->>G: Request + X-Correlation-ID + G->>U: Request + X-Correlation-ID (no standard headers) + end + + rect rgb(255, 245, 220) + Note over C,U: composite + custom_trace_header: X-Correlation-ID + C->>G: Request + X-Correlation-ID + G->>U: Request + X-Correlation-ID + traceparent + end +``` + +### Custom Trace Header + +If your upstream systems use a proprietary correlation header (for example, `X-Correlation-ID`), set `custom_trace_header` to that header name. The behavior depends on `context_propagation`: + + + +**Tracecontext mode** (`tracecontext` + `custom_trace_header`): reads from the custom header (with fallback to `traceparent`), writes only the standard `traceparent` header to upstream. + +```json +{ + "opentelemetry": { + "traces": { + "enabled": true, + "context_propagation": "tracecontext", + "custom_trace_header": "X-Correlation-ID" + } + } +} +``` + + +**Custom mode** (`custom` + `custom_trace_header`) — reads from and writes to the custom header only. No standard headers are involved. + +```json +{ + "opentelemetry": { + "traces": { + "enabled": true, + "context_propagation": "custom", + "custom_trace_header": "X-Correlation-ID" + } + } +} +``` + + +**Composite mode** (`composite` + `custom_trace_header`) — reads from the custom header (priority) or standard headers (fallback), and writes both the custom header and `traceparent` to upstream. + +```json +{ + "opentelemetry": { + "traces": { + "enabled": true, + "context_propagation": "composite", + "custom_trace_header": "X-Correlation-ID" + } + } +} +``` + + + +In all modes, if the custom header is absent from the incoming request, the gateway falls back to standard headers or generates a new trace ID. The custom header is never generated if it wasn't present in the original request. + +### Sampling + +Tyk supports configuring the following sampling strategies using [opentelemetry.sampling](/tyk-oss-gateway/configuration#opentelemetry) in `tyk.conf`. + +```mermaid +flowchart LR + Req["Incoming Request"] --> Type{"opentelemetry.sampling.type"} + Type -- AlwaysOn --> Sampled["Sampled"] + Type -- AlwaysOff --> Dropped["Not Sampled"] + Type -- TraceIDRatioBased --> PB{"parent_based enabled?"} + PB -- No --> Ratio{"random value\n< sampling.rate?"} + PB -- Yes --> Parent{"Has parent span?"} + Parent -- Yes --> Inherit["Inherit parent's sampling decision"] + Parent -- No --> Ratio + Ratio -- Yes --> Sampled + Ratio -- No --> Dropped + + style Sampled fill:#22c55e,color:#fff + style Dropped fill:#ef4444,color:#fff +``` + +#### Sampling Type + +This setting dictates the sampling policy that OpenTelemetry uses to decide if a trace should be sampled for analysis. The decision is made at the start of a trace and applies throughout its lifetime. By default, the setting is `AlwaysOn`. + +Set the [opentelemetry.traces.sampling.type](/tyk-oss-gateway/configuration#opentelemetry-traces-sampling-type) field in the Tyk Gateway configuration file or use the equivalent environment variable. Allowed values for this setting are: + +| Value | Behavior | +|-------|-----------| +| `AlwaysOn` (default) | All traces are sampled | +| `AlwaysOff` | No traces are sampled | +| `TraceIDRatioBased` | Samples a fraction of traces based on the configured [sampling rate](/api-management/traces#sampling-rate) | + +#### Sampling Rate + +The [opentelemetry.traces.sampling.rate](/tyk-oss-gateway/configuration#opentelemetry-traces-sampling-rate) field is used to control what fraction of total traces will be sampled when the `TraceIDRatioBased` sampling is configured. It accepts a value between 0.0 and 1.0. For example, a `rate` set to 0.5 implies that approximately 50% of the traces will be sampled. The default value is 0.5. + +- **Configuration File**: Update the `opentelemetry.traces.sampling.rate` field in the configuration file. + +#### ParentBased Sampling + +Parent based sampling ensures sampling consistency between parent and child spans. Specifically, if a parent span is sampled, all its child spans will be sampled at the same rate. + +This is particularly effective when used with `TraceIDRatioBased` sampling, as it helps to keep the entire transaction story together. Using `ParentBased` with `AlwaysOn` or `AlwaysOff` may not be as useful, since in these cases, either all or no spans are sampled. + +Enable parent based sampling by setting [opentelemetry.traces.sampling.parent_based](/tyk-oss-gateway/configuration#opentelemetry-traces-sampling-parent_based) or the equivalent environment variable. The default value is `false`. + +## Tracing Backends + +For step-by-step setup guides connecting Tyk Gateway traces to a specific backend: + +- [Datadog](/api-management/traces/datadog) +- [Dynatrace](/api-management/traces/dynatrace) +- [Elasticsearch](/api-management/traces/elasticsearch) +- [New Relic](/api-management/traces/new-relic) +- [Jaeger](/api-management/traces/jaeger) + +All configuration options are documented in the [Tyk Gateway configuration reference](/tyk-oss-gateway/configuration#opentelemetry). + +## OpenTracing (deprecated) + + +**Deprecation** + +The CNCF has archived the OpenTracing project. Tyk introduced [OpenTelemetry](#opentelemetry) support in v5.2. + +Migrate to OpenTelemetry for active support and wider vendor compatibility. OpenTracing is deprecated in all Tyk products. + + +### Enabling OpenTracing + +Configure OpenTracing at the Gateway level in `tyk.conf`: + +```json +{ + "tracing": { + "enabled": true, + "name": "${tracer_name}", + "options": {} + } +} +``` + +| Field | Environment variable | Description | +|-------|----------------------|-------------| +| `tracing.enabled` | `TYK_GW_TRACER_ENABLED` | Set to `true` to enable tracing | +| `tracing.name` | `TYK_GW_TRACER_NAME` | Name of the supported tracer | +| `tracing.options` | `TYK_GW_TRACER_OPTIONS` | Key-value pairs for configuring the tracer. See the tracer's documentation for details | + +Tyk automatically propagates tracing headers to upstream APIs when tracing is enabled. + +### Legacy Vendor Configurations + + + + + Tyk's OpenTelemetry tracing works with Jaeger. Follow the [Jaeger guide](/api-management/traces/jaeger) instead. + + + Prior to Tyk 5.2, use [OpenTracing](https://opentracing.io/) with the [Jaeger client libraries](https://www.jaegertracing.io/docs/1.11/client-libraries/). + + ```json + { + "tracing": { + "enabled": true, + "name": "jaeger", + "options": { + "baggage_restrictions": null, + "disabled": false, + "headers": null, + "reporter": { + "BufferFlushInterval": "0s", + "collectorEndpoint": "", + "localAgentHostPort": "jaeger:6831", + "logSpans": true, + "password": "", + "queueSize": 0, + "user": "" + }, + "rpc_metrics": false, + "sampler": { + "maxOperations": 0, + "param": 1, + "samplingRefreshInterval": "0s", + "samplingServerURL": "", + "type": "const" + }, + "serviceName": "tyk-gateway", + "tags": null, + "throttler": null + } + } + } + ``` + + + + Tyk's OpenTelemetry tracing works with New Relic. Follow the [New Relic guide](/api-management/traces/new-relic) instead. + + + Prior to Tyk 5.2, use [OpenTracing](https://opentracing.io/) with Zipkin format to send traces to New Relic. + + ```json + { + "tracing": { + "enabled": true, + "name": "zipkin", + "options": { + "reporter": { + "url": "https://trace-api.newrelic.com/trace/v1?Api-Key=NEW_RELIC_LICENSE_KEY&Data-Format=zipkin&Data-Format-Version=2" + } + } + } + } + ``` + + + Prior to Tyk 5.2, use [OpenTracing](https://opentracing.io/) with the [Zipkin Go tracer](https://zipkin.io/pages/tracers_instrumentation). + + ```json + { + "tracing": { + "enabled": true, + "name": "zipkin", + "options": { + "reporter": { + "url": "http://localhost:9411/api/v2/spans" + } + } + } + } + ``` + + diff --git a/api-management/traces/datadog.mdx b/api-management/traces/datadog.mdx new file mode 100644 index 0000000000..19f464fff3 --- /dev/null +++ b/api-management/traces/datadog.mdx @@ -0,0 +1,157 @@ +--- +title: "Send Tyk Traces to Datadog" +description: "Step-by-step guide to connect Tyk Gateway distributed traces to Datadog using the OpenTelemetry Collector." +keywords: "Distributed Tracing, Datadog, OpenTelemetry, OTLP, OTel Collector, Tyk Gateway, APM, Observability" +sidebarTitle: "Datadog" +--- + +This guide explains how to set up Datadog to ingest OpenTelemetry traces via the OpenTelemetry Collector (OTel Collector) using Docker. It follows the reference documentation from [Datadog](https://docs.datadoghq.com/opentelemetry/otel_collector_datadog_exporter/?tab=onahost). + +While this tutorial demonstrates using an OpenTelemetry Collector running in Docker, the core concepts remain consistent regardless of how and where the OpenTelemetry collector is deployed. + +## Prerequisites + +- [Docker installed on your machine](https://docs.docker.com/get-docker/) +- Tyk Gateway v5.2.0 or higher +- OpenTelemetry Collector Contrib [docker image](https://hub.docker.com/r/otel/opentelemetry-collector-contrib). Make sure to use the Contrib distribution of the OpenTelemetry Collector as it is required for the [Datadog exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/datadogexporter). +- An [API key from Datadog](https://docs.datadoghq.com/account_management/api-app-keys/#add-an-api-key-or-client-token). For example, `6c35dacbf2e16aa8cda85a58d9015c3c`. +- Your [Datadog site](https://docs.datadoghq.com/getting_started/site/#access-the-datadog-site). Examples are: `datadoghq.com`, `us3.datadoghq.com` and `datadoghq.eu`. + +## Instructions + +### Step 1. Configure the OpenTelemetry Collector + + Create a new YAML configuration file named `otel-collector.yml` with the following content: + + ```yaml + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + processors: + batch: + send_batch_max_size: 100 + send_batch_size: 10 + timeout: 10s + exporters: + datadog: + api: + site: "YOUR-DATADOG-SITE" + key: "YOUR-DATAGOG-API-KEY" + service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [datadog] + ``` + +### Step 2. Configure a test API + + If you don't have any APIs configured yet, create a subdirectory called `apps` in the current directory. Create a new file `apidef-hello-world.json` and copy this very simple API definition for testing purposes: + + ```json + { + "name": "Hello-World", + "slug": "hello-world", + "api_id": "Hello-World", + "org_id": "1", + "use_keyless": true, + "detailed_tracing": true, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "use_extended_paths": true + } + } + }, + "proxy": { + "listen_path": "/hello-world/", + "target_url": "http://httpbin.org/", + "strip_listen_path": true + }, + "active": true + } + ``` + +### Step 3. Create the Docker-Compose file + + Save the following YAML configuration to a file named `docker-compose.yml`. + + ```yaml + version: "2" + services: + # OpenTelemetry Collector Contrib + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + volumes: + - ./otel-collector.yml:/etc/otel-collector.yml + command: ["--config=/etc/otel-collector.yml"] + ports: + - "4317" # OTLP gRPC receiver + networks: + - tyk + + # Tyk API Gateway, open-source deployment + tyk: + image: tykio/tyk-gateway:v5.2 + ports: + - 8080:8080 + environment: + - TYK_GW_OPENTELEMETRY_ENABLED=true + - TYK_GW_OPENTELEMETRY_EXPORTER=grpc + - TYK_GW_OPENTELEMETRY_ENDPOINT=otel-collector:4317 + volumes: + - ./apps:/opt/tyk-gateway/apps + depends_on: + - redis + networks: + - tyk + + redis: + image: redis:4.0-alpine + ports: + - 6379:6379 + command: redis-server --appendonly yes + networks: + - tyk + + networks: + tyk: + ``` + + + To start the services, go to the directory that contains the docker-compose.yml file and run the following command: + + ```bash + docker-compose up + ``` + +### Step 4. Explore OpenTelemetry traces in Datadog + + 1. Send a few requests to the API endpoint configured in step 2: + `` + http://localhost:8080/hello-world/ + `` + + 2. Log in to Datadog and navigate to the **APM / Traces** section. Here, you should start observing traces generated by Tyk: + + Tyk API Gateway distributed trace in Datadog + + 3. Click on a trace to view all its internal spans: + + Tyk API Gateway spans in Datadog + + 4. Datadog will generate a service entry to monitor Tyk Gateway and will automatically compute valuable metrics using the ingested traces.4 + + Tyk API Gateway service monitoring in Datadog + +## Troubleshooting + +If you do not see any traces from Tyk appearing in Datadog, consider the following steps for resolution: + +- Logging: Examine logs from Tyk Gateway and from the OpenTelemetry Collector for any issues or warnings that might provide insights. +- Data Ingestion Delays: Be patient, as there could be some delay in data ingestion. We configured a 10 second timeout in the batch processing of the OpenTelemetry collector in step 1, so give the system time to process the data. diff --git a/api-management/traces/dynatrace.mdx b/api-management/traces/dynatrace.mdx new file mode 100644 index 0000000000..a4f4341545 --- /dev/null +++ b/api-management/traces/dynatrace.mdx @@ -0,0 +1,124 @@ +--- +title: "Send Tyk Traces to Dynatrace" +description: "Step-by-step guide to connect Tyk Gateway distributed traces to Dynatrace using the OpenTelemetry Collector and Docker." +keywords: "Distributed Tracing, Dynatrace, OpenTelemetry, OTLP, OTel Collector, Tyk Gateway, APM, Observability" +sidebarTitle: "Dynatrace" +--- + +This guide explains how to set up Dynatrace to ingest OpenTelemetry traces via the OpenTelemetry Collector (OTel Collector) using Docker. + +## Prerequisites + +- [Docker installed on your machine](https://docs.docker.com/get-docker/) +- [Dynatrace account](https://www.dynatrace.com/) +- Dynatrace Token +- Gateway v5.2.0 or higher +- OTel Collector [docker image](https://hub.docker.com/r/otel/opentelemetry-collector) + +## Instructions + +### Step 1. Generate Dynatrace Token + + 1. In the Dynatrace console, navigate to access keys. + 2. Click on _Create a new key_ + 3. You will be prompted to select a scope. Choose _Ingest OpenTelemetry_ traces. + 4. Save the generated token securely; it cannot be retrieved once lost. + Example of a generated token ([taken from Dynatrace website](https://www.dynatrace.com/support/help/dynatrace-api/basics/dynatrace-api-authentication#token-format-example)): + + ```bash + dt0s01.ST2EY72KQINMH574WMNVI7YN.G3DFPBEJYMODIDAEX454M7YWBUVEFOWKPRVMWFASS64NFH52PX6BNDVFFM572RZM + ``` + +### Step 2. Configuration Files + + 1. **OTel Collector Configuration File** + + Create a YAML file named `otel-collector-config.yml`. In this file replace `` with the string from the address bar when you log into Dynatrace. Replace `` with the token you generated earlier. + + Here's a sample configuration file: + + ```yaml expandable + receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + grpc: + endpoint: 0.0.0.0:4317 + processors: + batch: + exporters: + otlphttp: + endpoint: "https://.live.dynatrace.com/api/v2/otlp" + headers: + Authorization: "Api-Token " # You must keep 'Api-Token', just modify + extensions: + health_check: + pprof: + endpoint: :1888 + zpages: + endpoint: :55679 + service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlphttp] + ``` + + 2. **Docker Compose File** + + Create a file named docker-compose.yml. + + Here is the sample Docker Compose file: + + ```yaml expandable + version: "3.9" + services: + otel-collector: + image: otel/opentelemetry-collector:latest + volumes: + - ./configs/otel-collector-config.yml:/etc/otel-collector.yml + command: ["--config=/etc/otel-collector.yml"] + networks: + - tyk + ports: + - "1888:1888" # pprof extension + - "13133:13133" # health_check extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + - "55670:55679" # zpages extension + networks: + tyk: + ``` + +### Step 3. Testing and Viewing Traces + + 1. Launch the Docker containers: docker-compose up -d + + 2. Initialize your Tyk environment. + + 3. Deploy a simple HTTP API on your Tyk Gateway. + + 4. Use cURL or Postman to send requests to the Gateway. + + 5. Navigate to Dynatrace -> Services -> Tyk-Gateway. + + Dynatrace Services + + 6. Wait for 5 minutes and refresh. + + 7. Traces, along with graphs, should appear. If they don't, click on the "Full Search" button. + + Dynatrace Metrics + +And there you have it! You've successfully integrated Dynatrace with the OpenTelemetry Collector using Docker. + +## Troubleshooting + +If traces are not appearing: +- try clicking on the "Full Search" button after waiting for 5 minutes. +- Make sure your Dynatrace token is correct in the configuration files. + - Validate the Docker Compose setup by checking the logs for any errors: `docker-compose logs` + diff --git a/api-management/traces/elasticsearch.mdx b/api-management/traces/elasticsearch.mdx new file mode 100644 index 0000000000..8c84d5e271 --- /dev/null +++ b/api-management/traces/elasticsearch.mdx @@ -0,0 +1,112 @@ +--- +title: "Send Tyk Traces to Elasticsearch" +description: "Step-by-step guide to connect Tyk Gateway distributed traces to Elasticsearch using the OpenTelemetry Collector, for OSS, self-managed, and hybrid deployments." +keywords: "Distributed Tracing, Elasticsearch, Elastic APM, OpenTelemetry, OTLP, OTel Collector, Tyk Gateway, Observability" +sidebarTitle: "Elasticsearch" +--- + +This guide explains how to set up [Elasticsearch](https://www.elastic.co/observability) to ingest OpenTelemetry traces via the OpenTelemetry Collector (OTel Collector). + +## Prerequisites + +Ensure the following prerequisites are met before proceeding: + +- Tyk Gateway v5.2 or higher +- OpenTelemetry Collector deployed locally +- Elasticsearch deployed locally or an account on Elastic Cloud with Elastic APM + +Elastic Observability natively supports OpenTelemetry and its OpenTelemetry protocol (OTLP) to ingest traces, metrics, and logs. + +OpenTelemetry support in Elasticsearch +Credit: Elasticsearch, [OpenTelemetry on Elastic](https://www.elastic.co/blog/opentelemetry-observability) + +## Steps for Configuration + +### Step 1. Configure Tyk Gateway + + + To enable OpenTelemetry when using Tyk Helm Charts add the following configuration to the Tyk Gateway section: + + ```yaml + tyk-gateway: + gateway: + opentelemetry: + enabled: true + endpoint: {{Add your endpoint here}} + exporter: grpc + ``` + + To enable OpenTelemetry when using Docker Compose add the following environment variables to your `docker-compose.yml` file for Tyk Gateway: + + ```yaml + environment: + - TYK_GW_OPENTELEMETRY_ENABLED=true + - TYK_GW_OPENTELEMETRY_EXPORTER=grpc + - TYK_GW_OPENTELEMETRY_ENDPOINT={{Add your endpoint here}} + ``` + + + For both deployment types, make sure to replace `` with the appropriate endpoint from your OpenTelemetry collector. + + + After enabling OpenTelemetry for the Gateway, you can activate [detailed tracing](/api-management/traces#detailed-tracing) for specific APIs in their respective API definitions. Detailed tracing is not enabled by default so you will need to set the `detailed_tracing` option to `true` to collect detailed traces. + +### Step 2. Configure the OpenTelemetry Collector to Export to Elasticsearch + + To configure the OTel Collector with Elasticsearch Cloud, follow these steps: + + 1. Sign up for an [Elastic account](https://www.elastic.co/) if you haven't already + 2. Once logged in to your Elastic account, select **Observability** and click on the option **Monitor my application performance** + + Configure Elasticsearch + + 3. Scroll down to the **APM Agents** section and click on the **OpenTelemetry** tab + + Configure Elasticsearch + + 4. Search for the section **Configure OpenTelemetry in your application"** You will need to copy the value of `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_HEADERS` in your OpenTelemetry Collector configuration file. + + Configure Elasticsearch + + 5. Update your OpenTelemetry Collector configuration, here's a simple example: + + ```yaml expandable + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 # OpenTelemetry receiver endpoint + processors: + batch: + exporters: + otlp/elastic: + endpoint: "ELASTIC_APM_SERVER_ENDPOINT_GOES_HERE" #exclude scheme, e.g. HTTPS:// or HTTP:// + headers: + # Elastic APM Server secret token + Authorization: "Bearer ELASTIC_APM_SECRET_TOKEN_GOES_HERE" + service: + pipelines: + traces: + receivers: [otlp] + exporters: [otlp/elastic] + ``` + + If are running Elasticsearch locally, you will need to use your APM Server endpoint (elastic-apm-server:8200) and set up [a secret token authorization in ElasticSearch](https://www.elastic.co/guide/en/observability/current/secret-token.html). + + You can refer to the [example configuration provided by Elastic](https://www.elastic.co/guide/en/observability/current/open-telemetry-direct.html#connect-open-telemetry-collector) for more guidance on the OpenTelemetry Collector configuration. + +### Step 3. Explore OpenTelemetry Traces in Elasticsearch + + In Elasticsearch Cloud: + 1. Go to **Home** and select **Observability**. + Configure Elasticsearch + 2. On the right menu, click on **APM / Services**. + 3. Click on **tyk-gateway**. + + 4. You will see a dashboard automatically generated based on the distributed traces sent by Tyk Gateway to Elasticsearch. + + Configure Elasticsearch + + Select a transaction to view more details, including the distributed traces: + + Configure Elasticsearch diff --git a/api-management/traces/jaeger.mdx b/api-management/traces/jaeger.mdx new file mode 100644 index 0000000000..31fb97a35e --- /dev/null +++ b/api-management/traces/jaeger.mdx @@ -0,0 +1,285 @@ +--- +title: "Send Tyk Traces to Jaeger" +description: "Step-by-step guide to connect Tyk Gateway distributed traces to Jaeger using Docker or Kubernetes." +keywords: "Distributed Tracing, Jaeger, OpenTelemetry, OTLP, Tyk Gateway, Kubernetes, Docker, Observability" +sidebarTitle: "Jaeger" +--- + +## Using Docker + +This guide explains how to set up [Jaeger](https://www.jaegertracing.io/) to ingest OpenTelemetry traces via the OpenTelemetry Collector (OTel Collector) using Docker. We will cover the installation of essential components, their configuration, and the process of ensuring seamless integration. + +For Kubernetes instructions, please refer to [How to integrate with Jaeger on Kubernetes](#using-kubernetes). + +### Prerequisites + +Ensure the following prerequisites are met before proceeding: + +- [Docker installed on your machine](https://docs.docker.com/get-docker/) +- Gateway v5.2.0 or higher + +### Steps for Configuration + +1. **Create the Docker-Compose File for Jaeger** + + Save the following YAML configuration in a file named docker-compose.yml: + + ```yaml + version: "2" + services: + # Jaeger: Distributed Tracing System + jaeger-all-in-one: + image: jaegertracing/all-in-one:latest + ports: + - "16686:16686" # Jaeger UI + - "4317:4317" # OTLP receiver + ``` + + This configuration sets up Jaeger's all-in-one instance with ports exposed for Jaeger UI and the OTLP receiver. + +2. **Deploy a Test API Definition** + + If you haven't configured any APIs yet, follow these steps: + + - Create a subdirectory named apps in the current directory. + - Create a new file named `apidef-hello-world.json`. + - Copy the provided simple API definition below into the `apidef-hello-world.json` file: + + + ```json + { + "name": "Hello-World", + "slug": "hello-world", + "api_id": "Hello-World", + "org_id": "1", + "use_keyless": true, + "detailed_tracing": true, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default", + "use_extended_paths": true + } + } + }, + "proxy": { + "listen_path": "/hello-world/", + "target_url": "http://httpbin.org/", + "strip_listen_path": true + }, + "active": true + } + ``` + + This API definition sets up a basic API named Hello-World for testing purposes, configured to proxy requests to `http://httpbin.org/`. + +3. **Run Tyk Gateway OSS with OpenTelemetry Enabled** + + To run Tyk Gateway with OpenTelemetry integration, extend the previous Docker Compose file to include Tyk Gateway and Redis services. Follow these steps: + + - Add the following configuration to your existing docker-compose.yml file: + + ```yaml + # ... Existing docker-compose.yml content for jaeger + + tyk: + image: tykio/tyk-gateway:v5.2.0 + ports: + - 8080:8080 + environment: + - TYK_GW_OPENTELEMETRY_ENABLED=true + - TYK_GW_OPENTELEMETRY_EXPORTER=grpc + - TYK_GW_OPENTELEMETRY_ENDPOINT=jaeger-all-in-one:4317 + volumes: + - ${TYK_APPS:-./apps}:/opt/tyk-gateway/apps + depends_on: + - redis + + redis: + image: redis:4.0-alpine + ports: + - 6379:6379 + command: redis-server --appendonly yes + ``` + + - Navigate to the directory containing the docker-compose.yml file in your terminal. + - Execute the following command to start the services: + + ```bash + docker compose up + ``` + +4. **Explore OpenTelemetry Traces in Jaeger** + + - Start by sending a few requests to the API endpoint configured in Step 2: + ```bash + curl http://localhost:8080/hello-world/ -i + ``` + + - Access Jaeger at [http://localhost:16686](http://localhost:16686). + - In Jaeger's interface: + - Select the service named tyk-gateway. + - Click the *Find Traces* button. + + You should observe traces generated by Tyk Gateway, showcasing the distributed tracing information. + + Tyk API Gateway distributed trace in Jaeger + + Select a trace to visualize its corresponding internal spans: + + Tyk API Gateway spans in Jaeger + + +## Using Kubernetes + +This guide explains how to set up [Jaeger](https://www.jaegertracing.io/) to ingest OpenTelemetry traces via the OpenTelemetry Collector (OTel Collector) using Kubernetes. We will cover the installation of essential components, their configuration, and the process of ensuring seamless integration. + +For Docker instructions, please refer to [How to integrate with Jaeger on Docker](#using-docker). + + +### Prerequisites + +Ensure the following prerequisites are in place before proceeding: + +- A functional Kubernetes cluster +- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) and [helm](https://helm.sh/docs/intro/install/) CLI tools installed + +### Steps for Configuration + +1. **Install Jaeger Operator** + + For the purpose of this tutorial, we will use jaeger-all-in-one, which includes the Jaeger agent, collector, query, and UI in a single pod with in-memory storage. This deployment is intended for development, testing, and demo purposes. Other deployment patterns can be found in the [Jaeger Operator documentation](https://www.jaegertracing.io/docs/1.51/operator/#deployment-strategies). + + + 1. Install the cert-manager release manifest (required by Jaeger) + + ```bash + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml + ``` + + 2. Install [Jaeger Operator](https://www.jaegertracing.io/docs/1.51/operator/). + + ```bash + kubectl create namespace observability + kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.51.0/jaeger-operator.yaml -n observability + + ``` + + 3. After the Jaeger Operator is deployed to the `observability` namespace, create a Jaeger instance: + + ```bash + kubectl apply -n observability -f - < + + + +Please make sure you are installing Redis versions that are supported by Tyk. Please refer to Tyk docs to get list of [supported versions](/tyk-self-managed/install#redis). + + + + + Tyk Gateway is now accessible through service `gateway-svc-tyk-oss-tyk-gateway` at port `8080` and exports the OpenTelemetry traces to the `jaeger-all-in-one-collector` service. + +3. **Deploy Tyk Operator** + + Deploy Tyk Operator to manage APIs in your cluster: + + ```bash + kubectl create namespace tyk-operator-system + kubectl create secret -n tyk-operator-system generic tyk-operator-conf \ + --from-literal "TYK_AUTH=$APISecret" \ + --from-literal "TYK_ORG=org" \ + --from-literal "TYK_MODE=ce" \ + --from-literal "TYK_URL=http://gateway-svc-tyk-otel-tyk-gateway.tyk.svc:8080" \ + --from-literal "TYK_TLS_INSECURE_SKIP_VERIFY=true" + helm install tyk-operator tyk-helm/tyk-operator -n tyk-operator-system + + ``` + +4. **Deploy a Test API Definition** + + Save the following API definition as `apidef-hello-world.yaml`: + + ```yaml + apiVersion: tyk.tyk.io/v1alpha1 + kind: ApiDefinition + metadata: + name: hello-world + spec: + name: hello-world + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org/ + listen_path: /hello-world + strip_listen_path: true + ``` + + To apply this API definition, run the following command: + + ```bash + kubectl apply -f apidef-hello-world.yaml + ``` + + This step deploys an API definition named *hello-world* using the provided configuration. It enables a keyless HTTP API proxying requests to `http://httpbin.org/` and accessible via the path `/hello-world`. + +5. **Explore OpenTelemetry traces in Jaeger** + + You can use the kubectl `port-forward command` to access Tyk and Jaeger services running in the cluster from your local machine's localhost: + + For Tyk Gateway: + + ```bash + kubectl port-forward service/gateway-svc-tyk-otel-tyk-gateway 8080:8080 -n tyk + ``` + + For Jaeger: + + ```bash + kubectl port-forward service/jaeger-all-in-one-query 16686 -n observability + ``` + + Begin by sending a few requests to the API endpoint configured in step 2: + + ```bash + curl http://localhost:8080/hello-world/ -i + ``` + + Next, navigate to Jaeger on `http://localhost:16686`, select the ´service´ called ´tyk-gateway´ and click on the button ´Find traces´. You should see traces generated by Tyk: + + Tyk Gateway distributed trace in Jaeger + + Click on a trace to view all its internal spans: + + Tyk Gateway spans in Jaeger \ No newline at end of file diff --git a/api-management/traces/new-relic.mdx b/api-management/traces/new-relic.mdx new file mode 100644 index 0000000000..2cea676f59 --- /dev/null +++ b/api-management/traces/new-relic.mdx @@ -0,0 +1,142 @@ +--- +title: "Send Tyk Traces to New Relic" +description: "Step-by-step guide to connect Tyk Gateway distributed traces to New Relic using the OpenTelemetry Collector." +keywords: "Distributed Tracing, New Relic, OpenTelemetry, OTLP, OTel Collector, Tyk Gateway, APM, Observability" +sidebarTitle: "New Relic" +--- + +This guide explains how to set up [New Relic](https://newrelic.com/) to ingest OpenTelemetry traces via the OpenTelemetry Collector (OTel Collector) using Docker. At the end of this guide, you will be able to visualize traces and metrics from your Tyk Gateway on the New Relic console. + +## Prerequisites + +- [Docker installed on your machine](https://docs.docker.com/get-docker/) +- [New Relic Account](https://newrelic.com/) +- New Relic API Key +- Gateway v5.2.0 or higher +- OTel Collector [docker image](https://hub.docker.com/r/otel/opentelemetry-collector) + +## Steps for Configuration + +### Step 1. Obtain New Relic API Key + + 1. Navigate to your New Relic Console. + + 2. Go to **Profile → API keys**. + + 3. Copy the key labeled as `INGEST-LICENSE`. + + + You can follow the [official New Relic documentation](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/) for more information. + + + **Example token:** + + ```bash + 93qwr27e49e168d3844c5h3d1e878a463f24NZJL + ``` + +### Step 2. Configuration Files + + **OTel Collector Configuration YAML** + + 1. Create a file named `otel-collector-config.yml` under the configs directory. + 2. Copy the following template into that file: + + ```yaml expandable + receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + grpc: + endpoint: 0.0.0.0:4317 + processors: + batch: + exporters: + otlphttp: + endpoint: "" + headers: + api-Key: "" + extensions: + health_check: + pprof: + endpoint: :1888 + zpages: + endpoint: :55679 + service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlphttp] + ``` + + - Replace `` with your specific New Relic endpoint (`https://otlp.nr-data.net` for US or `https://otlp.eu01.nr-data.net` for EU). + - Replace `` with the API key obtained in Step 1. + + **Docker Compose configuration** + + 1. Create a file named `docker-compose.yml` at the root level of your project directory. + + 2. Paste the following code into that file: + + ```yaml expandable + version: "3.9" + services: + otel-collector: + image: otel/opentelemetry-collector:latest + volumes: + - ./otel-collector-config.yml:/etc/otel-collector.yml + command: ["--config=/etc/otel-collector.yml"] + networks: + - tyk + ports: + - "1888:1888" # pprof extension + - "13133:13133" # health_check extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + - "55670:55679" # zpages extension + + networks: + tyk: + ``` + + + Replace the variable fields with the relevant data. + + + +### Step 3. Testing and Verifying Traces + + 1. Run `docker-compose up -d` to start all services. + + 2. Initialize your Tyk environment. + + 3. Create a simple API and deploy it to your Tyk Gateway. You can follow the [Tyk Dashboard documentation](/api-management/gateway-config-managing-classic#create-an-api) for more information. + + 4. Send requests to the API using cURL or Postman. + + 5. Open New Relic Console. + + 6. Navigate to **APM & Services → Services - OpenTelemetry → tyk-gateway**. + + New Relic Services + + 7. Wait for about 5 minutes for the data to populate. + + Traces and graphs should now be visible on your New Relic console. + + New Relic Metrics + + + If traces are not showing, try refreshing the New Relic dashboard. + + +You have successfully integrated New Relic with Tyk Gateway via the OpenTelemetry Collector. You can now monitor and trace your APIs directly from the New Relic console. + +## Troubleshooting + +If the traces aren't appearing: +- Double-check your API key and endpoints. +- Ensure that your Tyk Gateway and New Relic are both running and connected. diff --git a/api-management/traffic-transformation.mdx b/api-management/traffic-transformation.mdx new file mode 100644 index 0000000000..5bb0ff9171 --- /dev/null +++ b/api-management/traffic-transformation.mdx @@ -0,0 +1,107 @@ +--- +title: "Transform Traffic by using Tyk Middleware" +description: "Learn how to transform API traffic using Tyk's middleware capabilities." +keywords: "Overview, Allow List, Block List, Ignore Authentication, Internal Endpoint, Request Method , Request Body , Request Headers , Response Body, Response Headers, Request Validation, Mock Response, Virtual Endpoints, Go Templates, JQ Transforms, Request Context Variables" +sidebarTitle: "Overview" +--- + +## Overview + +When you configure an API on Tyk, the Gateway will proxy all requests received at the listen path that you have defined through to the upstream (target) URL configured in the API definition. Responses from the upstream are likewise proxied on to the originating client. Requests and responses are processed through a powerful [chain of middleware](/api-management/traffic-transformation#request-middleware-chain) that perform security and processing functions. + +Within that chain are a highly configurable set of optional middleware that can, on a per-endpint basis: +- apply processing to [API requests](#middleware-applied-to-the-api-request) before they are proxied to the upstream service +- apply customization to the [API response](#middleware-applied-to-the-api-response) prior to it being proxied back to the client + +Tyk also supports a powerful custom plugin feature that enables you to add custom processing at different stages in the processing chains. For more details on custom plugins please see the [dedicated guide](/api-management/plugins/overview#). + +### Middleware applied to the API Request + +The following standard middleware can optionally be applied to API requests on a per-endpoint basis. + +#### Allow list + +The [Allow List](/api-management/traffic-transformation/allow-list) middleware is a feature designed to restrict access to only specific API endpoints. It rejects requests to endpoints not specifically "allowed", returning `HTTP 403 Forbidden`. This enhances the security of the API by preventing unauthorized access to endpoints that are not explicitly permitted. + +Enabling the allow list will cause the entire API to become blocked other than for endpoints that have this middleware enabled. This is great if you wish to have very strict access rules for your services, limiting access to specific published endpoints. + +#### Block list + +The [Block List](/api-management/traffic-transformation/block-list) middleware is a feature designed to prevent access to specific API endpoints. Tyk Gateway rejects all requests made to endpoints with the block list enabled, returning `HTTP 403 Forbidden`. + +#### Cache + +Tyk's [API-level cache](/api-management/response-caching#basic-caching) does not discriminate between endpoints and will usually be configured to cache all safe requests. You can use the granular [Endpoint Cache](/api-management/response-caching#endpoint-caching) to ensure finer control over which API responses are cached by Tyk. + +#### Circuit Breaker + +The [Circuit Breaker](/planning-for-production/ensure-high-availability/circuit-breakers) is a protective mechanism that helps to maintain system stability by preventing repeated failures and overloading of services that are erroring. When a network or service failure occurs, the circuit breaker prevents further calls to that service, allowing the affected service time to recover while ensuring that the overall system remains functional. + +#### Do Not Track Endpoint + +If [traffic logging](/api-management/logs/traffic-logs) is enabled for your Tyk Gateway, then it will create transaction logs for all API requests (and responses) to deployed APIs. You can use the [Do-Not-Track](/api-management/traffic-transformation/do-not-track) middleware to suppress creation of transaction records for specific endpoints. + +#### Enforced Timeout + +Tyk’s [Enforced Timeout](/planning-for-production/ensure-high-availability/circuit-breakers) middleware can be used to apply a maximum time that the Gateway will wait for a response before it terminates (or times out) the request. This helps to maintain system stability and prevents unresponsive or long-running tasks from affecting the overall performance of the system. + +#### Ignore Authentication + +Adding the [Ignore Authentication](/api-management/traffic-transformation/ignore-authentication) middleware means that Tyk Gateway will not perform authentication checks on requests to that endpoint. This plugin can be very useful if you have a specific endpoint (such as a ping) that you don't need to secure. + +#### Internal Endpoint + +The [Internal Endpoint](/advanced-configuration/transform-traffic/looping#internal-only-apis) middleware instructs Tyk Gateway not to expose the endpoint externally. Tyk Gateway will then ignore external requests to that endpoint while continuing to process internal requests from other APIs; this is used for [internal routing](/advanced-configuration/transform-traffic/looping). + +#### Method Transformation + +The [Method Transformation](/api-management/traffic-transformation/request-method) middleware allows you to change the HTTP method of a request. + +#### Mock Response + +A [Mock Response](/api-management/traffic-transformation/mock-response#mock-response) is a simulated API response that can be returned by the API gateway without actually sending the request to the backend API. Mock responses are an integral feature for API development, enabling developers to emulate API behavior without the need for upstream execution. + +#### Request Body Transform + +The [Request Body Transform](/api-management/traffic-transformation/request-body) middleware allows you to perform modification to the body (payload) of the API request to ensure that it meets the requirements of your upstream service. + +#### Request Header Transform + +The [Request Header Transform](/api-management/traffic-transformation/request-headers) middleware allows you to modify the header information provided in the request before it leaves the Gateway and is passed to your upstream API. + +#### Request Size Limit + +Tyk Gateway offers a flexible tiered system of limiting request sizes ranging from globally applied limits across all APIs deployed on the gateway down to specific size limits for individual API endpoints. The [Request Size Limit](/api-management/traffic-transformation/request-size-limits) middleware provides the most granular control over request size by enabling you to set different limits for individual endpoints. + +#### Request Validation + +Tyk’s [Request Validation](/api-management/traffic-transformation/request-validation) middleware provides a way to validate the presence, correctness and conformity of HTTP requests to make sure they meet the expected format required by the upstream API endpoints. + +When working with Tyk OAS APIs, the request validation covers both headers and body (payload); with the older Tyk Classic API style we can validate only the request body (payload). + +#### Track Endpoint + +If you do not want to include all endpoints in your [Activity by Endpoint](/api-management/dashboard-analytics#activity-by-endpoint) statistics in Tyk Dashboard, you can enable this middleware for the endpoints to be included. + +#### URL Rewrite + +[URL Rewriting](/transform-traffic/url-rewriting) is a powerful feature that enables the modification of the target path to match the expected endpoint format of your backend services. This allows you to translate an outbound API interface to the internal structure of your services. It is a key capability used in [internal looping](/advanced-configuration/transform-traffic/looping) + +#### Virtual Endpoint + +Tyk’s [Virtual Endpoints](/api-management/traffic-transformation/virtual-endpoints) is a programmable middleware component that allows you to perform complex interactions with your upstream service(s) that cannot be handled by one of the other middleware components. + +### Middleware applied to the API Response + +The following transformations can be applied to the response recieved from the upstream to ensure that it contains the correct data and format expected by your clients. + +#### Response Body Transform + +The [Response Body Transform](/api-management/traffic-transformation/response-body) middleware allows you to perform modification to the body (payload) of the response received from the upstream service to ensure that it meets the expectations of the client. + +#### Response Header Transform + +The [Response Header Transform](/api-management/traffic-transformation/response-headers) middleware allows you to modify the header information provided in the response before it leaves the Gateway and is passed to the client. +### Request Middleware Chain + +Middleware execution flow diff --git a/api-management/traffic-transformation/allow-list.mdx b/api-management/traffic-transformation/allow-list.mdx new file mode 100644 index 0000000000..28d8c56692 --- /dev/null +++ b/api-management/traffic-transformation/allow-list.mdx @@ -0,0 +1,320 @@ +--- +title: "Allow List" +description: "Learn how to configure an endpoint Allow List for your API" +keywords: "Traffic Transformation, Allow List" +sidebarTitle: "Allow List" +--- + +## Overview + +The Allow List middleware is a feature designed to restrict access to only specific API endpoints. It rejects requests to endpoints not specifically "allowed", returning `HTTP 403 Forbidden`. This enhances the security of the API by preventing unauthorized access to endpoints that are not explicitly permitted. + +Note that this is not the same as Tyk's [IP allow list](/api-management/gateway-config-tyk-classic#ip-access-control) feature, which is used to restrict access to APIs based upon the IP of the requestor. + +### Use Cases + +#### Restricting access to private endpoints + +If you have a service that exposes endpoints or supports methods that you do not want to be available to clients, you should use the allow list to perform strict restriction to a subset of methods and paths. If the allow list is not enabled, requests to endpoints that are not explicitly defined in Tyk will be proxied to the upstream service and may lead to unexpected behavior. + +### Working + +Tyk Gateway does not actually maintain a list of allowed endpoints but rather works on the model whereby if the *allow list* middleware is added to an endpoint then this will automatically block all other endpoints. + +Tyk Gateway will subsequently return `HTTP 403 Forbidden` to any requested endpoint that doesn't have the *allow list* middleware enabled, even if the endpoint is defined and configured in the API definition. + +
+ + +If you enable the allow list feature by adding the middleware to any endpoint, ensure that you also add the middleware to any other endpoint for which you wish to accept requests. + + + +#### Case sensitivity + +By default the allow list is case-sensitive, so for example if you have defined the endpoint `GET /userID` in your API definition then only calls to `GET /userID` will be allowed: calls to `GET /UserID` or `GET /userid` will be rejected. You can configure the middleware to be case-insensitive at the endpoint level. + +You can also set case sensitivity for the entire [gateway](/tyk-oss-gateway/configuration#ignore_endpoint_case) in the Gateway configuration file `tyk.conf`. If case insensitivity is configured at the gateway level, this will override the endpoint-level setting. + +#### Endpoint parsing + +When using the allow list middleware, we recommend that you familiarize yourself with [Request Matching](/getting-started/key-concepts/url-matching) concepts. + +
+ + +Tyk recommends that you use [exact matching](/getting-started/key-concepts/url-matching#matching-modes) for maximum security, though prefix and wildcard strategies might also apply for your particular deployment or use case. + + + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Allow List middleware summary + - The Allow List is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Allow List can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +## Using Tyk OAS + + +The [allow list](/api-management/traffic-transformation/allow-list) is a feature designed to restrict access to only specific API endpoints. It rejects requests to endpoints not specifically "allowed", returning `HTTP 403 Forbidden`. This enhances the security of the API by preventing unauthorized access to endpoints that are not explicitly permitted. + +When working with Tyk OAS APIs the middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#allow-list-using-classic) page. + + +### API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The allow list middleware (`allow`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `allow` object has the following configuration: + +- `enabled`: enable the middleware for the endpoint +- `ignoreCase`: if set to `true` then the path matching will be case insensitive + +For example: + +```json {hl_lines=["47-50", "53-56"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-allow-list", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + }, + "put": { + "operationId": "anythingput", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-allow-list", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-allow-list/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingget": { + "allow": { + "enabled": true, + "ignoreCase": true + } + }, + "anythingput": { + "allow": { + "enabled": true, + "ignoreCase": true + } + } + } + } + } +} +``` + +In this example the allow list middleware has been configured for requests to the `GET /anything` and `PUT /anything` endpoints. Requests to any other endpoints will be rejected with `HTTP 403 Forbidden`, unless they also have the allow list middleware enabled. +Note that the allow list has been configured to be case insensitive, so calls to `GET /Anything` will be allowed +Note also that the endpoint path has not been terminated with `$`. Requests to, for example, `GET /anything/foobar` will be allowed as the [regular expression pattern match](#endpoint-parsing) will recognize this as `GET /anything`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the allow list feature. + +### API Designer + +Adding the allow list to your API endpoints is easy is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Allow List middleware** + + Select **ADD MIDDLEWARE** and choose the **Allow List** middleware from the *Add Middleware* screen. + + Adding the Allow List middleware + +3. **Optionally configure case-insensitivity** + + If you want to disable case-sensitivity for the allow list, then you must select **EDIT** on the Allow List icon. + + Allow List middleware added to endpoint - click through to edit the config + + This takes you to the middleware configuration screen where you can alter the case sensitivity setting. + Configuring case sensitivity for the Allow List + + Select **UPDATE MIDDLEWARE** to apply the change to the middleware configuration. + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [allow list](/api-management/traffic-transformation/allow-list) is a feature designed to restrict access to only specific API endpoints. It rejects requests to endpoints not specifically "allowed", returning `HTTP 403 Forbidden`. This enhances the security of the API by preventing unauthorized access to endpoints that are not explicitly permitted. + +When working with Tyk Classic APIs the middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#allow-list-using-tyk-oas) page. + +### API Definition + +To enable and configure the allow list you must add a new `white_list` object to the `extended_paths` section of your API definition. + + + +Historically, Tyk followed the out-dated whitelist/blacklist naming convention. We are working to remove this terminology from the product and documentation, however this configuration object currently retains the old name. + + + +The `white_list` object has the following configuration: + +- `path`: the endpoint path +- `method`: this should be blank +- `ignore_case`: if set to `true` then the path matching will be case insensitive +- `method_actions`: a shared object used to configure the [mock response](/api-management/traffic-transformation/mock-response#tyk-classic) middleware for Tyk Classic APIs + +The `method_actions` object should be configured as follows, with an entry created for each allowed method on the path: + +- `action`: this should be set to `no_action` +- `code`: this should be set to `200` +- `headers` : this should be blank + +For example: + +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "white_list": [ + { + "disabled": false, + "path": "/status/200", + "method": "", + "ignore_case": false, + "method_actions": { + "GET": { + "action": "no_action", + "code": 200, + "headers": {} + }, + "PUT": { + "action": "no_action", + "code": 200, + "headers": {} + } + } + } + ] + } +} +``` + +In this example the allow list middleware has been configured for HTTP `GET` and `PUT` requests to the `/status/200` endpoint. Requests to any other endpoints will be rejected with `HTTP 403 Forbidden`, unless they also have the allow list middleware enabled. +Note that the allow list has been configured to be case sensitive, so calls to `GET /Status/200` will also be rejected. +Note also that the endpoint path has not been terminated with `$`. Requests to, for example, `GET /status/200/foobar` will be allowed as the [regular expression pattern match](#endpoint-parsing) will recognize this as `GET /status/200`. + +Consult section [configuring the Allow List in Tyk Operator](#tyk-operator) for details on how to configure allow lists for endpoints using Tyk Operator. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the allow list middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer**, add an endpoint that matches the path for which you want to allow access. Select the **Whitelist** plugin. + +2. **Configure the allow list** + + Once you have selected the middleware for the endpoint, the only additional feature that you need to configure is whether to make the middleware case insensitive by selecting **Ignore Case**. + + Allowlist options + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the allow list middleware. + +### Tyk Operator + +Similar to the configuration of a Tyk Classic API Definition you must add a new `white_list` object to the `extended_paths` section of your API definition. Furthermore, the `use_extended_paths` configuration parameter should be set to `true`. + + + +Historically, Tyk followed the out-dated whitelist/blacklist naming convention. We are working to remove this terminology from the product and documentation, however this configuration object currently retains the old name. + + + +```yaml {linenos=true,linenostart=1,hl_lines=["26-34"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-whitelist +spec: + name: httpbin-whitelist + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org/ + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + white_list: + - ignore_case: true + method_actions: + GET: + action: "no_action" + code: 200 + data: "" + headers: {} + path: "/get" +``` + +In this example the allow list middleware has been configured for `HTTP GET` requests to the `/get` endpoint. Requests to any other endpoints will be rejected with `HTTP 403 Forbidden`, unless they also have the allow list middleware enabled. Note that the allow list has been configured to case insensitive, so calls to `GET /Get` will also be accepted. Note also that the endpoint path has not been terminated with `$`. Requests to, for example, `GET /get/foobar` will be allowed as the [regular expression pattern match](#endpoint-parsing) will recognize this as `GET /get`. + + diff --git a/api-management/traffic-transformation/block-list.mdx b/api-management/traffic-transformation/block-list.mdx new file mode 100644 index 0000000000..758dca26d5 --- /dev/null +++ b/api-management/traffic-transformation/block-list.mdx @@ -0,0 +1,309 @@ +--- +title: "Block List" +description: "Learn how to configure an endpoint Block List for your API" +keywords: "Traffic Transformation, Block List" +sidebarTitle: "Block List" +--- + +## Overview + +The Block List middleware is a feature designed to block access to specific API endpoints. Tyk Gateway rejects all requests made to endpoints with the block list enabled, returning `HTTP 403 Forbidden`. + +Note that this is not the same as Tyk's [IP block list](/api-management/gateway-config-tyk-classic#ip-access-control) feature, which is used to restrict access to APIs based upon the IP of the requestor. + +### Use Cases + +#### Prevent access to deprecated resources + +If you are versioning your API and deprecating an endpoint then, instead of having to remove the functionality from your upstream service's API you can simply block access to it using the block list middleware. + +### Working + +Tyk Gateway does not actually maintain a list of blocked endpoints but rather works on the model whereby if the *block list* middleware is added to an endpoint then any request to that endpoint will be rejected, returning `HTTP 403 Forbidden`. + +#### Case sensitivity + +By default the block list is case-sensitive, so for example if you have defined the endpoint `GET /userID` in your API definition then only calls to `GET /userID` will be blocked: calls to `GET /UserID` or `GET /userid` will be allowed. You can configure the middleware to be case-insensitive at the endpoint level. + +You can also set case sensitivity for the entire [gateway](/tyk-oss-gateway/configuration#ignore_endpoint_case) in the Gateway configuration file `tyk.conf`. If case insensitivity is configured at the gateway level, this will override the endpoint-level setting. + +#### Endpoint parsing + +When using the block list middleware, we recommend that you familiarize yourself with [Request Matching](/getting-started/key-concepts/url-matching) options. + +
+ + +Tyk recommends that you use [exact matching](/getting-started/key-concepts/url-matching#matching-modes) for maximum security, though prefix and wildcard strategies might also apply for your particular deployment or use case. + + + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Block List middleware summary + - The Block List is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Block List can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + +## Using Tyk OAS + + +The [block list](/api-management/traffic-transformation/block-list) is a feature designed to block access to specific API endpoints. Tyk Gateway rejects all requests made to endpoints with the block list enabled, returning `HTTP 403 Forbidden`. + +When working with Tyk OAS APIs the middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#block-list-using-classic) page. + +### API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. The `path` can contain wildcards in the form of any string bracketed by curly braces, for example `{user_id}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The block list middleware (`block`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `block` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `ignoreCase`: if set to `true` then the path matching will be case insensitive + +For example: +```json {hl_lines=["47-50", "53-56"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-block-list", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + }, + "put": { + "operationId": "anythingput", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-block-list", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-block-list/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingget": { + "block": { + "enabled": true, + "ignoreCase": true + } + }, + "anythingput": { + "block": { + "enabled": true, + "ignoreCase": true + } + } + } + } + } +} +``` + +In this example the block list middleware has been configured for requests to the `GET /anything` and `PUT /anything` endpoints. Requests to these endpoints will be rejected with `HTTP 403 Forbidden`. +Note that the block list has been configured to be case insensitive, so calls to `GET /Anything` will also be blocked. +Note also that the endpoint path has not been terminated with `$`. Requests to, for example, `GET /anything/foobar` will be rejected as the [regular expression pattern match](#endpoint-parsing) will recognize this as `GET /anything`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the block list feature. + +### API Designer + +Adding the block list to your API endpoints is easy is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Block List middleware** + + Select **ADD MIDDLEWARE** and choose the **Block List** middleware from the *Add Middleware* screen. + + Adding the Block List middleware + +3. **Optionally configure case-insensitivity** + + If you want to disable case-sensitivity for the block list, then you must select **EDIT** on the Block List icon. + + Block List middleware added to endpoint - click through to edit the config + + This takes you to the middleware configuration screen where you can alter the case sensitivity setting. + Configuring case sensitivity for the Block List + + Select **UPDATE MIDDLEWARE** to apply the change to the middleware configuration. + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [block list](/api-management/traffic-transformation/block-list) is a feature designed to block access to specific API endpoints. Tyk Gateway rejects all requests made to endpoints with the block list enabled, returning `HTTP 403 Forbidden`. + +When working with Tyk Classic APIs the middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#block-list-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the block list in Tyk Operator](#tyk-operator) section below. + +### API Definition + +To enable and configure the block list you must add a new `black_list` object to the `extended_paths` section of your API definition. + + + +Historically, Tyk followed the out-dated whitelist/blacklist naming convention. We are working to remove this terminology from the product and documentation, however this configuration object currently retains the old name. + + + +The `black_list` object has the following configuration: +- `path`: the endpoint path +- `method`: this should be blank +- `ignore_case`: if set to `true` then the path matching will be case insensitive +- `method_actions`: a shared object used to configure the [mock response](/api-management/traffic-transformation/mock-response#tyk-classic) middleware for Tyk Classic APIs + +The `method_actions` object should be configured as follows, with an entry created for each blocked method on the path: +- `action`: this should be set to `no_action` +- `code`: this should be set to `200` +- `headers` : this should be blank + +For example: +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "black_list": [ + { + "disabled": false, + "path": "/status/200", + "method": "", + "ignore_case": false, + "method_actions": { + "GET": { + "action": "no_action", + "code": 200, + "headers": {} + } + "PUT": { + "action": "no_action", + "code": 200, + "headers": {} + } + } + } + ] + } +} +``` + +In this example the block list middleware has been configured for HTTP `GET` and `PUT` requests to the `/status/200` endpoint. Requests to these endpoints will be rejected with `HTTP 403 Forbidden`. +Note that the block list has been configured to be case sensitive, so calls to `GET /Status/200` will not be rejected. +Note also that the endpoint path has not been terminated with `$`. Requests to, for example, `GET /status/200/foobar` will be rejected as the [regular expression pattern match](#endpoint-parsing) will recognize this as `GET /status/200`. + +Consult section [configuring the Allow List in Tyk Operator](#tyk-operator) for details on how to configure allow lists for endpoints using Tyk Operator. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the block list middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to prevent access. Select the **Blacklist** plugin. + +2. **Configure the block list** + + Once you have selected the middleware for the endpoint, the only additional feature that you need to configure is whether to make the middleware case insensitive by selecting **Ignore Case**. + + Blocklist options + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +Similar to the configuration of a Tyk Classic API Definition you must add a new `black_list` object to the `extended_paths` section of your API definition. Furthermore, the `use_extended_paths` configuration parameter should be set to `true`. + + + +Historically, Tyk followed the out-dated whitelist/blacklist naming convention. We are working to remove this terminology from the product and documentation, however this configuration object currently retains the old name. + + + +```yaml {linenos=true, linenostart=1, hl_lines=["26-34"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-blacklist +spec: + name: httpbin-blacklist + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org/ + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + black_list: + - ignore_case: true + method_actions: + GET: + action: "no_action" + code: 200 + data: "" + headers: {} + path: "/get" +``` + +In this example the block list middleware has been configured for HTTP `GET` requests to the `/get` endpoint. Requests to this endpoint will be rejected with `HTTP 403 Forbidden`. +Note that the block list has been configured to be case insensitive, so calls to `GET /Get` will not be rejected. +Note also that the endpoint path has not been terminated with `$`. Requests to, for example, `GET /get/foobar` will be rejected as the [regular expression pattern match](#endpoint-parsing) will recognize this as `GET /get`. + + + diff --git a/api-management/traffic-transformation/do-not-track.mdx b/api-management/traffic-transformation/do-not-track.mdx new file mode 100644 index 0000000000..ff0d939a51 --- /dev/null +++ b/api-management/traffic-transformation/do-not-track.mdx @@ -0,0 +1,278 @@ +--- +title: "Do Not Track" +description: "Learn how to suppress generation of traffic logs for an API endpoint" +keywords: "Traffic Transformation, Do Not Track" +sidebarTitle: "Do Not Track" +--- + +## Overview + + +When [traffic logging](/api-management/logs/traffic-logs) is enabled in the Tyk Gateway, a traffic log will be generated for every request made to an API endpoint deployed on the gateway. You can suppress the generation of traffic logs for any API by enabling the do-not-track middleware. This provides granular control over request tracking. + +### Use Cases + +#### Compliance and privacy + +Disabling tracking on endpoints that handle personal or sensitive information is crucial for adhering to privacy laws such as GDPR or HIPAA. This action prevents the storage and logging of sensitive data, ensuring compliance and safeguarding user privacy. + +#### Optimizing performance + +For endpoints experiencing high traffic, disabling tracking can mitigate the impact on the analytics processing pipeline and storage systems. Disabling tracking on endpoints used primarily for health checks or load balancing can prevent the analytics data from being cluttered with information that offers little insight. These optimizations help to maintain system responsiveness and efficiency by reducing unnecessary data load and help to ensure that analytics efforts are concentrated on more meaningful data. + +#### Cost Management + +In scenarios where analytics data storage and processing incur significant costs, particularly in cloud-based deployments, disabling tracking for non-essential endpoints can be a cost-effective strategy. This approach allows for focusing resources on capturing valuable data from critical endpoints. + +### Working + +When transaction logging is enabled, the gateway will automatically generate a transaction record for every request made to deployed APIs. + +You can enable the do-not-track middleware on whichever endpoints for which you do not want to generate logs. This will instruct the Gateway not to generate any transaction records for those endpoints or APIs. As no record of these transactions will be generated by the Gateway, there will be nothing created in Redis and hence nothing for the pumps to transfer to the persistent storage and these endpoints will not show traffic in the Dashboard's analytics screens. + + + +You can disable tracking at the API or endpoint level, for both Tyk OAS and Tyk Classic APIs. + + + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Do-Not-Track middleware summary + - The Do-Not-Track middleware is an optional stage in Tyk's API Request processing chain sitting between the [TBC]() and [TBC]() middleware. + - The Do-Not-Track middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +## Using Tyk OAS + + +The [Do-Not-Track](#do-not-track-overview) middleware provides the facility to disable generation of transaction records (which are used to track requests to your APIs), at either the API level or the endpoint level. + +When working with Tyk OAS APIs the middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation) either manually within the `.json` file or from the API Designer in the Tyk Dashboard. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#do-not-track-using-classic) page. + +### API Definition + +To disable tracking for an entire API, set `enabled` to `false` under `middleware.global.trafficLogs` in the Tyk OAS Extension (`x-tyk-api-gateway`): + +```json +"middleware": { + "global": { + "trafficLogs": { + "enabled": false + } + } +} +``` + +To disable tracking for specific endpoints instead, use the `doNotTrackEndpoint` middleware, described below. + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. The `path` can contain wildcards in the form of any string bracketed by curly braces, for example `{user_id}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The do-not-track middleware (`doNotTrackEndpoint`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `doNotTrackEndpoint` object has the following configuration: +- `enabled`: enable the middleware for the endpoint + +For example: +```json {hl_lines=["39-41"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-do-not-track", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-do-not-track", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-do-not-track/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingget": { + "doNotTrackEndpoint": { + "enabled": true + } + } + } + } + } +} +``` + +In this example the do-not-track middleware has been configured for requests to the `GET /anything` endpoint. Any such calls will not generate transaction records from the Gateway and so will not appear in the analytics. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the do-not-track middleware. + +### API Designer + +The API-level `trafficLogs.enabled` setting is currently only configurable via the raw API Definition, not a dedicated control in the API Designer. Adding do-not-track to your API endpoints, however, is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Do Not Track Endpoint middleware** + + Select **ADD MIDDLEWARE** and choose the **Do Not Track Endpoint** middleware from the *Add Middleware* screen. + + Adding the Do Not Track middleware + +3. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [Do-Not-Track](#do-not-track-overview) middleware provides the facility to disable generation of transaction records (which are used to track requests) at the API or endpoint level. + +When working with Tyk Classic APIs the middleware is configured in the Tyk Classic API Definition either manually within the `.json` file or from the API Designer in the Tyk Dashboard. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](/api-management/traffic-transformation/do-not-track#do-not-track-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +You can prevent tracking for all endpoints of an API by configuring the `do_not_track` field in the root of your API definition. +- `true`: no transaction logs will be generated for requests to the API +- `false`: transaction logs will be generated for requests to the API + +If you want to be more granular and disable tracking only for selected endpoints, then you must add a new `do_not_track_endpoints` object to the `extended_paths` section of your API definition. + +The `do_not_track_endpoints` object has the following configuration: +- `path`: the endpoint path +- `method`: the endpoint HTTP method + +The `path` can contain wildcards in the form of any string bracketed by curly braces, for example `{user_id}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +For example: +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "do_not_track_endpoints": [ + { + "disabled": false, + "path": "/anything", + "method": "GET" + } + ] + } +} +``` + +In this example the do-not-track middleware has been configured for requests to the `GET /anything` endpoint. Any such calls will not generate transaction records from the Gateway and so will not appear in the analytics. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the per-endpoint Do-Not-Track middleware for your Tyk Classic API by following these steps. Note that the API-level middleware can only be configured from the Raw Definition screen. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you do not want to generate records. Select the **Do not track endpoint** plugin. + + Select the middleware + +2. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +The process for configuring the middleware in Tyk Operator is similar to that explained in configuring the middleware in the Tyk Classic API Definition. + +It is possible to prevent tracking for all endpoints of an API by configuring the `do_not_track` field in the root of your API definition as follows: + +- `true`: no transaction logs will be generated for requests to the API +- `false`: transaction logs will be generated for requests to the API + +```yaml {linenos=true, linenostart=1, hl_lines=["10"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-do-not-track +spec: + name: httpbin-do-not-track + use_keyless: true + protocol: http + active: true + do_not_track: true + proxy: + target_url: http://example.com + listen_path: /example + strip_listen_path: true +``` + +If you want to disable tracking only for selected endpoints, then the process is similar to that defined in configuring the middleware in the Tyk Classic API Definition, i.e. you must add a new `do_not_track_endpoints` list to the extended_paths section of your API definition. +This should contain a list of objects representing each endpoint `path` and `method` that should have tracking disabled: + +```yaml {linenos=true, linenostart=1, hl_lines=["31-33"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-endpoint-tracking +spec: + name: httpbin - Endpoint Track + use_keyless: true + protocol: http + active: true + do_not_track: false + proxy: + target_url: http://httpbin.org/ + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + track_endpoints: + - method: GET + path: "/get" + do_not_track_endpoints: + - method: GET + path: "/headers" +``` + +In the example above we can see that the `do_not_track_endpoints` list is configured so that requests to `GET /headers` will have tracking disabled. + diff --git a/api-management/traffic-transformation/go-templates.mdx b/api-management/traffic-transformation/go-templates.mdx new file mode 100644 index 0000000000..2db74891a2 --- /dev/null +++ b/api-management/traffic-transformation/go-templates.mdx @@ -0,0 +1,229 @@ +--- +title: "Go Templates" +description: "Learn how to use Go Templates with your body transforms" +keywords: "Traffic Transformation, Go Templates" +sidebarTitle: "Go Templates" +--- + +Tyk's [request](/api-management/traffic-transformation/request-body) and [response](/api-management/traffic-transformation/response-body) body transform middleware use the [Go template language](https://golang.org/pkg/text/template/) to parse and modify the provided input. + +Go templates are also used by Tyk's [webhook event handler](/api-management/gateway-events#event-handling-with-webhooks) to produce the payload for the HTTP request sent to the target system. + +In this section of the documentation, we provide some guidance and a few examples on the use of Go templating with Tyk. + +## Data format conversion using helper functions + +Tyk provides two helper functions to assist with data format translation between JSON and XML: +- `jsonMarshal` performs JSON style character escaping on an XML field and, for complex objects, serialises them to a JSON string ([example](#xml-to-json-conversion-using-jsonmarshal)) +- `xmlMarshal` performs the equivalent conversion from JSON to XML ([example](#json-to-xml-conversion-using-xmlmarshal)) + +When creating these functions within your Go templates, please note: +- the use of `.` in the template refers to the entire input, whereas something like `.myField` refers to just the `myField` field of the input +- the pipe `|` joins together the things either side of it, which is typically input data on the left and a receiving command to process the data on the right, such as `jsonMarshal` + +Hence `{{ . | jsonMarshal }}` will pass the entire input to the `jsonMarshal` helper function. + +## Using functions within Go templates + +You can define and use functions in the Go templates that are used for body transforms in Tyk. Functions allow you to abstract common template logic for cleaner code and to aid reusability. Breaking the template into functions improves readability of more complex tenplates. + +Here is an example where we define a function called `myFunction` that accepts one parameter: +```go +{{- define "myFunction" }} + Hello {{.}}! +{{- end}} +``` + +We can call that function and pass "world" as the parameter: +```go +{ + "message": {{ call . "myFunction" "world"}} +} +``` + +The output would be: +```json +{ + "message": "Hello world!" +} +``` + +We have bundled the [Sprig Library (v3)](http://masterminds.github.io/sprig/) which provides over 70 pre-written functions for transformations to assist the creation of powerful Go templates to transform your API requests. + +## Additional resources + +Here's a useful [blog post](https://blog.gopheracademy.com/advent-2017/using-go-templates/) and [YouTube tutorial](https://www.youtube.com/watch?v=k5wJv4XO7a0) that can help you to learn about using Go templates. + +## Go templating examples +Here we provide worked examples for both [JSON](#example-json-transformation-template) and [XML](#example-xml-transformation-template) formatted inputs. We also explain examples using the [jsonMarshal](#xml-to-json-conversion-using-jsonmarshal) and [xmlMarshal](#json-to-xml-conversion-using-xmlmarshal) helper functions. + +### Example JSON transformation template +Imagine you have a published API that accepts the request listed below, but your upstream service requires a few alterations, namely: +- swapping the values of parameters `value1` and `value2` +- renaming the `value_list` to `transformed_list` +- adding a `user-id` extracted from the session metadata +- adding a `client-ip` logging the client IP +- adding a `req-type` that logs the value provided in query parameter `type` + +**Input** +- Session metadata `uid` = `user123` +- IP address of calling client = `192.0.0.1` +- Query parameter `type` = `strict` +```json +{ + "value1": "value-1", + "value2": "value-2", + "value_list": [ + "one", + "two", + "three" + ] +} +``` + +**Template** +```go +{ + "value1": "{{.value2}}", + "value2": "{{.value1}}", + "transformed_list": [ + {{range $index, $element := index . "value_list"}} + {{if $index}}, {{end}} + "{{$element}}" + {{end}} + ], + "user-id": "{{._tyk_meta.uid}}", + "user-ip": "{{._tyk_context.remote_addr}}", + "req-type": "{{ ._tyk_context.request_data.param.type }}" +} +``` +In this template: +- `.value1` accesses the "value1" field of the input JSON +- we swap value1 and value2 +- we use the range function to loop through the "value_list" array +- `._tyk_meta.uid` injects the "uid" session metadata value +- `._tyk_context.remote_addr` injects the client IP address from the context +- `._tyk_context.request_data.param.type` injects query parameter "type" + +**Output** +``` .json +{ + "value1": "value-2", + "value2": "value-1", + "transformed_list": [ + "one", + "two", + "three" + ], + "user-id": "user123" + "user-ip": "192.0.0.1" + "req-type": "strict" +} +``` + +### Example XML transformation template +XML cannot be as easily decoded into strict structures as JSON, so the syntax is a little different when working with an XML document. Here we are performing the reverse translation, starting with XML and converting to JSON. + +**Input** +- Session metadata `uid` = `user123` +- IP address of calling client = `192.0.0.1` +- Query parameter `type` = `strict` +```xml + + + + value-1 + value-2 + + one + two + three + + + +``` + +**Template** +``` .xml + + + + {{ .data.body.value2 }} + {{ .data.body.value1 }} + + {{range $index, $element := .data.body.valueList.item }} + {{$element}} + {{end}} + + {{ ._tyk_meta.uid }} + {{ ._tyk_context.remote_addr }} + {{ ._tyk_context.request_data.param.type }} + + +``` +In this template: +- `.data.body.value1` accesses the "value1" field of the input XML +- we swap value1 and value2 +- we use the range function to loop through the "value_list" array +- `._tyk_meta.uid` injects the "uid" session metadata value +- `._tyk_context.remote_addr` injects the client IP address from the context +- `._tyk_context.request_data.param.type` injects query parameter "type" + +**Output** +``` .xml + + + + value-2 + value-1 + + one + two + three + + user123 + 192.0.0.1 + strict + + +``` + +### XML to JSON conversion using jsonMarshal +The `jsonMarshal` function converts XML formatted input into JSON, for example: + +**Input** +```xml +world +``` + +**Template** +```go +{{ . | jsonMarshal }} +``` + +**Output** +```json +{"hello":"world"} +``` + +Note that in this example, Go will step through the entire data structure provided to the template. When used in the [Request](/api-management/traffic-transformation/request-body#data-accessible-to-the-middleware) or [Response](/api-management/traffic-transformation/request-body#data-accessible-to-the-middleware) Body Transform middleware, this would include Context Variables and Session Metadata if provided to the middleware. + +### JSON to XML conversion using xmlMarshal +The `xmlMarshal` function converts JSON formatted input into XML, for example: + +**Input** +```json +{"hello":"world"} +``` +**Template** +``` .go +{{ . | xmlMarshal }} +``` + +**Output** +```xml +world +``` + +Note that in this example, Go will step through the entire data structure provided to the template. When used in the [Request](/api-management/traffic-transformation/request-body#data-accessible-to-the-middleware) or [Response](/api-management/traffic-transformation/request-body#data-accessible-to-the-middleware) Body Transform middleware, this would include Context Variables and Session Metadata if provided to the middleware. + diff --git a/api-management/traffic-transformation/ignore-authentication.mdx b/api-management/traffic-transformation/ignore-authentication.mdx new file mode 100644 index 0000000000..f4e64f0a7f --- /dev/null +++ b/api-management/traffic-transformation/ignore-authentication.mdx @@ -0,0 +1,301 @@ +--- +title: "Ignore Authentication" +description: "Learn how to skip authentication for an API endpoint" +keywords: "Traffic Transformation, Ignore Authentication" +sidebarTitle: "Ignore Authentication" +--- + +## Overview + + +The Ignore Authentication middleware instructs Tyk Gateway to skip the authentication step for calls to an endpoint, even if authentication is enabled for the API. + +### Use Cases + +#### Health and liveness endpoints + +This plugin can be very useful if you have an endpoint (such as a ping or health check) that you don’t need to secure. + +### Working + +When the Ignore Authentication middleware is configured for a specific endpoint, it instructs the gateway to bypass the client authentication process for requests made to that endpoint. If other (non-authentication) middleware are configured for the endpoint, they will still execute on the request. + +It is important to exercise caution when using the Ignore Authentication middleware, as it effectively disables Tyk's security features for the ignored paths. Only endpoints that are designed to be public or have independent security mechanisms should be configured to bypass authentication in this way. When combining Ignore Authentication with response transformations be careful not to inadvertently expose sensitive data or rely on authentication or session data that is not present. + +#### Case sensitivity + +By default the ignore authentication middleware is case-sensitive. If, for example, you have defined the endpoint `GET /ping` in your API definition then only calls to `GET /ping` will ignore the authentication step: calls to `GET /Ping` or `GET /PING` will require authentication. You can configure the middleware to be case insensitive at the endpoint level. + +You can also set case sensitivity for the entire Tyk Gateway in its [configuration file](/tyk-oss-gateway/configuration#ignore_endpoint_case) `tyk.conf`. If case insensitivity is configured at the gateway level, this will override the endpoint-level setting. + +#### Endpoint parsing + +When using the ignore authentication middleware, we recommend that you familiarize yourself with [Request Matching](/getting-started/key-concepts/url-matching) options. + +
+ + +Tyk recommends that you use [exact matching](/getting-started/key-concepts/url-matching#matching-modes) for maximum security, though prefix and wildcard strategies might also apply for your particular deployment or use case. + + + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Ignore Authentication middleware summary + - The Ignore Authentication middleware is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Ignore Authentication middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +## Using Tyk OAS + + +The [Ignore Authentication](/api-management/traffic-transformation/ignore-authentication) middleware instructs Tyk Gateway to skip the authentication step for calls to an endpoint, even if authentication is enabled for the API. + +When working with Tyk OAS APIs the middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#ignore-authentication-using-classic) page. + +### API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The ignore authentication middleware (`ignoreAuthentication`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `ignoreAuthentication` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `ignoreCase`: if set to `true` then the path matching will be case insensitive + +For example: +```json {hl_lines=["65-69"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-ignore-authentication", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "servers": [ + { + "url": "http://localhost:8181/example-ignore-authentication/" + } + ], + "security": [ + { + "authToken": [] + } + ], + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "components": { + "securitySchemes": { + "authToken": { + "type": "apiKey", + "in": "header", + "name": "Authorization" + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-ignore-authentication", + "state": { + "active": true, + "internal": false + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "authToken": { + "enabled": true + } + } + }, + "listenPath": { + "strip": true, + "value": "/example-ignore-authentication/" + } + }, + "middleware": { + "operations": { + "anythingget": { + "ignoreAuthentication": { + "enabled": true + } + } + } + } + } +} +``` + +In this example the ignore authentication middleware has been configured for requests to the `GET /anything` endpoint. Any such calls will skip the authentication step in the Tyk Gateway's processing chain. +- the middleware has been configured to be case sensitive, so calls to `GET /Anything` will not skip authentication + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the Ignore Authentication middleware. + +### API Designer + +Adding and configuring the Ignore Authentication middleware to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Ignore Authentication middleware** + + Select **ADD MIDDLEWARE** and choose the **Ignore Authentication** middleware from the *Add Middleware* screen. + + Adding the Ignore Authentication middleware + +3. **Optionally configure case-insensitivity** + + If you want to disable case-sensitivity for the path that you wish to skip authentication, then you must select **EDIT** on the Ignore Authentication icon. + + Ignore Authentication middleware added to endpoint - click through to edit the config + + This takes you to the middleware configuration screen where you can alter the case sensitivity setting. + Configuring case sensitivity for the path for which to ignore authentication + + Select **UPDATE MIDDLEWARE** to apply the change to the middleware configuration. + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [Ignore Authentication](/api-management/traffic-transformation/ignore-authentication) middleware instructs Tyk Gateway to skip the authentication step for calls to an endpoint, even if authentication is enabled for the API. + +When working with Tyk Classic APIs the middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#ignore-authentication-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +To enable the middleware you must add a new `ignored` object to the `extended_paths` section of your API definition. + +The `ignored` object has the following configuration: +- `path`: the endpoint path +- `method`: this should be blank +- `ignore_case`: if set to `true` then the path matching will be case insensitive +- `method_actions`: a shared object used to configure the [mock response](/api-management/traffic-transformation/mock-response#tyk-classic) middleware for Tyk Classic APIs + +The `method_actions` object should be configured as follows, with an entry created for each allowed method on the path: +- `action`: this should be set to `no_action` +- `code`: this should be set to `200` +- `headers` : this should be blank + +For example: +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "ignored": [ + { + "disabled": false, + "path": "/status/200", + "method": "", + "ignore_case": false, + "method_actions": { + "GET": { + "action": "no_action", + "code": 200, + "headers": {} + } + } + } + ] + } +} +``` + +In this example the ignore authentication middleware has been configured for requests to the `GET /status/200` endpoint. Any such calls will skip the authentication step in the Tyk Gateway's processing chain. +- the middleware has been configured to be case sensitive, so calls to `GET /Status/200` will not skip authentication + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the Ignore Authentication middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to ignore authentication. Select the **Ignore** plugin. + + Adding the ignore authentication middleware to a Tyk Classic API endpoint + +2. **Configure the middleware** + + Once you have selected the Ignore middleware for the endpoint, the only additional feature that you need to configure is whether to make it case-insensitive by selecting **Ignore Case**. + + Ignore options + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +The process for configuring the middleware in Tyk Operator is similar to that explained in configuring the middleware in the Tyk Classic API Definition. It is possible to configure an enforced timeout using the `ignored` object within the `extended_paths` section of the API Definition. + +In the example below the ignore authentication middleware has been configured for requests to the `GET /get` endpoint. Any such calls will skip the authentication step in the Tyk Gateway's processing chain. +- the middleware has been configured to be case insensitive, so calls to `GET /Get` will also skip authentication + +```yaml {linenos=true, linenostart=1, hl_lines=["27-35"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-ignored +spec: + name: httpbin-ignored + use_keyless: false + use_standard_auth: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org/ + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + ignored: + - ignore_case: true + method_actions: + GET: + action: "no_action" + code: 200 + data: "" + headers: {} + path: "/get" +``` \ No newline at end of file diff --git a/api-management/traffic-transformation/jq-transforms.mdx b/api-management/traffic-transformation/jq-transforms.mdx new file mode 100644 index 0000000000..9fe3e99686 --- /dev/null +++ b/api-management/traffic-transformation/jq-transforms.mdx @@ -0,0 +1,65 @@ +--- +title: "JQ Transforms" +description: "Learn how to use the experimental JQ traffic transformation middleware" +keywords: "Traffic Transformation, JQ Transforms" +sidebarTitle: "JQ Transforms" +--- + + + +This feature is experimental and can be used only if you compile Tyk yourself own using `jq` tag: `go build --tags 'jq'` + + + + +If you work with JSON you are probably aware of the popular `jq` command line JSON processor. For more details, see https://stedolan.github.io/jq/. + +Now you can use the full power of its queries and transformations to transform requests, responses, headers and even context variables. + +We have added two new plugins: + +* `transform_jq` - for request transforms. +* `transform_jq_response` - for response transforms + +Both have the same structure, similar to the rest of our plugins: +`{ "path": "", "method": "", "filter": "" }` + +## Request Transforms +Inside a request transform you can use following variables: + +* `.body` - your current request body +* `._tyk_context` - Tyk context variables. You can use it to access request headers as well. + +Your JQ request transform should return an object in the following format: +`{ "body": , "rewrite_headers": , "tyk_context": }`. + +`body` is required, while `rewrite_headers` and `tyk_context` are optional. + +## Response Transforms +Inside a response transform you can use following variables: + +* `.body` - your current response body +* `._tyk_context` - Tyk context variables. You can use it to access request headers as well. +* `._tyk_response_headers` - Access to response headers + +Your JQ response transform should return an object in the following format: +`{ "body": , "rewrite_headers": }`. + +`body` is required, while `rewrite_headers` is optional. + +## Example +```{.json} +"extended_paths": { + "transform_jq": [{ + "path": "/post", + "method": "POST", + "filter": "{\"body\": (.body + {\"TRANSFORMED-REQUEST-BY-JQ\": true, path: ._tyk_context.path, user_agent: ._tyk_context.headers_User_Agent}), \"rewrite_headers\": {\"X-added-rewrite-headers\": \"test\"}, \"tyk_context\": {\"m2m_origin\": \"CSE3219/C9886\", \"deviceid\": .body.DEVICEID}}" + }], + "transform_jq_response": [{ + "path": "/post", + "method": "POST", + "filter": "{\"body\": (.body + {\"TRANSFORMED-RESPONSE-BY-JQ\": true, \"HEADERS-OF-RESPONSE\": ._tyk_response_headers}), \"rewrite_headers\": {\"JQ-Response-header\": .body.origin}}" + }] +} +``` + diff --git a/api-management/traffic-transformation/mock-response.mdx b/api-management/traffic-transformation/mock-response.mdx new file mode 100644 index 0000000000..ba16dc0b01 --- /dev/null +++ b/api-management/traffic-transformation/mock-response.mdx @@ -0,0 +1,324 @@ +--- +title: "Request Termination" +description: "Learn how to configure Tyk to handle a request entirely within the Gateway, returning a response to the client without forwarding to an upstream service" +keywords: "request termination, mock response, virtual endpoint, static response, fixed response, deprecated endpoint, endpoint blocking" +sidebarTitle: "Request Termination" +--- + +Request termination configures an endpoint to generate a response directly within Tyk Gateway, without forwarding the request to an upstream service. Tyk handles the request entirely and returns the response to the client. + +Common uses include: +- Returning a fixed response for a health check or status endpoint that requires no backend logic. +- Retiring a deprecated endpoint with an informative error response. +- Returning a maintenance message while an upstream service is offline. +- Implementing complex response logic or aggregation that would otherwise require a dedicated microservice. + +## Approaches + +Tyk Gateway offers three mechanisms for request termination, differing in flexibility and complexity: + +| Approach | Flexibility | Best For | +| :--- | :--- | :--- | +| [Mock Response](#mock-response) | Fixed response configured in the API definition | Static responses, deprecation notices, health checks, development mocking | +| [Virtual Endpoint](#virtual-endpoint) | JavaScript function runs in the Gateway | Dynamic responses, conditional logic, aggregation from multiple upstreams | +| [Custom Plugins](#custom-plugins) | Custom code in Go or via gRPC (Python, Lua, others) | High-performance termination, complex business logic, access to external systems | + +## Mock Response + +The mock response middleware returns a configured static response for an endpoint. No upstream call is made. + +### How It Works + +The point in the request pipeline at which the mock response fires differs between Tyk OAS and Tyk Classic: + +- **Tyk OAS**: the mock response fires near the end of the request-processing chain, after authentication, rate limiting, and request transforms. The request is fully authenticated before the response is generated. Analytics records are created as normal. +- **Tyk Classic**: the mock response fires at the start of the request-processing chain, before authentication. Credentials are not required to receive the response, and no analytics records are created. + +### Configuration + +#### Tyk OAS + +The `mockResponse` middleware is configured in the `operations` section of the Tyk Vendor Extension (`x-tyk-api-gateway`), under the `operationId` for the endpoint. + +The response that will be returned by the endpoint can be configured manually, or automatically generated from the OpenAPI description. + +**Manual Configuration** + +You can configure the desired response directly in the API definition: + +| Field | Description | +| :--- | :--- | +| `enabled` | Activates the middleware for the endpoint. | +| `code` | The HTTP response status code. Defaults to `200` if not set. | +| `body` | The response body, as a string. | +| `headers` | An array of `{ "name": "...", "value": "..." }` objects to include as response headers. | + +```json +"operations": { + "getBooksHealth": { + "mockResponse": { + "enabled": true, + "code": 200, + "body": "{\"status\": \"ok\"}", + "headers": [ + { "name": "Content-Type", "value": "application/json" } + ] + } + } +} +``` + +**From OAS Examples** + +Alternatively, the response can be generated automatically from example data or schema declarations in the OpenAPI description. + +The OpenAPI description provides the response content via one of three mechanisms: + +- **`example`** - a single sample value for a specific content type and response code +- **`examples`** - a named map of sample values; use `exampleName` in `fromOASExamples` to select one, or leave `exampleName` unset to use the first entry (sorted by key). +- **`schema`** - a JSON schema for the response body; Tyk uses any `example` values on individual schema properties, and falls back to type defaults (`"string"`, `0`, `true`) for properties with no example. + +`example` and `examples` are mutually exclusive within the OpenAPI description for a given response object - you cannot provide both for the same content type and response code. + +Response headers defined in the OpenAPI description via `headers[*].schema` are also included in the generated response. + +This approach is configured using `fromOASExamples`: + +| Field | Description | +| :--- | :--- | +| `enabled` | Activates automatic response generation from the OpenAPI description. | +| `code` | (Optional) Identifies the HTTP response code for the example response that should be returned. Defaults to `200`. | +| `contentType` | (Optional) Identifies the content type of the example response that should be returned. Defaults to `application/json`. | +| `exampleName` | (Optional) Identifies the specific example response that should be returned via the `name` given to the example in the OpenAPI description. If not set, the first entry alphabetically is used. | + +The optional `code`, `contentType` and `exampleName` fields are used to select a specific response when the OpenAPI description defines responses for multiple codes, content types, or named examples. The API client can override this configuration using [control headers](#response-selection) to select a specific response. + +```json +"operations": { + "anythingget": { + "mockResponse": { + "enabled": true, + "fromOASExamples": { + "enabled": true, + "code": 200, + "contentType": "text/plain", + "exampleName": "preview-example" + } + } + } +} +``` + +**API Designer** + +1. **Add an endpoint** - From the API Designer, add an endpoint for the path and method to configure. +2. **Select the Mock Response middleware** - Select **ADD MIDDLEWARE** and choose **Mock Response**. +3. **Configure the response**: + - To provide the response directly, select **Manually configure mock response** and set the HTTP status code, content type, response body, and any headers. + - To generate from the OpenAPI description, select **Use mock response from Open API Specification** and choose the desired response code and content type from the drop-down. +4. **Save the API** - Select **UPDATE MIDDLEWARE**, then **SAVE API**. + +#### Tyk Classic + +If using Tyk Classic, mock responses are manually configured via the `method_actions` field on an allow list, block list, or ignore authentication entry for the endpoint. Set `action` to `reply` and provide the response `code`, `data` (body), and `headers`. + +```json +"extended_paths": { + "white_list": [ + { + "path": "/health", + "method": "", + "ignore_case": false, + "method_actions": { + "GET": { + "action": "reply", + "code": 200, + "data": "{\"status\": \"ok\"}", + "headers": { + "Content-Type": "application/json" + } + } + } + } + ] +} +``` + +The endpoint must be registered in one of the three list types (`white_list`, `black_list`, or `ignored`) for the mock response to take effect. The choice of list type does not affect the response returned - it determines the path matching rules applied to other endpoints on the same API. + +Because the Tyk Classic mock response fires before authentication, the endpoint does not require credentials regardless of the authentication method configured for the API. + +**API Designer** + +1. **Add an endpoint** - From the Endpoint Designer, add an endpoint and select the **Allow List** (or **Block List** / **Ignore Authentication**) plugin to register the path. +2. **Add the mock response plugin** - Select the **Mock response** plugin on the same endpoint. +3. **Configure the response** - Set the HTTP status code, headers, and body. Select **ADD** to add each header. +4. **Save the API** - Select **Save** or **Create**. + +#### Tyk Operator + +Tyk Operator supports both Tyk OAS and Tyk Classic API definitions. The mock response middleware is configured in the same way as described above for each format. + +See [Tyk Operator](/api-management/automations/operator) for details on creating and managing API definitions with Tyk Operator. + +### Response Selection + +This section applies to mock responses generated from the OpenAPI description (`fromOASExamples`). Note that when the response is [configured directly](#tyk-oas) in the Tyk Vendor Extension, Tyk returns it as-is and response selection does not apply. + +Three control headers can be used in the request to override the defaults configured in the Tyk Vendor Extension, selecting a different response from the OpenAPI description without modifying the API definition: + +| Header | Overrides | Description | +| :--- | :--- | :--- | +| `Accept` | `fromOASExamples.contentType` | Standard HTTP header, for example `text/plain`. | +| `X-Tyk-Accept-Example-Code` | `fromOASExamples.code` | Selects which response code's definition to use, for example `404`. | +| `X-Tyk-Accept-Example-Name` | `fromOASExamples.exampleName` | Selects a named entry from an `examples` map. Has no effect when the media type uses a direct `example` value. | + +The three headers are independent and can be combined. They operate at different levels of the OAS response structure: +- `X-Tyk-Accept-Example-Code` selects the response object. +- `Accept` selects the media type within that response. +- `X-Tyk-Accept-Example-Name` selects a named entry within the media type's `examples` map. + +#### Defaults + +When `fromOASExamples` is enabled and no control headers are sent, Tyk uses the values from the `fromOASExamples` configuration as defaults: + +- **Response code** - the value of `fromOASExamples.code`, defaulting to `200` if not set. +- **Content type** - the value of `fromOASExamples.contentType`, defaulting to `application/json` if not set. +- **Example name** - the value of `fromOASExamples.exampleName`, if set. If not set, no name is used and selection falls through to the priority order below. + +#### Selection Priority + +Once the response code and content type have been resolved, Tyk selects the example body from the matching section of the OpenAPI description in the following order: + +1. **Direct `example` value** - if the media type declares a single `example` value (not an `examples` map), that value is returned. The `X-Tyk-Accept-Example-Name` header and `fromOASExamples.exampleName` are both ignored in this case. + +2. **Named entry from `examples`** - if an example name is set (via `fromOASExamples.exampleName` or the `X-Tyk-Accept-Example-Name` header), Tyk looks up that name in the media type's `examples` map. If the name is not found, Tyk returns `HTTP 404`. + +3. **First entry alphabetically from `examples`** - if no example name is set and the media type has an `examples` map, Tyk sorts the keys alphabetically and returns the first non-nil entry. This selection is deterministic from Tyk Gateway 5.8 onwards. In earlier versions, Tyk iterated directly over the map and the selection was non-deterministic. + +4. **Schema-derived example** - if no `example` or `examples` values are present, Tyk generates a body from the media type's JSON schema, using any `example` values on individual properties and falling back to type defaults (`"string"`, `0`, `true`) for properties with no example. + +If no example can be resolved at the selected code and content type, Tyk returns `HTTP 404`. + +#### Error Responses + +| Condition | Response | +| :--- | :--- | +| `X-Tyk-Accept-Example-Code` is not a valid integer | `HTTP 400` | +| No response defined for the selected HTTP code | `HTTP 404` | +| No content defined for the selected content type | `HTTP 404` | +| Named example not found in the `examples` map | `HTTP 404` | + +## Virtual Endpoint + +The virtual endpoint middleware runs a JavaScript function directly within Tyk Gateway. The function determines the response returned to the client. No upstream call is made unless the function explicitly makes one. + +Virtual endpoints run after authentication, rate limiting, and request transforms. They have access to the full request context and the client's session data. + +Use a virtual endpoint when the response depends on logic that cannot be expressed in static configuration - for example, conditional responses based on request content, aggregation of data from multiple upstream services, or calls to an external service to look up data before responding. + +Virtual endpoints require the Tyk JavaScript Virtual Machine to be enabled in `tyk.conf` (`enable_jsvm: true`). They are not available in deployments where the JSVM is disabled. + +For full configuration detail, the JavaScript function API, and examples, see [Virtual Endpoints](/api-management/traffic-transformation/virtual-endpoints). + +## Custom Plugins + +Custom plugins can also terminate requests by writing a response directly, without forwarding to an upstream service. Unlike mock response and virtual endpoints, plugins are compiled and loaded separately from the API definition, and can call external systems, use arbitrary dependencies, and run at native speed. + +Plugins can be attached at multiple points in the request-processing chain - as pre-auth hooks, post-auth hooks, or response hooks. A plugin that terminates the request should be registered as a post-auth hook when authentication is required, or as a pre-auth hook when the response must be returned before authentication runs. + +For full detail on writing and deploying plugins, see: +- [Go Plugins](/api-management/plugins/golang) +- [Rich Plugins](/api-management/plugins/rich-plugins) (gRPC, Python, Lua) +- [Plugin Types](/api-management/plugins/plugin-types) + +## Worked Example + +Acme Publishing is retiring the legacy single-book download endpoint `GET /books/{category}/{id}/download` in favor of the authenticated download flow introduced in [Internal Routing](/advanced-configuration/transform-traffic/looping). The endpoint is configured to return `HTTP 410 Gone` with a message directing clients to the replacement flow. + +**Tyk OAS Configuration** + +```json +"operations": { + "getLegacyBookDownload": { + "mockResponse": { + "enabled": true, + "code": 410, + "body": "This endpoint has been retired. Download books using GET /books/{category}/{id}?download=true with a valid subscriber token.", + "headers": [ + { "name": "Content-Type", "value": "text/plain" }, + { "name": "Deprecation", "value": "true" } + ] + } + } +} +``` + +If using Tyk Classic, add to `extended_paths.white_list` (or `black_list` / `ignored`): + +```json +"extended_paths": { + "white_list": [ + { + "path": "/{category}/{id}/download", + "method": "", + "ignore_case": false, + "method_actions": { + "GET": { + "action": "reply", + "code": 410, + "data": "This endpoint has been retired. Download books using GET /books/{category}/{id}?download=true with a valid subscriber token.", + "headers": { + "Content-Type": "text/plain", + "Deprecation": "true" + } + } + } + } + ] +} +``` + +**Outcomes** + +| Request | Response | +| :--- | :--- | +| `GET /books/fiction/9780/download` | `HTTP 410` with deprecation message and `Deprecation: true` header | + +With Tyk OAS, the Books API is keyless so no credentials are required; the mock response fires after the no-op authentication stage. With Tyk Classic, the response fires before authentication. + +**DIAGRAM PLACEHOLDER: Request Termination vs Normal Routing** +{/* + Diagram: Request Termination vs Normal Routing + + Purpose: Show the contrast between a normal request (forwarded to upstream) and a + terminated request (response generated at the Gateway). The key insight is that + the request never reaches the upstream when termination is configured. + + Structure: Two vertical flows side by side. + + Left flow (normal routing): + 1. Client sends request + 2. Gateway: Auth → Rate limit → Transforms (single labelled box) + 3. Arrow to upstream service (grey rectangle) + 4. Response returned to client + + Right flow (request termination): + 1. Client sends request + 2. Gateway: Auth → Rate limit → Transforms (same box structure as left) + 3. Mock Response / Virtual Endpoint / Plugin box (blue, labelled "terminates here") + 4. Arrow blocked before upstream - upstream box greyed out with "not reached" label + 5. Response returned directly to client from the termination box + + Key visual elements: + - The "terminates here" label on the right flow's termination box is the most + important annotation - make it visually prominent + - The upstream box on the right must be visually suppressed (grey, strikethrough, + or absent) to make clear the upstream is not reached + - A small annotation "no upstream call" on the right flow + + Design notes: + - Keep both flows vertically aligned so the contrast is immediately obvious + - A small inset below the diagram can show the Tyk Classic difference: mock + response fires before the Auth/Rate limit box, not after it +*/} diff --git a/api-management/traffic-transformation/request-body.mdx b/api-management/traffic-transformation/request-body.mdx new file mode 100644 index 0000000000..4731aac989 --- /dev/null +++ b/api-management/traffic-transformation/request-body.mdx @@ -0,0 +1,395 @@ +--- +title: "Request Body" +description: "Learn how to transform the body of API requests" +keywords: "Traffic Transformation, Request Body" +sidebarTitle: "Request Body" +--- + +## Overview + +Tyk enables you to modify the payload of API requests before they are proxied to the upstream. This makes it easy to transform between payload data formats or to expose legacy APIs using newer schema models without having to change any client implementations. This middleware is only applicable to HTTP methods that can support a request body (i.e. PUT, POST or PATCH). + +With the body transform middleware you can modify XML or JSON formatted payloads to ensure that the response contains the information required by your upstream service. You can enrich the request by adding contextual data that is held by Tyk but not included in the original request from the client. + +This middleware changes only the payload and not the headers. You can, however, combine this with the [Request Header Transform](/api-management/traffic-transformation/request-headers) middleware to apply more complex transformation to requests. + +There is a closely related [Response Body Transform](/api-management/traffic-transformation/response-body) middleware that provides the same functionality on the response from the upstream, prior to it being returned to the client. + +### Use Cases + +#### Maintaining compatibility with legacy clients + +Sometimes you might have a legacy API and need to migrate the transactions to a new upstream service but do not want to upgrade all the existing clients to the newer upstream API. Using request body transformation, you can convert the incoming legacy XML or JSON request structure into a newer, cleaner JSON format that your upstream services expect. + +#### Shaping requests received from different devices + +You can detect device types via headers or context variables and transform the request payload to optimize it for that particular device. For example, you might send extra metadata to the upstream for mobile apps. + +#### SOAP to REST translation + +A common use of the request body transform middleware is to surface a legacy SOAP service with a REST API. Full details of how to perform this conversion using Tyk are provided [here](/advanced-configuration/transform-traffic/soap-rest). + +### Working + +Tyk's body transform middleware uses the [Go template language](https://golang.org/pkg/text/template/) to parse and modify the provided input. We have bundled the [Sprig Library (v3)](http://masterminds.github.io/sprig/) which provides over 70 pre-written functions for transformations to assist the creation of powerful Go templates to transform your API requests. + +The Go template can be defined within the API Definition or can be read from a file that is accessible to Tyk. + +We have provided more detail, links to reference material and some examples of the use of Go templating [here](/api-management/traffic-transformation/go-templates). + + + +Tyk evaluates templates stored in files on startup, so if you make changes to a template you must remember to restart the gateway. + + + +#### Supported request body formats + +The body transformation middleware can modify request payloads in the following formats: +- JSON +- XML + +When working with JSON format data, the middleware will unmarshal the data into a data structure, and then make that data available to the template in dot-notation. + +#### Data accessible to the middleware + +The middleware has direct access to the request body and also to dynamic data as follows: + - [context variables](/api-management/traffic-transformation/request-context-variables), extracted from the request at the start of the middleware chain, can be injected into the template using the `._tyk_context.KEYNAME` namespace + - [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context), from the Tyk Session Object linked to the request, can be injected into the template using the `._tyk_meta.KEYNAME` namespace + - inbound form or query data can be accessed through the `._tyk_context.request_data` namespace where it will be available in as a `key:[]value` map + - values from [key-value (KV) storage](/tyk-configuration-reference/kv-store#transformation-middleware) can be injected into the template using the notation appropriate to the location of the KV store + +The request body transform middleware can iterate through list indices in dynamic data so, for example, calling `{{ index ._tyk_context.request_data.variablename 0 }}` in a template will expose the first entry in the `request_data.variablename` key/value array. + + + +As explained in the [documentation](https://pkg.go.dev/text/template), templates are executed by applying them to a data structure. The template receives the decoded JSON or XML of the request body. If session variables or meta data are enabled, additional fields will be provided: `_tyk_context` and `_tyk_meta` respectively. + + + +#### Automatic XML <-> JSON Transformation + +A very common transformation that is applied in the API Gateway is to convert between XML and JSON formatted body content. + +The Request Body Transform supports two helper functions that you can use in your Go templates to facilitate this: + - `jsonMarshal` performs JSON style character escaping on an XML field and, for complex objects, serialises them to a JSON string ([example](/api-management/traffic-transformation/go-templates#xml-to-json-conversion-using-jsonmarshal)) + - `xmlMarshal` performs the equivalent conversion from JSON to XML ([example](/api-management/traffic-transformation/go-templates#json-to-xml-conversion-using-xmlmarshal)) + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Request Body Transform middleware summary + - The Request Body Transform middleware is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Request Body Transform middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. + - Request Body Transform can access both [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) and [request context variables](/api-management/traffic-transformation/request-context-variables). */} + +## Using Tyk OAS + + +The [request body transform](/api-management/traffic-transformation/request-body) middleware provides a way to modify the payload of API requests before they are proxied to the upstream. + +The middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#request-body-using-classic) page. + +### API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The request body transformation middleware (`transformRequestBody`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `transformRequestBody` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `format`: the format of input data the parser should expect (either `xml` or `json`) +- `body`: [see note] this is a `base64` encoded representation of your template +- `path`: [see note] this is the path to the text file containing the template + + + + + You should configure only one of `body` or `path` to indicate whether you are embedding the template within the middleware or storing it in a text file. The middleware will automatically select the correct source based on which of these fields you complete. If both are provided, then `body` will take precedence and `path` will be ignored. + + + +For example: +```json {hl_lines=["39-43"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-request-body-transform", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "put": { + "operationId": "anythingput", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-request-body-transform", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-request-body-transform/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingput": { + "transformRequestBody": { + "enabled": true, + "format": "json", + "body": "ewogICJ2YWx1ZTEiOiAie3sudmFsdWUyfX0iLAogICJ2YWx1ZTIiOiAie3sudmFsdWUxfX0iLAogICJyZXEtaGVhZGVyIjogInt7Ll90eWtfY29udGV4dC5oZWFkZXJzX1hfSGVhZGVyfX0iLAogICJyZXEtcGFyYW0iOiAie3suX3R5a19jb250ZXh0LnJlcXVlc3RfZGF0YS5wYXJhbX19Igp9" + } + } + } + } + } +} +``` + +In this example the request body transform middleware has been configured for requests to the `PUT /anything` endpoint. The `body` contains a base64 encoded Go template (which you can check by pasting the value into a service such as [base64decode.org](https://www.base64decode.org)). + +Decoded, this template is: +```json +{ + "value1": "{{.value2}}", + "value2": "{{.value1}}", + "req-header": "{{._tyk_context.headers_X_Header}}", + "req-param": "{{._tyk_context.request_data.param}}" +} +``` + +So if you make a request to `PUT /anything?param=foo` as follows: +```bash +PUT /anything?param=foo +HTTP/1.1 +Host: my-gateway.host +X-Header: bar + +{ + "value1": "world", + "value2": "hello" +} +``` + +You will receive a response from the upstream with this payload: +```json +{ + "req-header": "bar", + "req-param": "[foo]", + "value1": "hello", + "value2": "world" +} +``` + +The `/anything` endpoint returns the details of the request that was received by httpbin.org. You can see that Tyk has swapped `value1` and `value2` and embedded the `X-Header` header and `param` query values into the body of the request. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the mock response middleware. + + + +If using a template in a file (i.e. you configure `path` in the `transformRequestBody` object), remember that Tyk will load and evaluate the template when the Gateway starts up. If you modify the template, you will need to restart Tyk in order for the changes to take effect. + + + +### API Designer + +Adding Request Body Transformation to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow the following steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Request Body Transform middleware** + + Select **ADD MIDDLEWARE** and choose the **Request Body Transform** middleware from the *Add Middleware* screen. + + Adding the Request Body Transform middleware + +3. **Configure the middleware** + + Now you can select the request body format (JSON or XML) and add either a path to the file containing the template, or directly enter the transformation template in the text box. + + Configuring the Request Body Transform middleware + + The **Test with data** control will allow you to test your body transformation function by providing an example request body and generating the output from the transform. It is not possible to configure headers, other request parameters, context or session metadata to this template test so if you are using these data sources in your transform it will not provide a complete output, for example: + + Testing the Request Body Transform + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [request body transform](/api-management/traffic-transformation/request-body) middleware provides a way to modify the payload of API requests before they are proxied to the upstream. + +This middleware is configured in the Tyk Classic API Definition at the endpoint level. You can do this via the Tyk Dashboard API or in the API Designer. + +If you want to use dynamic data from context variables, you must [enable](/api-management/traffic-transformation/request-context-variables#enabling-context-variables-for-use-with-tyk-classic-apis) context variables for the API to be able to access them from the request header transform middleware. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#request-body-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [Configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +To enable the middleware you must add a new `transform` object to the `extended_paths` section of your API definition. + +The `transform` object has the following configuration: +- `path`: the path to match on +- `method`: this method to match on +- `template_data`: details of the Go template to be applied for the transformation of the request body + +The Go template is described in the `template_data` object by the following fields: +- `input_type`: the format of input data the parser should expect (either `xml` or `json`) +- `enable_session`: set this to `true` to make session metadata available to the transform template +- `template_mode`: instructs the middleware to look for the template either in a `file` or in a base64 encoded `blob`; the actual file location (or base64 encoded template) is provided in `template_source` +- `template_source`: if `template_mode` is set to `file`, this will be the path to the text file containing the template; if `template_mode` is set to `blob`, this will be a `base64` encoded representation of your template + +For example: +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "transform": [ + { + "path": "/anything", + "method": "POST", + "template_data": { + "template_mode": "file", + "template_source": "./templates/transform_test.tmpl", + "input_type": "json", + "enable_session": true + } + } + ] + } +} +``` + +In this example, the Request Body Transform middleware is directed to use the template located in the `file` at location `./templates/transform_test.tmpl`. The input (pre-transformation) request payload will be `json` format and session metadata will be available for use in the transformation. + + + +Tyk will load and evaluate the template file when the Gateway starts up. If you modify the template, you will need to restart Tyk in order for the changes to take effect. + + + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the request body transform middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + +From the **Endpoint Designer** add an endpoint that matches the path for which you want to perform the transformation. Select the **Body Transforms** plugin. + +Endpoint designer + +2. **Configure the middleware** + +Ensure that you have selected the `REQUEST` tab, then select your input type, and then add the template you would like to use to the **Template** input box. + +Setting the body request transform + +3. **Test the Transform** + +If sample input data is available, you can use the Input box to add it, and then test it using the **Test** button. You will see the effect of the template on the sample input displayed in the Output box. + +Testing the body transform function + +4. **Save the API** + +Use the *save* or *create* buttons to save the changes and activate the Request Body Transform middleware. + +### Tyk Operator + +The process for configuring a request body transform is similar to that defined in section configuring the middleware in the Tyk Classic API Definition. Tyk Operator allows you to configure a request body transform by adding a `transform` object to the `extended_paths` section of your API definition. + +In the example below the Request Body middleware (`transform`) has been configured for `HTTP POST` requests to the `/anything` endpoint. The Request Body Transform middleware is directed to use the template located in the blob included in the `template_source` field. The input (pre-transformation) request payload will be json format and session metadata will be available for use in the transformation. + +```yaml {linenos=true, linenostart=1, hl_lines=["32-40"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-transform +spec: + name: httpbin-transform + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-transform + strip_listen_path: true + response_processors: + - name: response_body_transform + - name: header_injector + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + transform: + - method: POST + path: /anything + template_data: + enable_session: false + input_type: json + template_mode: blob + # base64 encoded template + template_source: eyJiYXIiOiAie3suZm9vfX0ifQ== + transform_headers: + - delete_headers: + - "remove_this" + add_headers: + foo: bar + path: /anything + method: POST + transform_response: + - method: GET + path: /xml + template_data: + enable_session: false + input_type: xml + template_mode: blob + # base64 encoded template + template_source: e3sgLiB8IGpzb25NYXJzaGFsIH19 + transform_response_headers: + - method: GET + path: /xml + add_headers: + Content-Type: "application/json" + act_on: false + delete_headers: [] +``` + diff --git a/api-management/traffic-transformation/request-context-variables.mdx b/api-management/traffic-transformation/request-context-variables.mdx new file mode 100644 index 0000000000..783370d6a8 --- /dev/null +++ b/api-management/traffic-transformation/request-context-variables.mdx @@ -0,0 +1,122 @@ +--- +title: "Request Context Variables" +description: "Learn how to use the request context in your transformation middleware" +keywords: "Traffic Transformation, Request Context Variables" +sidebarTitle: "Request Context Variables" +--- + +Context variables are extracted from the request at the start of the middleware chain. These values can be very useful for later transformation of request data, for example, in converting a form POST request into a JSON PUT request or to capture an IP address as a header. + + + +When using Tyk Classic APIs, you must [enable](#enabling-context-variables-for-use-with-tyk-classic-apis) context variables for the API to be able to access them. When using Tyk OAS APIs, the context variables are always available to the context-aware middleware. + + + + +## Available context variables + +| Variable name | Description | +|----------------|-------------| +| `request_data` | If the inbound request contained any query data or form data, it will be available in this object. For the header injector Tyk will format this data as `key:value1,value2,valueN;key:value1,value2` etc. | +| `path_parts` | The components of the path, split on `/`. These values should be in the format of a comma delimited list. | +|`token` | The inbound raw token of this user (if bearer tokens are being used). | +| `path` | The path that is being requested. | +| `remote_addr` | The client's IP address. | +| `request_id` | Allows the injection of request correlation ID (for example X-Request-ID) | +| `jwt_claims_CLAIMNAME` | If JWT tokens are being used, then each claim in the JWT is available in this format to the context processor. `CLAIMNAME` is case sensitive so use the exact claim. | +| `cookies_COOKIENAME` | If there are cookies, then each cookie is available in context processor in this format. `COOKIENAME` is case sensitive so use the exact cookie name and replace any `-` in the cookie name with `_`. | +| `headers_HEADERNAME` | Request headers are accessed using the following format: Convert the **first letter** in each word of an incoming header to Capital Case and replace any `-` in the `HEADERNAME` name with `_`. For example, to get the value stored in `test-header`, the syntax would be `$tyk_context.headers_Test_Header`. | + +From Tyk 5.13.0 the current Session's rate limit and quota data are available from the following context variables: + +| Variable name | Description | +|------------------------|-------------| +| `rate_limit_limit` | Number of requests that can be sent in the rate limit period | +| `rate_limit_remaining` | Number of requests remaining in the current rate limit period | +| `rate_limit_reset` | Timestamp for next rate limit period reset (Unix time) | +| `quota_limit` | Number of requests that can be sent in the quota period | +| `quota_remaining` | Number of requests remaining in the current quota period | +| `quota_reset` | Timestamp for next quota period reset (Unix time) | + +## Middleware that can use context variables: +Context variables are exposed in three middleware plugins but are accessed differently depending on the caller as follows: + +1. URL Rewriter - Syntax is `$tyk_context.CONTEXTVARIABLES`. See [Path Modification](/transform-traffic/url-rewriting) for more details. +2. Modify Headers - Syntax is `$tyk_context.CONTEXTVARIABLES`. See [Request Headers](/api-management/traffic-transformation/request-headers) for more details. +3. Body Transforms - Syntax is `{{ ._tyk_context.CONTEXTVARIABLES }}`. See [Body Transforms](/api-management/traffic-transformation/request-body) for more details. + + + + + The Body Transform can fully iterate through list indices within context data so, for example, calling `{{ index ._tyk_context.path_parts 0 }}` in the Go Template in a Body Transform will expose the first entry in the `path_parts` list. + + URL Rewriter and Header Transform middleware cannot iterate through list indices. + + + + +## Example use of context variables + +### Examples of the syntax to use with all the available context variables: +``` +"x-remote-addr": "$tyk_context.remote_addr", +"x-token": "$tyk_context.token", +"x-jwt-sub": "$tyk_context.jwt_claims_sub", +"x-part-path": "$tyk_context.path_parts", +"x-jwt-pol": "$tyk_context.jwt_claims_pol", +"x-cookie": "$tyk_context.cookies_Cookie_Context_Var", +"x-cookie-sensitive": "$tyk_context.cookies_Cookie_Case_sensitive", +"x-my-header": "$tyk_context.headers_My_Header", +"x-path": "$tyk_context.path", +"x-request-data": "$tyk_context.request_data", +"x-req-id": "$tyk_context.request_id" +``` +Example of the syntax in the UI + +### The context variable values in the response: +``` +"My-Header": "this-is-my-header", +"User-Agent": "PostmanRuntime/7.4.0", +"X-Cookie": "this-is-my-cookie", +"X-Cookie-Sensitive": "case-sensitive", +"X-Jwt-Pol": "5bca6a739afe6a00017eb267", +"X-Jwt-Sub": "john.doe@test.com", +"X-My-Header": "this-is-my-header", +"X-Part-Path": "context-var-example,anything", +"X-Path": "/context-var-example/anything", +"X-Remote-Addr": "127.0.0.1", +"X-Req-Id": "e3e99350-b87a-4d7d-a75f-58c1f89b2bf3", +"X-Request-Data": "key1:val1;key2:val2", +"X-Token": "5bb2c2abfb6add0001d65f699dd51f52658ce2d3944d3d6cb69f07a2" +``` + +## Enabling Context Variables for use with Tyk Classic APIs +1. In the your Tyk Dashboard, select `APIs` from the `System Management` menu +2. Open the API you want to add Context Variable to +3. Click the `Advanced Options` tab and then select the `Enable context variables` option + +Context Variables + +If not using a Tyk Dashboard, add the field `enable_context_vars` to your API definition file at root level and set it to `true`. + +If you are using Tyk Operator, set the field `spec.enable_context_vars` to `true`. + +The example API Definition below enabled context variable: + +```yaml {linenos=true, linenostart=1, hl_lines=["10-10"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + enable_context_vars: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` diff --git a/api-management/traffic-transformation/request-headers.mdx b/api-management/traffic-transformation/request-headers.mdx new file mode 100644 index 0000000000..0e859e6520 --- /dev/null +++ b/api-management/traffic-transformation/request-headers.mdx @@ -0,0 +1,554 @@ +--- +title: "Request Headers" +description: "Learn how to modify API request headers" +keywords: "Traffic Transformation, Request Headers" +sidebarTitle: "Request Headers" +--- + +## Overview + +Tyk allows you to modify the headers of incoming requests to your API endpoints before they are passed to your upstream service. + +There are two options for this: +- API-level modification that is applied to all requests to the API +- endpoint-level modification that is applied only to requests to a specific endpoint + +With the header transform middleware you can append or delete any number of headers to ensure that the request contains the information required by your upstream service. You can enrich the request by adding contextual data that is held by Tyk but not included in the original request from the client. + +This middleware changes only the headers and not the method or payload. You can, however, combine this with the [Request Method Transform](/api-management/traffic-transformation/request-method) and [Request Body Tranform](/api-management/traffic-transformation/request-body) to apply more complex transformation to requests. + +There are related [Response Header Transform](/api-management/traffic-transformation/response-headers) middleware (at API-level and endpoint-level) that provide the same functionality on the response from your upstream, prior to it being returned to the client. + +### Use Cases + +#### Adding Custom Headers + +A common use of this feature is to add custom headers to requests, such as adding a secure header to all upstream requests (to verify that traffic is coming from the gateway), or adding a timestamp for tracking purposes. + +#### Modifying Headers for Compatibility + +You could use the request header transform middleware to modify headers for compatibility with a downstream system, such as changing the Content-Type header from "application/json" to "application/xml" for an API that only accepts XML requests while using the [Request Body Tranform](/api-management/traffic-transformation/request-body) to transform the payload. + +#### Prefixing or Suffixing Headers + +Upstream systems or corporate policies might mandate that a prefix or suffix is added to header names, such as adding a "Bearer" prefix to all Authorization headers for easier identification internally, without modifying the externally published API consumed by the client applications. + +#### Adding multi-user access to a service + +You can add multi-user access to an upstream API that has a single authentication key and you want to add multi-user access to it without modifying it or adding clunky authentication methods to it to support new users. + +### Working + +The request header transform can be applied per-API or per-endpoint; each has a separate entry in the API definition so that you can configure both API-level and endpoint-level transforms for a single API. + +The middleware is configured with a list of headers to delete from the request and a list of headers to add to the request. Each header to be added to the request is configured as a key:value pair. + +The "delete header" functionality is intended to ensure that any header in the delete list is not present once the middleware completes - so if a header is not originally present in the request but is on the list to be deleted, the middleware will ignore its omission. + +The "add header" functionality will capitalize any header name provided, for example if you configure the middleware to append `x-request-id` it will be added to the request as `X-Request-Id`. + +In the request middleware chain, the API-level transform is applied before the endpoint-level transform so if both middleware are enabled, the endpoint-level transform will operate on the headers that have been added by the API-level transform (and will not receive those that have been deleted by it). + +#### Injecting dynamic data into headers + +You can enrich the request headers by injecting data from context variables or session objects into the headers. +- [context variables](/api-management/traffic-transformation/request-context-variables) are extracted from the request at the start of the middleware chain and can be injected into added headers using the `$tyk_context.` namespace +- [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context), from the Tyk Session Object linked to the request, can be injected into added headers using the `$tyk_meta.` namespace +- values from [key-value (KV) storage](/tyk-configuration-reference/kv-store#transformation-middleware) can be injected into added headers using the notation appropriate to the location of the KV store + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Request Header Transform middleware summary + - The Request Header Transform is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Request Header Transform can be configured at the per-endpoint or per-API level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + +## Using Tyk OAS + + +Tyk's [request header transform](/api-management/traffic-transformation/request-headers) middleware enables you to append or delete headers on requests to your API endpoints before they are passed to your upstream service. + +There are two options for this: +- API-level modification that is applied to all requests to the API +- endpoint-level modification that is applied only to requests to a specific endpoint + + + + + If both API-level and endpoint-level middleware are configured, the API-level transformation will be applied first. + + + +When working with Tyk OAS APIs the transformation is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#request-headers-using-classic) page. + +### API Definition + +The API-level and endpoint-level request header transforms are configured in different sections of the API definition, though have a common configuration. + +### API-level transform + +To append headers to, or delete headers from, all requests to your API (i.e. for all endpoints) you must add a new `transformRequestHeaders` object to the `middleware.global` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition. + +You only need to enable the middleware (set `enabled:true`) and then configure the details of headers to `add` and those to `remove`. + +For example: +```json {hl_lines=["38-56"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-request-header", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/status/200": { + "get": { + "operationId": "status/200get", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-request-header", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-request-header/", + "strip": true + } + }, + "middleware": { + "global": { + "transformRequestHeaders": { + "enabled": true, + "remove": [ + "Auth_Id" + ], + "add": [ + { + "name": "X-Static", + "value": "foobar" + }, + { + "name": "X-Request-ID", + "value": "$tyk_context.request_id" + }, + { + "name": "X-User-ID", + "value": "$tyk_meta.uid" + } + ] + } + } + } + } +} +``` + +This configuration will add three new headers to each request: +- `X-Static` with the value `foobar` +- `X-Request-ID` with a dynamic value taken from the `request_id` [context variables](/api-management/traffic-transformation/request-context-variables) +- `X-User-ID` with a dynamic value taken from the `uid` field in the [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) + +It will also delete one header (if present) from each request: +- `Auth_Id` + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the API-level request header transform. + +### Endpoint-level transform + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The request header transform middleware (`transformRequestHeaders`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `transformRequestHeaders` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `add`: a list of headers, in key:value pairs, to be appended to the request +- `remove`: a list of headers to be deleted from the request (if present) + +For example: +```json {hl_lines=["39-50"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-request-header", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/status/200": { + "get": { + "operationId": "status/200get", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-request-header", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-request-header/", + "strip": true + } + }, + "middleware": { + "operations": { + "status/200get": { + "transformRequestHeaders": { + "enabled": true, + "remove": [ + "X-Static" + ], + "add": [ + { + "name": "X-Secret", + "value": "the-secret-key-is-secret" + } + ] + } + } + } + } + } +} +``` + +In this example the Request Header Transform middleware has been configured for requests to the `GET /status/200` endpoint. Any request received to that endpoint will have the `X-Static` header removed and the `X-Secret` header added, with the value set to `the-secret-key-is-secret`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the endpoint-level request header transform. + +### Combining API-level and Endpoint-level transforms + +If the API-level transform in the previous [example](/api-management/traffic-transformation/request-headers#api-level-transform) is applied to the same API, then because the API-level transformation is performed first, the `X-Static` header will be added (by the API-level transform) and then removed (by the endpoint-level transform) such that the overall effect of the two transforms for a call to `GET /status/200` would be to add three headers: + - `X-Request-ID` + - `X-User-ID` + - `X-Secret` + +and to remove one: + - `Auth_Id` + +### API Designer + +Adding and configuring the transforms to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +### Adding an API-level transform + +From the **API Designer** on the **Settings** tab, after ensuring that you are in *edit* mode, toggle the switch to **Enable Transform request headers** in the **Middleware** section: +Tyk OAS API Designer showing API-level Request Header Transform + +Then select **NEW HEADER** as appropriate to add or remove a header from API requests. You can add or remove multiple headers by selecting **ADD HEADER** to add another to the list: +Configuring the API-level Request Header Transform in Tyk OAS API Designer + +### Adding an endpoint level transform + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Request Header Transform middleware** + + Select **ADD MIDDLEWARE** and choose the **Request Header Transform** middleware from the *Add Middleware* screen. + + Adding the Request Header Transform middleware + +3. **Configure header transformation** + + Select **NEW HEADER** to configure a header to be added to or removed from the request. + + Configuring the Request Header transformation + + You can add multiple headers to either list by selecting **NEW HEADER** again. + + Adding another header to the transformation + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes. + +## Using Classic + + +Tyk's [request header transform](/api-management/traffic-transformation/request-headers) middleware enables you to append or delete headers on requests to your API endpoints before they are passed to your upstream service. + +There are two options for this: +- API-level modification that is applied to all requests to the API +- endpoint-level modification that is applied only to requests to a specific endpoint + + + + + If both API-level and endpoint-level middleware are configured, the API-level transformation will be applied first. + + + +When working with Tyk Classic APIs the transformation is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you want to use dynamic data from context variables, you must [enable](/api-management/traffic-transformation/request-context-variables#enabling-context-variables-for-use-with-tyk-classic-apis) context variables for the API to be able to access them from the request header transform middleware. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#request-headers-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the Request Header Transform in Tyk Operator](#tyk-operator) section below. + +### API Definition + +The API-level and endpoint-level request header transforms have a common configuration but are configured in different sections of the API definition. + +#### API-level transform + + +To **append** headers to all requests to your API (i.e. for all endpoints) you must add a new `global_headers` object to the `versions` section of your API definition. This contains a list of key:value pairs, being the names and values of the headers to be added to requests. + +To **delete** headers from all requests to your API, you must add a new `global_headers_remove` object to the `versions` section of the API definition. This contains a list of the names of existing headers to be removed from requests. + +For example: +```json {hl_lines=["39-45"],linenos=true, linenostart=1} +{ + "version_data": { + "versions": { + "Default": { + "global_headers": { + "X-Static": "foobar", + "X-Request-ID":"$tyk_context.request_id", + "X-User-ID": "$tyk_meta.uid" + }, + "global_headers_remove": [ + "Auth_Id" + ] + } + } + }, +} +``` + +This configuration will add three new headers to each request: +- `X-Static` with the value `foobar` +- `X-Request-ID` with a dynamic value taken from the `request_id` [context variables](/api-management/traffic-transformation/request-context-variables) +- `X-User-ID` with a dynamic value taken from the `uid` field in the [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) + +It will also delete one header (if present) from each request: +- `Auth_Id` + +#### Endpoint-level transform + + +To configure a transformation of the request header for a specific endpoint you must add a new `transform_headers` object to the `extended_paths` section of your API definition. + +It has the following configuration: +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `delete_headers`: A list of the headers that should be deleted from the request +- `add_headers`: A list of headers, in key:value pairs, that should be added to the request + +The `path` can contain wildcards in the form of any string bracketed by curly braces, for example `{user_id}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +For example: +```json +{ + "transform_headers": [ + { + "path": "status/200", + "method": "GET", + "delete_headers": ["X-Static"], + "add_headers": {"X-Secret": "the-secret-key-is-secret"} + } + ] +} +``` + +In this example the Request Header Transform middleware has been configured for HTTP `GET` requests to the `/status/200` endpoint. Any request received to that endpoint will have the `X-Static` header removed and the `X-Secret` header added, with the value set to `the-secret-key-is-secret`. + +#### Combining API-level and Endpoint-level transforms + +If the API-level transform in the previous [example](/api-management/traffic-transformation/request-headers#api-level-transform) is applied to the same API, then because the API-level transformation is performed first, the `X-Static` header will be added (by the API-level transform) and then removed (by the endpoint-level transform) such that the overall effect of the two transforms for a call to `GET /status/200` would be to add three headers: +- `X-Request-ID` +- `X-User-ID` +- `X-Secret` + +and to remove one: +- `Auth_Id` + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the request header transform middleware for your Tyk Classic API by following these steps. + +#### API-level transform + +Configuring the API-level request header transform middleware is very simple when using the Tyk Dashboard. + +In the Endpoint Designer you should select the **Global Version Settings** and ensure that you have selected the **Request Headers** tab: + +Global version settings + +Note that you must click **ADD** to add a header to the list (for appending or deletion). + +#### Endpoint-level transform + +1. **Add an endpoint for the path and select the Header Transform plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to perform the transformation. Select the **Modify Headers** plugin. + + Endpoint designer + +2. **Select the "Request" tab** + + This ensures that this will only be applied to inbound requests. + + Request tab + +3. **Declare the headers to be modified** + + Select the headers to delete and insert using the provided fields. You need to click **ADD** to ensure they are added to the list. + + Header transforms + +4. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +The process for configuring a request header transform is similar to that defined in section Configuring the Request Header Transform in the Tyk Classic API Definition. Tyk Operator allows you to configure a request size limit for [all endpoints of an API](#tyk-operator-api) or for a [specific API endpoint](#tyk-operator-endpoint). + +#### API-level transform + + +Request headers can be removed and inserted using the following fields within an `ApiDefinition`: + +- `global_headers`: Mapping of key values corresponding to headers to add to API requests. +- `global_headers_remove`: List containing the name of headers to remove from API requests. + +The example below shows an `ApiDefinition` custom resource that adds *foo-req* and *bar-req* headers to the request before it is sent upstream. The *foo-req* header has a value of *foo-val* and the *bar-req* header has a value of *bar-val*. Furthermore, the *hello* header is removed from the request before it is sent upstream. + +```yaml {linenos=true, linenostart=1, hl_lines=["25-29"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-global-headers +spec: + name: httpbin-global-headers + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-global-headers + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + global_headers: + foo-req: my-foo + bar-req: my-bar + global_headers_remove: + - hello +``` + +#### Endpoint-level transform + + +The process of configuring a transformation of a request header for a specific endpoint is similar to that defined in section [Endpoint-level transform](#tyk-classic-endpoint). To configure a transformation of the request header for a specific endpoint you must add a new `transform_headers` object to the `extended_paths` section of your API definition. + +In the example below the Request Header Transform middleware (`transform_headers`) has been configured for HTTP `POST` requests to the `/anything` endpoint. Any request received to that endpoint will have the `remove_this` header removed and the `foo` header added, with the value set to `bar`. + +```yaml {linenos=true, linenostart=1, hl_lines=["41-47"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-transform +spec: + name: httpbin-transform + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-transform + strip_listen_path: true + response_processors: + - name: response_body_transform + - name: header_injector + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + transform: + - method: POST + path: /anything + template_data: + enable_session: false + input_type: json + template_mode: blob + # base64 encoded template + template_source: eyJiYXIiOiAie3suZm9vfX0ifQ== + transform_headers: + - delete_headers: + - "remove_this" + add_headers: + foo: bar + path: /anything + method: POST + transform_response: + - method: GET + path: /xml + template_data: + enable_session: false + input_type: xml + template_mode: blob + # base64 encoded template + template_source: e3sgLiB8IGpzb25NYXJzaGFsIH19 + transform_response_headers: + - method: GET + path: /xml + add_headers: + Content-Type: "application/json" + act_on: false + delete_headers: [] +``` + diff --git a/api-management/traffic-transformation/request-method.mdx b/api-management/traffic-transformation/request-method.mdx new file mode 100644 index 0000000000..df34c7df08 --- /dev/null +++ b/api-management/traffic-transformation/request-method.mdx @@ -0,0 +1,243 @@ +--- +title: "Request Method" +description: "Learn how to modify the API request method" +keywords: "Traffic Transformation, Request Method" +sidebarTitle: "Request Method" +--- + +## Overview + +Tyk's Request Method Transform middleware allows you to modify the HTTP method of incoming requests to an API endpoint prior to the request being proxied to the upstream service. You might use this to map `POST` requests from clients to upstream services that support only `PUT` and `DELETE` operations, providing a modern interface to your users. It is a simple middleware that changes only the method and not the payload or headers. You can, however, combine this with the [Request Header Transform](/api-management/traffic-transformation/request-headers) and [Request Body Tranform](/api-management/traffic-transformation/request-body) to apply more complex transformation to requests. + +### Use Cases + +#### Simplifying API consumption + +In cases where an upstream API requires different methods (e.g. `PUT` or `DELETE`) for different functionality but you want to wrap this in a single client-facing API, you can provide a simple interface offering a single method (e.g. `POST`) and then use the method transform middleware to map requests to correct upstream method. + +#### Enforcing API governance and standardization + +You can use the transform middleware to ensure that all requests to a service are made using the same HTTP method, regardless of the original method used by the client. This can help maintain consistency across different client applications accessing the same upstream API. + +#### Error Handling and Redirection + +You can use the method transformation middleware to handle errors and redirect requests to different endpoints, such as changing a DELETE request to a GET request when a specific resource is no longer available, allowing for graceful error handling and redirection. + +#### Testing and debugging + +Request method transformation can be useful when testing or debugging API endpoints; temporarily changing the request method can help to identify issues or test specific functionalities. + +### Working + +This is a very simple middleware that is assigned to an endpoint and configured with the HTTP method to which the request should be modified. The Request Method Transform middleware modifies the request method for the entire request flow, not just for the specific upstream request, so all subsequent middleware in the processing chain will use the new (transformed) method. + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Request Method Transform middleware summary + - The Request Method Transform is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Request Method Transform is configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + +## Using Tyk OAS + + +Tyk's [request method transform](/api-management/traffic-transformation/request-method) middleware is configured at the endpoint level, where it modifies the HTTP method used in the request to a configured value. + +When working with Tyk OAS APIs the transformation is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#request-method-using-classic) page. + +### API Definition + +The request method transform middleware (`transformRequestMethod`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +You only need to enable the middleware (set `enabled:true`) and then configure `toMethod` as the new HTTP method to which the request should be transformed. The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the method should be transformed. + +All standard HTTP methods are supported: `GET`, `PUT`, `POST`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`. + +For example: +```json {hl_lines=["39-41"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-request-method", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/status/200": { + "get": { + "operationId": "status/200get", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-request-method", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-request-method/", + "strip": true + } + }, + "middleware": { + "operations": { + "status/200get": { + "transformRequestMethod": { + "enabled": true, + "toMethod": "POST" + } + } + } + } + } +} +``` + +In this example the Request Method Transform middleware has been configured for requests to the `GET /status/200` endpoint. Any request received to that endpoint will be modified to `POST /status/200`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the request method transform. + +### API Designer + +Adding the transform to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Method Transform middleware** + + Select **ADD MIDDLEWARE** and choose the **Method Transform** middleware from the *Add Middleware* screen. + + Adding the Request Method Transform middleware + +3. **Configure the middleware** + + Select the new HTTP method to which requests to this endpoint should be transformed + + Selecting the new HTTP method for requests to the endpoint + + Select **ADD MIDDLEWARE** to apply the change to the middleware configuration. + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +Tyk's [request method transform](/api-management/traffic-transformation/request-method) middleware is configured at the endpoint level, where it modifies the HTTP method used in the request to a configured value. + +When working with Tyk Classic APIs the transformation is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#request-method-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring a Request Method Transform in Tyk Operator](#tyk-operator) section below. + +### API Definition + +To configure a transformation of the request method you must add a new `method_transforms` object to the `extended_paths` section of your API definition. + +It has the following configuration: +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `to_method`: The new HTTP method to which the request should be transformed + +All standard HTTP methods are supported: `GET`, `PUT`, `POST`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`. + +For example: +```json +{ + "method_transforms": [ + { + "path": "/status/200", + "method": "GET", + "to_method": "POST" + } + ] +} +``` + +In this example the Request Method Transform middleware has been configured for HTTP `GET` requests to the `/status/200` endpoint. Any request received to that endpoint will be modified to `POST /status/200`. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the request method transform middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the Method Transform plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to perform the transformation. Select the **Method Transform** plugin. + + Method Transform + +2. **Configure the transform** + + Then select the HTTP method to which you wish to transform the request. + + Method Path + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +The process for configuring a request method transform for an endpoint in Tyk Operator is similar to that defined in section configuring a Request Method Transform in the Tyk Classic API Definition. + +To configure a transformation of the request method you must add a new `method_transforms` object to the `extended_paths` section of your API definition: + +```yaml {linenos=true, linenostart=1, hl_lines=["26-29"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.default.svc:8000 + listen_path: /transform + strip_listen_path: true + version_data: + default_version: v1 + not_versioned: true + versions: + v1: + name: v1 + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + method_transforms: + - path: /anything + method: GET + to_method: POST +``` + +The example API Definition above configures an API to listen on path `/transform` and forwards requests upstream to http://httpbin.org. + +In this example the Request Method Transform middleware has been configured for `HTTP GET` requests to the `/anything` endpoint. Any request received to that endpoint will be modified to `POST /anything`. + diff --git a/api-management/traffic-transformation/request-size-limits.mdx b/api-management/traffic-transformation/request-size-limits.mdx new file mode 100644 index 0000000000..03188c3e21 --- /dev/null +++ b/api-management/traffic-transformation/request-size-limits.mdx @@ -0,0 +1,368 @@ +--- +title: "Request Size Limits" +description: "Learn how to set request size limits" +keywords: "Traffic Transformation, Request Size Limits" +sidebarTitle: "Request Size Limits" +--- + +## Overview + +With Tyk, you can apply limits to the size of requests made to your HTTP APIs. You might use this feature to protect your Tyk Gateway or upstream services from excessive memory usage or brute force attacks. + +Tyk Gateway offers a flexible tiered system of limiting request sizes ranging from globally applied limits across all APIs deployed on the gateway down to specific size limits for individual API endpoints. + +### Use Case + +#### Protecting the entire Tyk Gateway from DDoS attacks +You can configure a system-wide request size limit that protects all APIs managed by the Tyk Gateway from being overwhelmed by excessively large requests, which could be part of a DDoS attack, ensuring the stability and availability of the gateway. + +#### Limiting request sizes for a lightweight microservice +You might expose an API for a microservice that is designed to handle lightweight, fast transactions and is not equipped to process large payloads. You can set an API-level size limit that ensures the microservice behind this API is not forced to handle requests larger than it is designed for, maintaining its performance and efficiency. + +#### Controlling the size of GraphQL queries +A GraphQL API endpoint might be susceptible to complex queries that can lead to performance issues. By setting a request size limit for the GraphQL endpoint, you ensure that overly complex queries are blocked, protecting the backend services from potential abuse and ensuring a smooth operation. + +#### Restricting upload size on a file upload endpoint +An API endpoint is designed to accept file uploads, but to prevent abuse, you want to limit the size of uploads to 1MB. To enforce this, you can enable the Request Size Limit middleware for this endpoint, configuring a size limit of 1MB. This prevents users from uploading excessively large files, protecting your storage and bandwidth resources. + +### Working + +Tyk compares each incoming API request with the configured maximum size for each level of granularity in order of precedence and will reject any request that exceeds the size you have set at any level of granularity, returning an HTTP 4xx error as detailed below. + +All size limits are stated in bytes and are applied only to the request _body_ (or payload), excluding the headers. + +| Precedence | Granularity | Error returned on failure | +| :------------ | :------------------ | :-------------------------------- | +| 1st | System (gateway) | `413 Request Entity Too Large` | +| 2nd | API | `400 Request is too large` | +| 3rd | Endpoint | `400 Request is too large` | + + + +The system level request size limit is the only size limit applied to [TCP](/key-concepts/tcp-proxy) and [Websocket](/advanced-configuration/websockets) connections. + + + +
+ +#### Applying a system level size limit +You can configure a request size limit (in bytes) that will be applied to all APIs on your Tyk Gateway by adding `max_request_body_size` to the `http_server_options` [element](/tyk-oss-gateway/configuration#http_server_options-max_request_body_size) of your `tyk.conf` Gateway configuration. For example: +```yaml +"max_request_body_size": 5000 +``` +A value of zero (default) means that no maximum is set and the system-wide size limit check will not be performed. + +This limit will be evaluated before API-level or endpoint-level configurations. If this test fails, the Tyk Gateway will return an error `HTTP 413 Request Entity Too Large`. + + + + +
+ +If you're using Tyk OAS APIs, then you can find details and examples of how to configure an API or endpoint-level request size limit [here](#request-size-limits-using-tyk-oas). + +If you're using Tyk Classic APIs, then you can find details and examples of how to configure an API or endpoint-level request size limit [here](#request-size-limits-using-classic). + +{/* proposed "summary box" to be shown graphically on each middleware page + # Request Size Limit middleware summary + - The Request Size Limit middleware is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Request Size Limit middleware can be configured at the system level within the Gateway config, or per-API or per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +## Using Tyk OAS + + +The [request size limit](/api-management/traffic-transformation/request-size-limits) middleware enables you to apply limits to the size of requests made to your HTTP APIs. You might use this feature to protect your Tyk Gateway or upstream services from excessive memory usage or brute force attacks. + +The middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#request-size-limits-using-classic) page. + +### API Definition + +There are three different levels of granularity that can be used when configuring a request size limit. +- [system-wide](#applying-a-system-level-size-limit): affecting all APIs deployed on the gateway +- [API-level](#applying-a-size-limit-for-a-specific-api): affecting all endpoints for an API +- [endpoint-level](#applying-a-size-limit-for-a-specific-endpoint): affecting a single API endpoint + +#### Applying a size limit for a specific API + +You can configure a request size limit (in bytes) to an API by configuring the `requestSizeLimit` within the `middleware.global` element of the Tyk OAS Extension (`x-tyk-api-gateway`), for example: + +```json +"x-tyk-api-gateway": { + "middleware": { + "global": { + "requestSizeLimit": { + "enabled": true, + "value": 2500 + } + } + } +} +``` + +A value of zero (default) means that no maximum is set and the API-level size limit check will not be performed. + +This limit is applied to all endpoints within an API. It is evaluated after the Gateway-wide size limit and before any endpoint-specific size limit. If this test fails, the Tyk Gateway will report `HTTP 400 Request is too large`. + +#### Applying a size limit for a specific endpoint + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The virtual endpoint middleware (`requestSizeLimit`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `requestSizeLimit` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `value`: the maximum size permitted for a request to the endpoint (in bytes) + +For example: +```json {hl_lines=["39-44"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-request-size-limit", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "post": { + "operationId": "anythingpost", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-request-size-limit", + "state": { + "active": true, + "internal": false + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-request-size-limit/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingpost": { + "requestSizeLimit": { + "enabled": true, + "value": 100 + } + } + } + } + } +} +``` + +In this example the endpoint-level Request Size Limit middleware has been configured for HTTP `POST` requests to the `/anything` endpoint. For any call made to this endpoint, Tyk will check the size of the payload (Request body) and, if it is larger than 100 bytes, will reject the request, returning `HTTP 400 Request is too large`. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the virtual endpoint middleware. + +### API Designer + +Adding the Request Size Limit middleware to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint for the path** + + From the **API Designer** add an endpoint that matches the path for you want to limit the size of requests. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Request Size Limit middleware** + + Select **ADD MIDDLEWARE** and choose the **Request Size Limit** middleware from the *Add Middleware* screen. + + Adding the Request Size Limit middleware + +3. **Configure the middleware** + + Now you can set the **size limit** that the middleware should enforce - remember that this is given in bytes. + + Setting the size limit that should be enforced + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [request size limit](/api-management/traffic-transformation/request-size-limits) middleware enables you to apply limits to the size of requests made to your HTTP APIs. You might use this feature to protect your Tyk Gateway or upstream services from excessive memory usage or brute force attacks. + +This middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#request-size-limits-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +There are three different levels of granularity that can be used when configuring a request size limit. +- [system-wide](#applying-a-system-level-size-limit): affecting all APIs deployed on the gateway +- [API-level](/api-management/traffic-transformation/request-headers#tyk-classic-api): affecting all endpoints for an API +- [endpoint-level](#tyk-classic-endpoint): affecting a single API endpoint + +#### Applying a size limit for a specific API + + +You can configure a request size limit (in bytes) to an API by configuring the `global_size_limit` within the `version` element of the API Definition, for example: +``` +"global_size_limit": 2500 +``` + +A value of zero (default) means that no maximum is set and the API-level size limit check will not be performed. + +This limit is applied for all endpoints within an API. It is evaluated after the Gateway-wide size limit and before any endpoint-specific size limit. If this test fails, the Tyk Gateway will report `HTTP 400 Request is too large`. + +#### Applying a size limit for a specific endpoint + + +The most granular control over request sizes is provided by the endpoint-level configuration. This limit will be applied after any Gateway-level or API-level size limits and is given in bytes. If this test fails, the Tyk Gateway will report `HTTP 400 Request is too large`. + +To enable the middleware you must add a new `size_limits` object to the `extended_paths` section of your API definition. + +The `size_limits` object has the following configuration: +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `size_limit`: the maximum size permitted for a request to the endpoint (in bytes) + +For example: +```.json {linenos=true, linenostart=1} +{ + "extended_paths": { + "size_limits": [ + { + "disabled": false, + "path": "/anything", + "method": "POST", + "size_limit": 100 + } + ] + } +} +``` + +In this example the endpoint-level Request Size Limit middleware has been configured for HTTP `POST` requests to the `/anything` endpoint. For any call made to this endpoint, Tyk will check the size of the payload (Request body) and, if it is larger than 100 bytes, will reject the request, returning `HTTP 400 Request is too large`. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure a request size limit for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to limit the size of requests. Select the **Request size limit** plugin. + + Select middleware + +2. **Configure the middleware** + + Set the request size limit, in bytes. + + Configure limit + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + + + + + The Tyk Classic API Designer does not provide an option to configure `global_size_limit`, but you can do this from the Raw Definition editor. + + + +### Tyk Operator + +The process for configuring a request size limit is similar to that defined in section configuring the middleware in the Tyk Classic API Definition. Tyk Operator allows you to configure a request size limit for [all endpoints of an API](#tyk-operator-api) or for a [specific API endpoint](#tyk-operator-endpoint). + +#### Applying a size limit for a specific API + + +{/* Need an example */} +The process for configuring the request size_limits middleware for a specific API is similar to that explained in [applying a size limit for a specific API](#tyk-classic-api). + +You can configure a request size limit (in bytes) for all endpoints within an API by configuring the `global_size_limit` within the `version` element of the API Definition, for example: + +```yaml {linenos=true, linenostart=1, hl_lines=["19"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-global-limit +spec: + name: httpbin-global-limit + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-global-limit + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + global_size_limit: 5 + name: Default +``` + +The example API Definition above configures an API to listen on path `/httpbin-global-limit` and forwards requests upstream to http://httpbin.org. + +In this example the request size limit is set to 5 bytes. If the limit is exceeded then the Tyk Gateway will report `HTTP 400 Request is too large`. + +#### Applying a size limit for a specific endpoint + + +The process for configuring the request size_limits middleware for a specific endpoint is similar to that explained in [applying a size limit for a specific endpoint](#tyk-classic-endpoint). + +To configure the request size_limits middleware you must add a new `size_limits` object to the `extended_paths` section of your API definition, for example: + +```yaml {linenos=true, linenostart=1, hl_lines=["22-25"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-limit +spec: + name: httpbin-limit + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-limit + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + extended_paths: + size_limits: + - method: POST + path: /post + size_limit: 5 +``` + +The example API Definition above configures an API to listen on path `/httpbin-limit` and forwards requests upstream to http://httpbin.org. + +In this example the endpoint-level Request Size Limit middleware has been configured for `HTTP POST` requests to the `/post` endpoint. For any call made to this endpoint, Tyk will check the size of the payload (Request body) and, if it is larger than 5 bytes, will reject the request, returning `HTTP 400 Request is too large`. \ No newline at end of file diff --git a/api-management/traffic-transformation/request-validation.mdx b/api-management/traffic-transformation/request-validation.mdx new file mode 100644 index 0000000000..2d2e1df732 --- /dev/null +++ b/api-management/traffic-transformation/request-validation.mdx @@ -0,0 +1,399 @@ +--- +title: "Request Validation" +description: "Learn how to validate that requests meet the expected contract" +keywords: "Traffic Transformation, Request Validation" +sidebarTitle: "Request Validation" +--- + +## Overview + + +Requests to your upstream services should meet the contract that you have defined for those APIs. Checking the content and format of incoming requests before they are passed to the upstream APIs can avoid unexpected errors and provide additional security to those services. Tyk's request validation middleware provides a way to validate the presence, correctness and conformity of HTTP requests to make sure they meet the expected format required by the upstream API endpoints. + +Request validation enables cleaner backend APIs, better standardization across consumers, easier API evolution and reduced failure risk leading to higher end-to-end reliability. + +### Use Cases + +#### Improving security of upstream services + +Validating incoming requests against a defined schema protects services from unintended consequences arising from bad input, such as SQL injection or buffer overflow errors, or other unintended failures caused by missing parameters or invalid data types. Offloading this security check to the API Gateway provides an early line of defense as potentially bad requests are not proxied to your upstream services. + +#### Offloading contract enforcement + +You can ensure that client requests adhere to a defined contract specifying mandatory headers or parameters before sending requests upstream. Performing these validation checks in the API Gateway allows API developers to focus on core domain logic. + +#### Supporting data transformation + +Validation goes hand-in-hand with request [header](/api-management/traffic-transformation/request-headers) and [body](/api-management/traffic-transformation/request-body) transformation by ensuring that a request complies with the expected schema prior to transformation. For example, you could validate that a date parameter is present, then transform it into a different date format as required by your upstream API dynamically on each request. + +### Working + +The incoming request is compared with a defined schema, which is a structured description of the expected format for requests to the endpoint. This request schema defines the required and optional elements such as headers, path/query parameters, payloads and their data types. It acts as a contract for clients. + +If the incoming request does not match the schema, it will be rejected with an `HTTP 422 Unprocessable Entity` error. This error code can be customized if required. + +When using [Tyk OAS APIs](/api-management/traffic-transformation/request-validation#request-validation-using-tyk-oas), request validation is performed by the `Validate Request` middleware which can be enabled per-endpoint. The schema against which requests are compared is defined in the OpenAPI description of the endpoint. All elements of the request can have a `schema` defined in the OpenAPI description so requests to Tyk OAS APIs can be validated for headers, path/query parameters and body (payload). + +When using the legacy [Tyk Classic APIs](/api-management/traffic-transformation/request-validation#request-validation-using-classic), request validation is performed by the `Validate JSON` middleware which can be enabled per-endpoint. The schema against which requests are compared is defined in the middleware configuration and is limited to the request body (payload). Request headers and path/query parameters cannot be validated when using Tyk Classic APIs. + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Validate Request middleware summary + - The Validate Request middleware is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Validate Request middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +## Using Tyk OAS + + +The [request validation](#request-validation-overview) middleware provides a way to validate the presence, correctness and conformity of HTTP requests to make sure they meet the expected format required by the upstream API endpoints. If the incoming request fails validation, the Tyk Gateway will reject the request with an `HTTP 422 Unprocessable Entity` response. Tyk can be [configured](#configuring-the-request-validation-middleware) to return a different HTTP status code if required. + +The middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](/api-management/traffic-transformation/request-validation#request-validation-using-classic) page. + +### Request schema in OpenAPI Specification + +The OpenAPI Specification supports the definition of a [schema](https://learn.openapis.org/specification/content.html#the-schema-object) to describe and limit the content of any field in an API request or response. + +Tyk's request validation middleware automatically parses the schema for the request in the OpenAPI description part of the Tyk OAS API Definition and uses this to compare against the incoming request. + +An OpenAPI schema can reference other schemas defined elsewhere, letting you write complex validations very efficiently since you don’t need to re-define the validation for a particular object every time you wish to refer to it. Tyk only supports local references to schemas (within the same OpenAPI document). + +As explained in the OpenAPI [documentation](https://learn.openapis.org/specification/parameters.html), the structure of an API request is described by two components: +- parameters (headers, query parameters, path parameters) +- request body (payload) + +#### Request parameters + +The `parameters` field in the OpenAPI description is an array of [parameter objects](https://swagger.io/docs/specification/describing-parameters/) that each describe one variable element in the request. Each `parameter` has two mandatory fields: +- `in`: the location of the parameter (`path`, `query`, `header`) +- `name`: a unique identifier within that location (i.e. no duplicate header names for a given operation/endpoint) + +There are also optional `description` and `required` fields. + +For each parameter, a schema can be declared that defines the `type` of data that can be stored (e.g. `boolean`, `string`) and any `example` or `default` values. + +##### Operation (endpoint-level) parameters + +An operation is a combination of HTTP method and path or, as Tyk calls it, an endpoint - for example `GET /users`. Operation, or endpoint-level parameters can be defined in the OpenAPI description and will apply only to that operation within the API. These can be added or modified within Tyk Dashboard's [API designer](#api-designer). + +##### Common (path-level) parameters + +[Common parameters](https://swagger.io/docs/specification/v3_0/describing-parameters/#common-parameters), that apply to all operations within a path, can be defined at the path level within the OpenAPI description. Tyk refers to these as path-level parameters and displays them as read-only fields in the Dashboard's API designer. If you need to add or modify common parameters you must use the *Raw Definition* editor, or edit your OpenAPI document outside Tyk and [update](/api-management/gateway-config-managing-oas#updating-an-api) the API. + +#### Request body + +The `requestBody` field in the OpenAPI description is a [Request Body Object](https://swagger.io/docs/specification/describing-request-body/). This has two optional fields (`description` and `required`) plus the `content` section which allows you to define a schema for the expected payload. Different schemas can be declared for different media types that are identified by content-type (e.g. `application/json`, `application/xml` and `text/plain`). + +### Configuring the request validation middleware + +When working with Tyk OAS APIs, the request validation middleware automatically determines the validation rules based on the API schema. The only configurable option for the middleware is to set the desired HTTP status code that will be returned if a request fails validation. The default response will be `HTTP 422 Unprocessable Entity` unless otherwise configured. + +### Enabling the request validation middleware + +If the middleware is enabled for an endpoint, then Tyk will automatically validate requests made to that endpoint against the schema defined in the API definition. + +When you create a Tyk OAS API by importing your OpenAPI description, you can instruct Tyk to enable request validation [automatically](#automatically-enabling-the-request-validation-middleware) for all endpoints with defined schemas. + +If you are creating your API without import, or if you only want to enable request validation for some endpoints, you can [manually enable](#manually-enabling-the-request-validation-middleware) the middleware in the Tyk OAS API definition. + +#### Automatically enabling the request validation middleware + +The request validation middleware can be enabled for all endpoints that have defined schemas when [importing](/api-management/gateway-config-managing-oas#importing-an-openapi-description-to-create-an-api) an OpenAPI Document to create a Tyk OAS API. +- if you are using the `POST /apis/oas/import` endpoint in the [Tyk Dashboard API](/tyk-dashboard-api) or [Tyk Gateway API](/tyk-gateway-api) then you can do this by setting the `validateRequest=true` query parameter +- if you are using the API Designer, select the **Auto-generate middleware to validate requests** option on the **Import API** screen + +Select the option during OpenAPI import to validate requests + +As noted, the automatic application of request validation during import will apply the middleware to all endpoints declared in your OpenAPI description. If you want to adjust this configuration, for example to remove validation from specific endpoints or to change the HTTP status code returned on error, you can update the Tyk OAS API definition as described [here](#manually-enabling-the-request-validation-middleware). + +#### Manually enabling the request validation middleware + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The request validation middleware (`validateRequest`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId`. The `operationId` for an endpoint can be found within the `paths` section of your [OpenAPI specification](https://swagger.io/docs/specification/paths-and-operations/?sbsearch=operationIds). + +The `validateRequest` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `errorResponseCode`: [optional] the HTTP status code to be returned if validation fails (this defaults to `HTTP 422 Unprocessable Entity` if not set) + +For example: +```json {hl_lines=["69-72"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-validate-request", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "parameters": [ + { + "in": "header", + "name": "X-Security", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "firstname": { + "description": "The person's first name", + "type": "string" + }, + "lastname": { + "description": "The person's last name", + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-validate-request", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-validate-request/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingget": { + "validateRequest": { + "enabled": true, + "errorResponseCode": 400 + } + } + } + } + } +} +``` + +In this example the request validation middleware has been configured for requests to the `GET /anything` endpoint. The middleware will check for the existence of a header named `X-Security` and the request body will be validated against the declared schema. If there is no match, the request will be rejected and Tyk will return `HTTP 400` (as configured in `errorResponseCode`). + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the request validation middleware. + +### API Designer + +Adding and configuring Request Validation for your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Validate Request middleware** + + Select **ADD MIDDLEWARE** and choose **Validate Request** from the *Add Middleware* screen. + + Adding the Validate Request middleware + + The API Designer will show you the request body and request parameters schema detected in the OpenAPI description of the endpoint. + + Validate Request middleware schema is automatically populated + +3. **Configure the middleware** + + If required, you can select an alternative HTTP status code that will be returned if request validation fails. + + Configuring the Request Validation error response + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes. + + +## Using Classic + + +The [request validation](#request-validation-overview) middleware provides a way to validate the presence, correctness and conformity of HTTP requests to make sure they meet the expected format required by the upstream API endpoints. + +When working with legacy Tyk Classic APIs, request validation is performed by the `Validate JSON` middleware which can be enabled per-endpoint. The schema against which requests are compared is defined in the middleware configuration and is limited to the request body (payload). Request headers and path/query parameters cannot be validated when using Tyk Classic APIs. + +This middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](/api-management/traffic-transformation/request-validation#request-validation-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +To enable the middleware you must add a new `validate_json` object to the `extended_paths` section of your API definition. + +The `validate_json` object has the following configuration: + +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `schema`: the [JSON schema](https://json-schema.org/understanding-json-schema/basics) against which the request body will be compared +- `error_response_code`: the HTTP status code that will be returned if validation fails (defaults to `422 Unprocessable Entity`) + +For example: + +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "validate_json": [ + { + "disabled": false, + "path": "/register", + "method": "POST", + "schema": { + "type": "object", + "properties": { + "firstname": { + "type": "string", + "description": "The person's first name" + }, + "lastname": { + "type": "string", + "description": "The person's last name" + } + } + }, + "error_response_code": 422 + } + ] + } +} +``` + +In this example the Validate JSON middleware has been configured for requests to the `POST /register` endpoint. For any call made to this endpoint, Tyk will compare the request body with the schema and, if it does not match, the request will be rejected with the error code `HTTP 422 Unprocessable Entity`. + +#### Understanding JSON Schema Version Handling + +The Gateway automatically detects the version of the JSON schema from the `$schema` field in your schema definition. This field specifies the version of the [JSON schema standard](https://json-schema.org/specification-links) to be followed. + +From Tyk 5.8 onwards, supported versions are [draft-04](https://json-schema.org/draft-04/schema), [draft-06](https://json-schema.org/draft-06/schema) and [draft-07](https://json-schema.org/draft-07/schema). + +In previous versions of Tyk, only [draft-04](https://json-schema.org/draft-04/schema) is supported. Please be careful if downgrading from Tyk 5.8 to an earlier version that your JSON is valid as you might experience unexpected behaviour if using features from newer drafts of the JSON schema. + +- If the `$schema` field is present, the Gateway strictly follows the rules of the specified version. +- If the `$schema` field is missing or the version is not specified, the Gateway uses a hybrid mode that combines features from multiple schema versions. This mode ensures that the validation will still work, but may not enforce the exact rules of a specific version. + +To ensure consistent and predictable validation, it is recommended to always include the `$schema` field in your schema definition. For example: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "firstname": { + "type": "string" + }, + "lastname": { + "type": "string" + } + } +} +``` + +By including `$schema`, the validator can operate in strict mode, ensuring that the rules for your chosen schema version are followed exactly. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the request validation middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to validate the request payload. Select the **Validate JSON** plugin. + + validate json plugin + +2. **Configure the middleware** + + Once you have selected the request validation middleware for the endpoint, you can select an error code from the drop-down list (if you don't want to use the default `422 Unprocessable Entity`) and enter your JSON schema in the editor. + + Adding schema to the Validate JSON middleware + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +The process for configuring the middleware in Tyk Operator is similar to that explained in configuring the middleware in the Tyk Classic API Definition. To configure the request validation middleware you must add a new `validate_json` object to the `extended_paths` section of your API definition, for example: + +The example API Definition below configures an API to listen on path `/httpbin` and forwards requests upstream to http://httpbin.org. + +In this example, the Validate JSON middleware has been configured for requests to the `GET /get` endpoint. For any call made to this endpoint, Tyk will compare the request body with the schema and, if it does not match, the request will be rejected with the error code `HTTP 422 Unprocessable Entity`. + +```yaml {linenos=true, linenostart=1, hl_lines=["26-41"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-json-schema-validation +spec: + name: httpbin-json-schema-validation + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + validate_json: + - error_response_code: 422 + disabled: false + path: /get + method: GET + schema: + properties: + userName: + type: string + minLength: 2 + age: + type: integer + minimum: 1 + required: + - userName + type: object +``` + diff --git a/api-management/traffic-transformation/response-body.mdx b/api-management/traffic-transformation/response-body.mdx new file mode 100644 index 0000000000..363a80ad83 --- /dev/null +++ b/api-management/traffic-transformation/response-body.mdx @@ -0,0 +1,482 @@ +--- +title: "Response Body" +description: "Learn how to transform the body of API responses" +keywords: "Traffic Transformation, Response Body" +sidebarTitle: "Response Body" +--- + +## Overview + +Tyk enables you to modify the payload of API responses received from your upstream services before they are passed on to the client that originated the request. This makes it easy to transform between payload data formats or to expose legacy APIs using newer schema models without having to change any client implementations. This middleware is only applicable to endpoints that return a body with the response. + +With the body transform middleware you can modify XML or JSON formatted payloads to ensure that the response contains the information required by your upstream service. You can enrich the response by adding contextual data that is held by Tyk but not included in the original response from the upstream. + +This middleware changes only the payload and not the headers. You can, however, combine this with the [Response Header Transform](/api-management/traffic-transformation/response-headers) to apply more complex transformation to responses. + +There is a closely related [Request Body Transform](/api-management/traffic-transformation/request-body) middleware that provides the same functionality on the request sent by the client prior to it being proxied to the upstream. + +### Use Cases + +#### Maintaining compatibility with legacy clients + +Sometimes you might have a legacy API and need to migrate the transactions to a new upstream service but do not want to upgrade all the existing clients to the newer upstream API. Using response body transformation, you can convert the new format that your upstream services provide into legacy XML or JSON expected by the clients. + +#### Shaping responses for different devices + +You can detect the client device types via headers or context variables and transform the response payload to optimize it for that particular device. For example, you might optimize the response content for mobile apps. + +#### SOAP to REST translation + +A common use of the response body transform middleware is when surfacing a legacy SOAP service with a REST API. Full details of how to perform this conversion using Tyk are provided [here](/advanced-configuration/transform-traffic/soap-rest). + +### Working + +Tyk's body transform middleware uses the [Go template language](https://golang.org/pkg/text/template/) to parse and modify the provided input. We have bundled the [Sprig Library (v3)](http://masterminds.github.io/sprig/) which provides over 70 pre-written functions for transformations to assist the creation of powerful Go templates to transform your API responses. + +The Go template can be defined within the API Definition or can be read from a file that is accessible to Tyk. + +We have provided more detail, links to reference material and some examples of the use of Go templating [here](/api-management/traffic-transformation/go-templates). + + + +Tyk evaluates templates stored in files on startup, so if you make changes to a template you must remember to restart the gateway. + + + +#### Supported response body formats + +The body transformation middleware can modify response payloads in the following formats: +- JSON +- XML + +When working with JSON format data, the middleware will unmarshal the data into a data structure, and then make that data available to the template in dot-notation. + +#### Data accessible to the middleware + +The middleware has direct access to the response body and also to dynamic data as follows: +- [Context variables](/api-management/traffic-transformation/request-context-variables), extracted from the request at the start of the middleware chain, can be injected into the template using the `._tyk_context.KEYNAME` namespace +- [Session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context), from the Tyk Session Object linked to the request, can be injected into the template using the `._tyk_meta.KEYNAME` namespace +- Inbound form or query data can be accessed through the `._tyk_context.request_data` namespace where it will be available in as a `key:[]value` map +- values from [key-value (KV) storage](/tyk-configuration-reference/kv-store#transformation-middleware) can be injected into the template using the notation appropriate to the location of the KV store + +The response body transform middleware can iterate through list indices in dynamic data so, for example, calling `{{ index ._tyk_context.request_data.variablename 0 }}` in a template will expose the first entry in the `request_data.variablename` key/value array. + + + +As explained in the [documentation](https://pkg.go.dev/text/template), templates are executed by applying them to a data structure. The template receives the decoded JSON or XML of the response body. If session variables or meta data are enabled, additional fields will be provided: `_tyk_context` and `_tyk_meta` respectively. + + + +#### Automatic XML <-> JSON Transformation + +A very common transformation that is applied in the API Gateway is to convert between XML and JSON formatted body content. + +The Response Body Transform supports two helper functions that you can use in your Go templates to facilitate this: +- `jsonMarshal` performs JSON style character escaping on an XML field and, for complex objects, serialises them to a JSON string ([example](/api-management/traffic-transformation/go-templates#xml-to-json-conversion-using-jsonmarshal)) +- `xmlMarshal` performs the equivalent conversion from JSON to XML ([example](/api-management/traffic-transformation/go-templates#json-to-xml-conversion-using-xmlmarshal)) + +
+ + +{/* proposed "summary box" to be shown graphically on each middleware page + # Response Body Transform middleware summary + - The Response Body Transform middleware is an optional stage in Tyk's API Response processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Response Body Transform middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. + - Response Body Transform can access both [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) and [request context variables](/api-management/traffic-transformation/request-context-variables). */} + + +## Using Tyk OAS + + +The [response body transform](/api-management/traffic-transformation/response-body) middleware provides a way to modify the payload of API responses before they are returned to the client. + +The middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#response-body-using-classic) page. + +### API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The response body transformation middleware (`transformResponseBody`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `transformResponseBody` object has the following configuration: +- `enabled`: enable the middleware for the endpoint +- `format`: the format of input data the parser should expect (either `xml` or `json`) +- `body`: [see note] this is a `base64` encoded representation of your template +- `path`: [see note] this is the path to the text file containing the template + + + + + You should configure only one of `body` or `path` to indicate whether you are embedding the template within the middleware or storing it in a text file. The middleware will automatically select the correct source based on which of these fields you complete. If both are provided, then `body` will take precedence and `path` will be ignored. + + + +For example: +```json {hl_lines=["39-43"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-response-body-transform", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "put": { + "operationId": "anythingput", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-response-body-transform", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-response-body-transform/", + "strip": true + } + }, + "middleware": { + "operations": { + "anythingput": { + "transformResponseBody": { + "enabled": true, + "format": "json", + "body": "ewogICJ2YWx1ZTEiOiAie3sudmFsdWUyfX0iLAogICJ2YWx1ZTIiOiAie3sudmFsdWUxfX0iLAogICJyZXEtaGVhZGVyIjogInt7Ll90eWtfY29udGV4dC5oZWFkZXJzX1hfSGVhZGVyfX0iLAogICJyZXEtcGFyYW0iOiAie3suX3R5a19jb250ZXh0LnJlcXVlc3RfZGF0YS5wYXJhbX19Igp9" + } + } + } + } + } +} +``` + +In this example the response body transform middleware has been configured for requests to the `PUT /anything` endpoint. The `body` contains a base64 encoded Go template (which you can check by pasting the value into a service such as [base64decode.org](https://www.base64decode.org)). + +Decoded, this template is: +```go +{ + "value1": "{{.value2}}", + "value2": "{{.value1}}", + "req-header": "{{._tyk_context.headers_X_Header}}", + "req-param": "{{._tyk_context.request_data.param}}" +} +``` + +So if you make a request to `PUT /anything?param=foo`, configuring a header `X-Header`:`bar` and providing this payload: +```json +{ + "value1": "world", + "value2": "hello" +} +``` + +httpbin.org will respond with the original payload in the response and, if you do not have the response body transform middleware enabled, the response from Tyk will include: +```json +{ + "value1": "world", + "value2": "hello" +} +``` + +If, however, you enable the response body transform middleware, Tyk will modify the response to include this content: +```json +{ + "req-header": "bar", + "req-param": "[foo]", + "value1": "hello", + "value2": "world" +} +``` + +You can see that Tyk has swapped `value1` and `value2` and embedded the `X-Header` header and `param` query values from the request into the body of the response. + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the mock response middleware. + + + +If using a template in a file (i.e. you configure `path` in the `transformResponseBody` object), remember that Tyk will load and evaluate the template when the Gateway starts up. If you modify the template, you will need to restart Tyk in order for the changes to take effect. + + + +### API Designer + +Adding Response Body Transformation to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow the following steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Response Body Transform middleware** + + Select **ADD MIDDLEWARE** and choose the **Response Body Transform** middleware from the *Add Middleware* screen. + + Adding the Response Body Transform middleware + +3. **Configure the middleware** + + Now you can select the response body format (JSON or XML) and add either a path to the file containing the template, or directly enter the transformation template in the text box. + + Configuring the Response Body Transform middleware + + The **Test with data** control will allow you to test your body transformation function by providing an example response body and generating the output from the transform. It is not possible to configure headers, other request parameters, context or session metadata to this template test so if you are using these data sources in your transform it will not provide a complete output, for example: + + Testing the Response Body Transform + +4. **Save the API** + + Select **SAVE API** to apply the changes to your API. + +## Using Classic + + +The [response body transform](/api-management/traffic-transformation/response-body) middleware provides a way to modify the payload of API responses before they are returned to the client. + +This middleware is configured in the Tyk Classic API Definition at the endpoint level. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#response-body-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +To enable the middleware you must add a new `transform_response` object to the `extended_paths` section of your API definition. + +The `transform_response` object has the following configuration: +- `path`: the path to match on +- `method`: this method to match on +- `template_data`: details of the Go template to be applied for the transformation of the response body + +The Go template is described in the `template_data` object by the following fields: +- `input_type`: the format of input data the parser should expect (either `xml` or `json`) +- `enable_session`: set this to `true` to make session metadata available to the transform template +- `template_mode`: instructs the middleware to look for the template either in a `file` or in a base64 encoded `blob`; the actual file location (or base64 encoded template) is provided in `template_source` +- `template_source`: if `template_mode` is set to `file`, this will be the path to the text file containing the template; if `template_mode` is set to `blob`, this will be a `base64` encoded representation of your template + +For example: +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "transform_response": [ + { + "path": "/anything", + "method": "POST", + "template_data": { + "template_mode": "file", + "template_source": "./templates/transform_test.tmpl", + "input_type": "json", + "enable_session": true + } + } + ] + } +} +``` + +In this example, the Response Body Transform middleware is directed to use the template located in the `file` at location `./templates/transform_test.tmpl`. The input (pre-transformation) response payload will be `json` format and session metadata will be available for use in the transformation. + + + +Tyk will load and evaluate the template file when the Gateway starts up. If you modify the template, you will need to restart Tyk in order for the changes to take effect. + + + + + +Prior to Tyk 5.3, there was an additional step to enable response body transformation. You would need to add the following to the Tyk Classic API definition: + +```json +{ + "response_processors":[ + {"name": "response_body_transform"} + ] +} +``` + +If using the Endpoint Designer in the Tyk Dashboard, this would be added automatically. + +We removed the need to configure the `response_processors` element in Tyk 5.3.0. + + + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the response body transform middleware for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to perform the transformation. Select the **Body Transforms** plugin. + + Endpoint designer + +2. **Configure the middleware** + + Ensure that you have selected the `RESPONSE` tab, then select your input type, and then add the template you would like to use to the **Template** input box. + + Setting the body response transform + +3. **Test the Transform** + + If you have sample input data, you can use the Input box to add it, and then test it using the **Test** button. You will see the effect of the template on the sample input in the Output box. + + Testing the body transform function + +4. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the Response Body Transform middleware. + +### Tyk Operator + +The process of configuring a transformation of a response body for a specific endpoint is similar to that defined in section configuring the middleware in the Tyk Classic API Definition for the Tyk Classic API definition. To enable the middleware you must add a new `transform_response` object to the `extended_paths` section of your API definition. + +In the examples below, the Response Body Transform middleware (`transform_response`) is directed to use the template located in the `template_source`, decoding the xml in the base64 encoded string. The input (pre-transformation) response payload will be `xml` format and there is no session metadata provided for use in the transformation. + +#### Example + +```yaml {linenos=true, linenostart=1, hl_lines=["45-53"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-transform +spec: + name: httpbin-transform + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-transform + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + transform: + - method: POST + path: /anything + template_data: + enable_session: false + input_type: json + template_mode: blob + # base64 encoded template + template_source: eyJiYXIiOiAie3suZm9vfX0ifQ== + transform_headers: + - delete_headers: + - "remove_this" + add_headers: + foo: bar + path: /anything + method: POST + transform_response: + - method: GET + path: /xml + template_data: + enable_session: false + input_type: xml + template_mode: blob + # base64 encoded template + template_source: e3sgLiB8IGpzb25NYXJzaGFsIH19 + transform_response_headers: + - method: GET + path: /xml + add_headers: + Content-Type: "application/json" + act_on: false + delete_headers: [] +``` + +#### Tyk Gateway < 5.3.0 Example + +If using Tyk Gateway < v5.3.0 then a `response_processor` object must be added to the API definition containing a `response_body_transform` item, as highlighted below: + +```yaml {linenos=true, linenostart=1, hl_lines=["17-18", "48-56"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-transform +spec: + name: httpbin-transform + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-transform + strip_listen_path: true + response_processors: + - name: response_body_transform + - name: header_injector + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + transform: + - method: POST + path: /anything + template_data: + enable_session: false + input_type: json + template_mode: blob + # base64 encoded template + template_source: eyJiYXIiOiAie3suZm9vfX0ifQ== + transform_headers: + - delete_headers: + - "remove_this" + add_headers: + foo: bar + path: /anything + method: POST + transform_response: + - method: GET + path: /xml + template_data: + enable_session: false + input_type: xml + template_mode: blob + # base64 encoded template + template_source: e3sgLiB8IGpzb25NYXJzaGFsIH19 + transform_response_headers: + - method: GET + path: /xml + add_headers: + Content-Type: "application/json" + act_on: false + delete_headers: [] +``` + diff --git a/api-management/traffic-transformation/response-headers.mdx b/api-management/traffic-transformation/response-headers.mdx new file mode 100644 index 0000000000..3c99502099 --- /dev/null +++ b/api-management/traffic-transformation/response-headers.mdx @@ -0,0 +1,670 @@ +--- +title: "Response Headers" +description: "Learn how to modify API response headers" +keywords: "Traffic Transformation, Response Headers" +sidebarTitle: "Response Headers" +--- + +## Overview + +Tyk enables you to modify header information when a response is proxied back to the client. This can be very useful in cases where you have an upstream API that potentially exposes sensitive headers that you need to remove. + +There are two options for this: +- API-level modification that is applied to responses for all requests to the API +- endpoint-level modification that is applied only to responses for requests to a specific endpoint + +With the header transform middleware you can append or delete any number of headers to ensure that the response contains the information required by your client. You can enrich the response by adding contextual data that is held by Tyk but not included in the original response from the upstream. + +This middleware changes only the headers and not the payload. You can, however, combine this with the [Response Body Transform](/api-management/traffic-transformation/response-body) to apply more complex transformation to responses. + +There are related [Request Header Transform](/api-management/traffic-transformation/request-headers) middleware (at API-level and endpoint-level) that provide the same functionality on the request from a client, prior to it being proxied to the upstream. + +### Use Cases + +#### Customizing responses for specific clients + +A frequent use case for response header transformation is when a client requires specific headers for their application to function correctly. For example, a client may require a specific header to indicate the status of a request or to provide additional information about the response. + +#### Adding security headers + +The response header transform allows you to add security headers to the response to protect against common attacks such as cross-site scripting (XSS) and cross-site request forgery (CSRF). Some security headers may be required for compliance with industry standards and, if not provided by the upstream, can be added by Tyk before forwarding the response to the client. + +#### Adding metadata to response headers + +Adding metadata to response headers can be useful for tracking and analyzing API usage, as well as for providing additional information to clients. For example, you may want to add a header that indicates the version of the API being used or the time taken to process the request. + +#### Modifying response headers for dynamic performance optimization + +You can use response header transformation to dynamically optimize the performance of the API. For example, you may want to indicate to the client the maximum number of requests that they can make in a given time period. By doing so through the response headers, you can perform dynamic optimization of the load on the upstream service without triggering the rate limiter and so avoiding errors being sent to the client. + +### Working + +The response header transform can be applied per-API or per-endpoint; each has a separate entry in the API definition so that you can configure both API-level and endpoint-level transforms for a single API. + +The middleware is configured with a list of headers to delete from the response and a list of headers to add to the response. Each header to be added to the response is configured as a key:value pair. +- the "delete header" functionality is intended to ensure that any header in the delete list is not present once the middleware completes. If a header in the delete list is not present in the upstream response, the middleware will ignore the omission +- the "add header" functionality will capitalize any header name provided. For example, if you configure the middleware to append `x-request-id` it will be added to the response as `X-Request-Id` + +In the response middleware chain, the endpoint-level transform is applied before the API-level transform. Subsequently, if both middleware are enabled, the API-level transform will operate on the headers that have been added by the endpoint-level transform (and will not have access to those that have been deleted by it). + +#### Injecting dynamic data into headers + +You can enrich the response headers by injecting data from context variables or session objects into the headers. +- [context variables](/api-management/traffic-transformation/request-context-variables), extracted from the request at the start of the middleware chain, can be injected into added headers using the `$tyk_context.` namespace +- [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context), from the Tyk Session Object linked to the request, can be injected into added headers using the `$tyk_meta.` namespace +- values from [key-value (KV) storage](/tyk-configuration-reference/kv-store#transformation-middleware) can be injected into added headers using the notation appropriate to the location of the KV store + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Response Header Transform middleware summary + - The Response Header Transform is an optional stage in Tyk's API Response processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Response Header Transform can be configured at the per-endpoint or per-API level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + +## Using Tyk OAS + + +Tyk's [response header transform](/api-management/traffic-transformation/response-headers) middleware enables you to append or delete headers on responses received from the upstream service before sending them to the client. + +There are two options for this: +- API-level modification that is applied to all responses for the API +- endpoint-level modification that is applied only to responses from a specific endpoint + + + + + If both API-level and endpoint-level middleware are configured, the endpoint-level transformation will be applied first. + + + +When working with Tyk OAS APIs the transformation is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#response-headers-using-classic) page. + +### API Definition + +The API-level and endpoint-level response header transforms have a common configuration but are configured in different sections of the API definition. + +#### API-level transform + +To append headers to, or delete headers from, responses from all endpoints defined for your API you must add a new `transformResponseHeaders` object to the `middleware.global` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition. + +You only need to enable the middleware (set `enabled:true`) and then configure the details of headers to `add` and those to `remove`. + +For example: +```json {hl_lines=["38-57"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-response-header", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/status/200": { + "get": { + "operationId": "status/200get", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-response-header", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-response-header/", + "strip": true + } + }, + "middleware": { + "global": { + "transformResponseHeaders": { + "enabled": true, + "remove": [ + "X-Secret" + ], + "add": [ + { + "name": "X-Static", + "value": "foobar" + }, + { + "name": "X-Request-ID", + "value": "$tyk_context.request_id" + }, + { + "name": "X-User-ID", + "value": "$tyk_meta.uid" + } + ] + } + } + } + } +} +``` + +This configuration will add three new headers to each response: +- `X-Static` with the value `foobar` +- `X-Request-ID` with a dynamic value taken from the `request_id` [context variable](/api-management/traffic-transformation/request-context-variables) +- `X-User-ID` with a dynamic value taken from the `uid` field in the [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) + +It will also delete one header (if present) from each response: +- `X-Secret` + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the API-level response header transform. + +#### Endpoint-level transform + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The response header transform middleware (`transformResponseMethod`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +You only need to enable the middleware (set `enabled:true`) and then configure the details of headers to `add` and those to `remove`. + +For example: +```json {hl_lines=["39-50"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-response-method", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/status/200": { + "get": { + "operationId": "status/200get", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-response-method", + "state": { + "active": true + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-response-method/", + "strip": true + } + }, + "middleware": { + "operations": { + "status/200get": { + "transformResponseHeaders": { + "enabled": true, + "remove": [ + "X-Static" + ], + "add": [ + { + "name": "X-Secret", + "value": "the-secret-key-is-secret" + } + ] + } + } + } + } + } +} +``` + +In this example the Response Header Transform middleware has been configured for HTTP `GET` requests to the `/status/200` endpoint. Any response received from the upstream service following a request to that endpoint will have the `X-Static` header removed and the `X-Secret` and `X-New` headers added (with values set to `the-secret-key-is-secret` and `another-header`). + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the endpoint-level response header transform. + +#### Combining API-level and Endpoint-level transforms + +If the example [API-level](#api-level-transform) and [endpoint-level](#endpoint-level-transform) transforms are applied to the same API, then the `X-Secret` header will be added (by the endpoint-level transform first) and then removed (by the API-level transform). Subsequently, the result of the two transforms for a call to `GET /status/200` would be to add four headers: +- `X-Request-ID` +- `X-User-ID` +- `X-Static` +- `X-New` + +### API Designer + +Adding and configuring the transforms to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +#### Adding an API-level transform + +From the **API Designer** on the **Settings** tab, after ensuring that you are in *edit* mode, toggle the switch to **Enable Transform response headers** in the **Middleware** section: +Tyk OAS API Designer showing API-level Response Header Transform + +Then select **NEW HEADER** as appropriate to add or remove a header from API responses. You can add or remove multiple headers by selecting **ADD HEADER** to add another to the list: +Configuring the API-level Response Header Transform in Tyk OAS API Designer + +#### Adding an endpoint level transform + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Response Header Transform middleware** + + Select **ADD MIDDLEWARE** and choose the **Response Header Transform** middleware from the *Add Middleware* screen. + + Adding the URL Rewrite middleware + +3. **Configure header transformation** + + Select **NEW HEADER** to configure a header to be added to or removed from the response, you can add multiple headers to either list by selecting **NEW HEADER** again. + + Configuring the rewrite rules for Advanced Triggers + Configuring the Response Header Transform + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes. + +## Using Classic + + +Tyk's [response header transform](/api-management/traffic-transformation/response-headers) middleware enables you to append or delete headers on responses received from the upstream service before sending them to the client. + +There are two options for this: +- API-level modification that is applied to all responses for the API +- endpoint-level modification that is applied only to responses from a specific endpoint + + + + + If both API-level and endpoint-level middleware are configured, the endpoint-level transformation will be applied first. + + + +When working with Tyk Classic APIs the transformation is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you want to use dynamic data from context variables, you must [enable](/api-management/traffic-transformation/request-context-variables#enabling-context-variables-for-use-with-tyk-classic-apis) context variables for the API to be able to access them from the response header transform middleware. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#response-headers-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the Response Header Transform in Tyk Operator](#tyk-operator) section below. + +### API Definition + +The API-level and endpoint-level response header transforms have a common configuration but are configured in different sections of the API definition. + + +Prior to Tyk 5.3.0, there was an additional step to enable response header transforms (both API-level and endpoint-level). You would need to add the following to the Tyk Classic API definition: + +```json +{ + "response_processors":[ + {"name": "header_injector"} + ] +} +``` + +If using the Endpoint Designer in the Tyk Dashboard, this would be added automatically. + +We removed the need to configure the `response_processors` element in Tyk 5.3.0. + + + +#### API-level transform + + +To **append** headers to all responses from your API (i.e. for all endpoints) you must add a new `global_response_headers` object to the `versions` section of your API definition. This contains a list of key:value pairs, being the names and values of the headers to be added to responses. + +To **delete** headers from all responses from your API (i.e. for all endpoints), you must add a new `global_response_headers_remove` object to the `versions` section of the API definition. This contains a list of the names of existing headers to be removed from responses. + +For example: +```json {linenos=true, linenostart=1} +{ + "version_data": { + "versions": { + "Default": { + "global_response_headers": { + "X-Static": "foobar", + "X-Request-ID":"$tyk_context.request_id", + "X-User-ID": "$tyk_meta.uid" + }, + "global_response_headers_remove": [ + "X-Secret" + ] + } + } + }, +} +``` + +This configuration will add three new headers to each response: +- `X-Static` with the value `foobar` +- `X-Request-ID` with a dynamic value taken from the `request_id` [context variable](/api-management/traffic-transformation/request-context-variables) +- `X-User-ID` with a dynamic value taken from the `uid` field in the [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) + +It will also delete one header (if present) from each response: + - `X-Secret` + +#### Endpoint-level transform + + +To configure response header transformation for a specific endpoint you must add a new `transform_response_headers` object to the `extended_paths` section of your API definition. + +It has the following configuration: +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `delete_headers`: a list of the headers that should be deleted from the response +- `add_headers`: a list of headers, in key:value pairs, that should be added to the response + +For example: +```json {linenos=true, linenostart=1} +{ + "transform_response_headers": [ + { + "path": "status/200", + "method": "GET", + "delete_headers": ["X-Static"], + "add_headers": [ + {"X-Secret": "the-secret-key-is-secret"}, + {"X-New": "another-header"} + ], + } + ] +} +``` + +In this example the Response Header Transform middleware has been configured for HTTP `GET` requests to the `/status/200` endpoint. Any response received from the upstream service following a request to that endpoint will have the `X-Static` header removed and the `X-Secret` and `X-New` headers added (with values set to `the-secret-key-is-secret` and `another-header`). + +#### Combining API-level and Endpoint-level transforms + +If the example [API-level](#api-level-transform) and [endpoint-level](#endpoint-level-transform) transforms are applied to the same API, then the `X-Secret` header will be added (by the endpoint-level transform first) and then removed (by the API-level transform). Subsequently, the result of the two transforms for a call to `GET /status/200` would be to add four headers: +- `X-Request-ID` +- `X-User-ID` +- `X-Static` +- `X-New` + +#### Fixing response headers that leak upstream server data + +A middleware called `header_transform` was added in Tyk 2.1 specfically to allow you to ensure that headers such as `Location` and `Link` reflect the outward facade of your API Gateway and also align with the expected response location to be terminated at the gateway, not the hidden upstream proxy. + +This is configured by adding a new `rev_proxy_header_cleanup` object to the `response_processors` section of your API definition. + +It has the following configuration: +- `headers`: a list of headers in the response that should be modified +- `target_host`: the value to which the listed headers should be updated + +For example: +```json +{ + "response_processors": [ + { + "name": "header_transform", + "options": { + "rev_proxy_header_cleanup": { + "headers": ["Link", "Location"], + "target_host": "http://TykHost:TykPort" + } + } + } + ] +} +``` + +In this example, the `Link` and `Location` headers will be modified from the server-generated response, with the protocol, domain and port of the value set in `target_host`. + +This feature is rarely used and has not been implemented in the Tyk Dashboard UI, nor in the [Tyk OAS API](#response-headers-using-tyk-oas). + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure the response header transform middleware for your Tyk Classic API by following these steps. + +#### API-level transform + +Configuring the API-level response header transform middleware is very simple when using the Tyk Dashboard. + +In the Endpoint Designer you should select the **Global Version Settings** and ensure that you have selected the **Response Headers** tab: + +Configuring the API-level response header transform + +Note that you must click **ADD** to add a header to the list (for appending or deletion). + +#### Endpoint-level transform + +1. **Add an endpoint for the path and select the Header Transform plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to perform the transformation. Select the **Modify Headers** plugin. + + Adding the Modify Headers plugin to an endpoint + +2. **Select the "Response" tab** + + This ensures that the transform will be applied to responses prior to them being sent to the client. + + Selecting the response header transform + +3. **Declare the headers to be modified** + + Select the headers to delete and insert using the provided fields. You need to click **ADD** to ensure they are added to the list. + + Configuring the response header transform + +4. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the middleware. + +### Tyk Operator + +The process for configuring a response header transform in Tyk Operator is similar to that defined in section configuring the Response Header Transform in the Tyk Classic API Definition. Tyk Operator allows you to configure a response header transformation for [all endpoints of an API](#tyk-operator-endpoint) or for a [specific API endpoint](#tyk-operator-api). + +#### API-level transform + + +The process of configuring transformation of response headers for a specific API in Tyk Operator is similar to that defined in section [API-level transform](#tyk-classic-api) for the Tyk Classic API definition. + +To **append** headers to all responses from your API (i.e. for all endpoints) you must add a new `global_response_headers` object to the `versions` section of your API definition. This contains a list of key:value pairs, being the names and values of the headers to be added to responses. + +To **delete** headers from all responses from your API (i.e. for all endpoints), you must add a new `global_response_headers_remove` object to the `versions` section of the API definition. This contains a list of the names of existing headers to be removed from responses. + +An example is listed below: + +```yaml {linenos=true, linenostart=1, hl_lines=["25-30"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-global-header +spec: + name: httpbin-global-header + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-global-header + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + global_response_headers: + X-Static: foobar + X-Request-ID: "$tyk_context.request_id" + X-User-ID: "$tyk_meta.uid" + global_response_headers_remove: + - X-Secret +``` + +The example API Definition above configures an API to listen on path `/httpbin-global-header` and forwards requests upstream to http://httpbin.org. + +This configuration will add three new headers to each response: + +- `X-Static` with the value `foobar` +- `X-Request-ID` with a dynamic value taken from the `request_id` [context variable](/api-management/traffic-transformation/request-context-variables) +- `X-User-ID` with a dynamic value taken from the `uid` field in the [session metadata](/api-management/access-control/sessions-and-keys/understanding-sessions#metadata-and-context) + +It will also delete one header (if present) from each response: + +- `X-Secret` + + +#### Endpoint-level transform + + +The process of configuring a transformation of a response header for a specific endpoint in Tyk Operator is similar to that defined in section [endpoint-level transform](#tyk-classic-endpoint) for the Tyk Classic API definition. To configure a transformation of the response headers for a specific endpoint you must add a new `transform_response_headers` object to the `extended_paths` section of your API definition. + +In this example the Response Header Transform middleware (`transform_response_headers`) has been configured for HTTP `GET` requests to the `/xml` endpoint. Any response received from the upstream service following a request to that endpoint will have the `Content-Type` header added with a value set to `application/json`. + +#### Example + +```yaml {linenos=true, linenostart=1, hl_lines=["54-60"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-transform +spec: + name: httpbin-transform + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-transform + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + transform: + - method: POST + path: /anything + template_data: + enable_session: false + input_type: json + template_mode: blob + # base64 encoded template + template_source: eyJiYXIiOiAie3suZm9vfX0ifQ== + transform_headers: + - delete_headers: + - "remove_this" + add_headers: + foo: bar + path: /anything + method: POST + transform_response: + - method: GET + path: /xml + template_data: + enable_session: false + input_type: xml + template_mode: blob + # base64 encoded template + template_source: e3sgLiB8IGpzb25NYXJzaGFsIH19 + transform_response_headers: + - method: GET + path: /xml + add_headers: + Content-Type: "application/json" + act_on: false + delete_headers: [] +``` + +#### Tyk Gateway < 5.3.0 Example + +If using Tyk Gateway < v5.3.0 then a `response_processor` object must be added to the API definition containing a `header_injector` item, as highlighted below: + +```yaml {linenos=true, linenostart=1, hl_lines=["17", "19", "57-63"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-transform +spec: + name: httpbin-transform + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-transform + strip_listen_path: true + response_processors: + - name: response_body_transform + - name: header_injector + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_extended_paths: true + paths: + black_list: [] + ignored: [] + white_list: [] + extended_paths: + transform: + - method: POST + path: /anything + template_data: + enable_session: false + input_type: json + template_mode: blob + # base64 encoded template + template_source: eyJiYXIiOiAie3suZm9vfX0ifQ== + transform_headers: + - delete_headers: + - "remove_this" + add_headers: + foo: bar + path: /anything + method: POST + transform_response: + - method: GET + path: /xml + template_data: + enable_session: false + input_type: xml + template_mode: blob + # base64 encoded template + template_source: e3sgLiB8IGpzb25NYXJzaGFsIH19 + transform_response_headers: + - method: GET + path: /xml + add_headers: + Content-Type: "application/json" + act_on: false + delete_headers: [] +``` \ No newline at end of file diff --git a/api-management/traffic-transformation/virtual-endpoints.mdx b/api-management/traffic-transformation/virtual-endpoints.mdx new file mode 100644 index 0000000000..dd645e1adc --- /dev/null +++ b/api-management/traffic-transformation/virtual-endpoints.mdx @@ -0,0 +1,753 @@ +--- +title: "Virtual Endpoints" +description: "Learn how to terminate requests with a Virtual Endpoint" +keywords: "Traffic Transformation, Virtual Endpoints" +sidebarTitle: "Virtual Endpoints" +--- + +## Overview + +Tyk's Virtual Endpoint is a programmable middleware component that is invoked towards the end of the request processing chain. It can be enabled at the per-endpoint level and can perform complex interactions with your upstream service(s) that cannot be handled by one of the other middleware components. + +Virtual endpoint middleware provides a serverless compute function that allows for the execution of custom logic directly within the gateway itself, without the need to proxy the request to an upstream service. This functionality is particularly useful for a variety of use cases, including request transformation, aggregation of responses from multiple services, implementing custom authentication mechanisms, or [terminating requests](/api-management/traffic-transformation/mock-response#virtual-endpoint). + +The Virtual Endpoint is an extremely powerful feature that is unique to Tyk and provides exceptional flexibility to your APIs. + +### Use Cases + +#### Aggregating data from multiple services + +From a virtual endpoint, you can make calls out to other internal and upstream APIs. You can then aggregate and process the responses, returning a single response object to the originating client. This allows you to configure a single externally facing API to simplify interaction with multiple internal services, leaving the heavy lifting to Tyk rather than starting up an aggregation service within your stack. + +#### Enforcing custom policies + +Tyk provides a very flexible [middleware chain](/api-management/traffic-transformation#request-middleware-chain) where you can combine functions to implement the access controls you require to protect your upstream services. Of course, not all scenarios can be covered by Tyk's standard middleware functions, but you can use a virtual endpoint to apply whatever custom logic you require to optimize your API experience. + +#### Dynamic Routing + +With a virtual endpoint you can implement complex [dynamic routing](/transform-traffic/url-rewriting) of requests made to a single external endpoint on to different upstream services. The flexibility of the virtual endpoint gives access to data within the request (including the key session) and also the ability to make calls to other APIs to make decisions on the routing of the request. + +### Working + +The virtual endpoint middleware provides a JavaScript engine that runs the custom code that you provide either inline within the API definition or in a source code file accessible to the Gateway. The JavaScript Virtual Machine (JSVM) provided in the middleware is a traditional ECMAScript5 compatible environment which does not offer the more expressive power of something like Node.js. + +The virtual endpoint terminates the request, so the JavaScript function must provide the response to be passed to the client. When a request hits a virtual endpoint, the JSVM executes the JavaScript code which can modify the request, make calls to other APIs or upstream services, process data, and ultimately determines the response returned to the client. + + + +You will need to enable Tyk's JavaScript Virtual Machine by setting `enable_jsvm` to `true` in your `tyk.conf` [file](/tyk-oss-gateway/configuration#enable_jsvm) for your virtual endpoints to work. + + + +### Scripting virtual endpoint functions + +The [middleware scripting guide](/api-management/plugins/javascript#using-javascript-with-tyk) provides guidance on writing JS functions for your virtual endpoints, including how to access key session data and custom attributes from the API definition. + +#### Function naming + +The virtual endpoint middleware will invoke a named function within the JS code that you provide (either inline or in a file). Both the filename and function name are configurable per endpoint, but note that function names must be unique across your API portfolio because all plugins run in the same virtual machine. This means that you can share a single function definition across multiple endpoints and APIs but you cannot have two different functions with the same name (this applies across all [JavaScript middleware components](/api-management/plugins/javascript#)). + +Inline mode is mainly used by the dashboard to make code injection easier on multiple node deployments. + +### Virtual endpoint library + +We have put together a [library](https://github.com/TykTechnologies/custom-plugins#virtual-endpoints) of JS functions that you could use in your virtual endpoints. We welcome submissions from the Tyk community so if you've created a function that you think would be useful to other users, please open an issue in the Github repository and we can discuss bringing it into the library. + + + + +
+ +{/* proposed "summary box" to be shown graphically on each middleware page + # Virtual Endpoint middleware summary + - The Virtual Endpoint middleware is an optional stage in Tyk's API Request processing chain, sitting between the [TBC]() and [TBC]() middleware. + - The Virtual Endpoint middleware can be configured at the per-endpoint level within the API Definition and is supported by the API Designer within the Tyk Dashboard. */} + + +## Using Tyk OAS + + +The [virtual endpoint](/api-management/traffic-transformation/virtual-endpoints) middleware provides a serverless compute function that allows for the execution of custom logic directly within the gateway itself, without the need to proxy the request to an upstream service. This functionality is particularly useful for a variety of use cases, including request transformation, aggregation of responses from multiple services, or implementing custom authentication mechanisms. + +The middleware is configured in the [Tyk OAS API Definition](/api-management/gateway-config-tyk-oas#operation). You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the legacy Tyk Classic APIs, then check out the [Tyk Classic](#virtual-endpoints-using-classic) page. + +### API Definition + +The design of the Tyk OAS API Definition takes advantage of the `operationId` defined in the OpenAPI Document that declares both the path and method for which the middleware should be added. Endpoint `paths` entries (and the associated `operationId`) can contain wildcards in the form of any string bracketed by curly braces, for example `/status/{code}`. These wildcards are so they are human readable and do not translate to variable names. Under the hood, a wildcard translates to the “match everything” regex of: `(.*)`. + +The virtual endpoint middleware (`virtualEndpoint`) can be added to the `operations` section of the Tyk OAS Extension (`x-tyk-api-gateway`) in your Tyk OAS API Definition for the appropriate `operationId` (as configured in the `paths` section of your OpenAPI Document). + +The `virtualEndpoint` object has the following configuration: + +- `enabled`: enable the middleware for the endpoint +- `functionName`: the name of the JavaScript function that will be executed when the virtual endpoint is triggered +- `body`: [optional] a `base64` encoded string containing the JavaScript code +- `path`: [optional] the relative path to the source file containing the JavaScript code +- `proxyOnError`: [optional, defaults to `false`] a boolean that determines the behavior of the gateway if an error occurs during the execution of the virtual endpoint's function; if set to `true` the request will be proxied to upstream if the function errors, if set to `false` the request will not be proxied and Tyk will return an error response +- `requireSession`: [optional defaults to `false`] a boolean that indicates whether the virtual endpoint should have access to the session object; if `true` then the key session data will be provided to the function as the `session` variable + + + + + One of either `path` or `body` must be provided, depending on whether you are providing the JavaScript code in a file or inline within the API definition. If both are provided then `body` will take precedence. + + + +For example: + +```json {hl_lines=["39-50", "54-58"],linenos=true, linenostart=1} +{ + "components": {}, + "info": { + "title": "example-virtual-endpoint", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-virtual-endpoint", + "state": { + "active": true, + "internal": false + } + }, + "upstream": { + "url": "http://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/example-virtual-endpoint/", + "strip": true + } + }, + "middleware": { + "global": { + "pluginConfig": { + "data": { + "enabled": true, + "value": { + "map": { + "key": 3 + }, + "num": 4, + "string": "example" + } + } + } + }, + "operations": { + "anythingget": { + "virtualEndpoint": { + "enabled": true, + "functionName": "myVirtualHandler", + "body": "ZnVuY3Rpb24gbXlWaXJ0dWFsSGFuZGxlciAocmVxdWVzdCwgc2Vzc2lvbiwgY29uZmlnKSB7ICAgICAgCiAgdmFyIHJlc3BvbnNlT2JqZWN0ID0gewogICAgQm9keTogIlZpcnR1YWwgRW5kcG9pbnQgIitjb25maWcuY29uZmlnX2RhdGEuc3RyaW5nLAogICAgSGVhZGVyczogewogICAgICAiZm9vLWhlYWRlciI6ICJiYXIiLAogICAgICAibWFwLWhlYWRlciI6IEpTT04uc3RyaW5naWZ5KGNvbmZpZy5jb25maWdfZGF0YS5tYXApLAogICAgICAic3RyaW5nLWhlYWRlciI6IGNvbmZpZy5jb25maWdfZGF0YS5zdHJpbmcsCiAgICAgICJudW0taGVhZGVyIjogSlNPTi5zdHJpbmdpZnkoY29uZmlnLmNvbmZpZ19kYXRhLm51bSkKICAgIH0sCiAgICBDb2RlOiAyMDAKICB9CiAgcmV0dXJuIFR5a0pzUmVzcG9uc2UocmVzcG9uc2VPYmplY3QsIHNlc3Npb24ubWV0YV9kYXRhKQp9" + } + } + } + } + } +} +``` + +In this example the virtual endpoint middleware has been configured for requests to the `GET /anything` endpoint. We have also configured the following custom attributes in the `pluginConfig` section of the API definition: + +```json +{ + "map": { + "key": 3 + }, + "num": 4, + "string": "example" +} +``` + +The `body` field value is a `base64` encoded string containing this JavaScript code, which will be invoked by the virtual endpoint middleware: + +```js +function myVirtualHandler (request, session, config) { + var responseObject = { + Body: "Virtual Endpoint "+config.config_data.string, + Headers: { + "foo-header": "bar", + "map-header": JSON.stringify(config.config_data.map), + "string-header": config.config_data.string, + "num-header": JSON.stringify(config.config_data.num) + }, + Code: 200 + } + return TykJsResponse(responseObject, session.meta_data) +} +``` + +A call to the `GET /anything` endpoint returns: + +```bash +HTTP/1.1 200 OK +Date: Fri, 01 Mar 2024 12:14:36 GMT +Foo-Header: bar +Map-Header: {"key":3} +Num-Header: 4 +Server: tyk +String-Header: example +Content-Length: 24 +Content-Type: text/plain; charset=utf-8 + +Virtual Endpoint example +``` + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the virtual endpoint middleware. + +### API Designer + +Adding a Virtual Endpoint to your API endpoints is easy when using the API Designer in the Tyk Dashboard, simply follow these steps: + +1. **Add an endpoint** + + From the **API Designer** add an endpoint that matches the path and method to which you want to apply the middleware. + + Tyk OAS API Designer showing no endpoints created + + Adding an endpoint to an API using the Tyk OAS API Designer + + Tyk OAS API Designer showing no middleware enabled on endpoint + +2. **Select the Virtual Endpoint middleware** + + Select **ADD MIDDLEWARE** and choose **Virtual Endpoint** from the *Add Middleware* screen. + + Adding the Virtual Endpoint middleware + +3. **Configure the middleware** + + Now you can provide either the path to a file containing the JavaScript function to be run by the middleare, or you can directly enter the JavaScript in the code editor. + + For both sources, you must provide the **function name** that should be called when the middleware executes. + + You can also optionally configure the behavior required if the function should return an error and also indicate to Tyk whether the virtual middleware requires access to the key session metadata. + + Configuring the Virtual Endpoint middleware + +4. **Save the API** + + Select **ADD MIDDLEWARE** to save the middleware configuration. Remember to select **SAVE API** to apply the changes. + +## Using Classic + + +The [virtual endpoint](/api-management/traffic-transformation/virtual-endpoints) middleware provides a serverless compute function that allows for the execution of custom logic directly within the gateway itself, without the need to proxy the request to an upstream service. This functionality is particularly useful for a variety of use cases, including request transformation, aggregation of responses from multiple services, or implementing custom authentication mechanisms. + +This middleware is configured in the Tyk Classic API Definition. You can do this via the Tyk Dashboard API or in the API Designer. + +If you're using the newer Tyk OAS APIs, then check out the [Tyk OAS](#virtual-endpoints-using-tyk-oas) page. + +If you're using Tyk Operator then check out the [configuring the middleware in Tyk Operator](#tyk-operator) section below. + +### API Definition + +If you want to use Virtual Endpoints, you must [enable Tyk's JavaScript Virtual Machine](/tyk-oss-gateway/configuration#enable_jsvm) by setting `enable_jsvm` to `true` in your `tyk.conf` file. + +To enable the middleware you must add a new `virtual` object to the `extended_paths` section of your API definition. + +The `virtual` object has the following configuration: + +- `path`: the endpoint path +- `method`: the endpoint HTTP method +- `response_function_name`: this is the name of the JavaScript function that will be executed when the virtual endpoint is triggered +- `function_source_type`: instructs the middleware to look for the JavaScript code either in a `file` or in a base64 encoded `blob`; the actual file location (or base64 encoded code) is provided in `function_source_uri` +- `function_source_uri`: if `function_source_type` is set to `file`, this will be the relative path to the source file containing the JavaScript code; if `function_source_type` if set to `blob`, this will be a `base64` encoded string containing the JavaScript code +- `use_session`: a boolean that indicates whether the virtual endpoint should have access to the session object; if `true` then the key session data will be provided to the function as the `session` variable +- `proxy_on_error`: a boolean that determines the behavior of the gateway if an error occurs during the execution of the virtual endpoint's function; if set to `true` the request will be proxied to upstream if the function errors, if set to `false` the request will not be proxied and Tyk will return an error response + +For example: + +```json {linenos=true, linenostart=1} +{ + "extended_paths": { + "virtual": [ + { + "response_function_name": "myUniqueFunctionName", + "function_source_type": "blob", + "function_source_uri": "ZnVuY3Rpb24gbXlVbmlxdWVGdW5jdGlvbk5hbWUocmVxdWVzdCwgc2Vzc2lvbiwgY29uZmlnKSB7CiB2YXIgcmVzcG9uc2VPYmplY3QgPSB7IAogIEJvZHk6ICJUSElTIElTIEEgVklSVFVBTCBSRVNQT05TRSIsIAogIENvZGU6IDIwMCAKIH0KIHJldHVybiBUeWtKc1Jlc3BvbnNlKHJlc3BvbnNlT2JqZWN0LCBzZXNzaW9uLm1ldGFfZGF0YSkKfQ==", + "path": "/anything", + "method": "GET", + "use_session": false, + "proxy_on_error": false + } + ] + } +} +``` + +In this example the Virtual Endpoint middleware has been configured for requests to the `GET /anything` endpoint. For any call made to this endpoint, Tyk will invoke the function `myUniqueFunctionName` that is `base64` encoded in the `function_source_uri` field. This virtual endpoint does not require access to the session data and will not proxy the request on to the upstream if there is an error when processing the `myUniqueFunctionName` function. + +Decoding the value in `function_source_uri` we can see that the JavaScript code is: + +```js {linenos=true, linenostart=1} +function myUniqueFunctionName(request, session, config) { + var responseObject = { + Body: "THIS IS A VIRTUAL RESPONSE", + Code: 200 + } + return TykJsResponse(responseObject, session.meta_data) +} +``` + +This function will terminate the request without proxying it to the upstream returning `HTTP 200` as follows: + +```bash +HTTP/1.1 200 OK +Date: Wed, 28 Feb 2024 20:52:30 GMT +Server: tyk +Content-Type: text/plain; charset=utf-8 +Content-Length: 26 + +THIS IS A VIRTUAL RESPONSE +``` + +If, however, we introduce an error to the JavaScript, such that Tyk fails to process the function, we will receive an `HTTP 500 Internal Server Error` as follows: + +```bash +HTTP/1.1 500 Internal Server Error +Date: Wed, 28 Feb 2024 20:55:27 GMT +Server: tyk +Content-Type: application/json +Content-Length: 99 + +{ +"error": "Error during virtual endpoint execution. Contact Administrator for more details." +} +``` + +If we set `proxy_on_error` to `true` and keep the error in the Javascript, the request will be forwarded to the upstream and Tyk will return the response received from that service. + +### API Designer + +You can use the API Designer in the Tyk Dashboard to configure a virtual endpoint for your Tyk Classic API by following these steps. + +1. **Add an endpoint for the path and select the plugin** + + From the **Endpoint Designer** add an endpoint that matches the path for which you want to trigger the virtual endpoint. Select the **Virtual Endpoint** plugin. + + Selecting the middleware + +2. **Configure the middleware** + + Once you have selected the virtual endpoint middleware for the endpoint, you need to supply: + + - JS function to call + - Source type (`file` or `inline`) + + If you select source type `file` you must provide the path to the file: + Configuring file based JS code + + If you select `inline` you can enter the JavaScript code in the Code Editor window. + Configuring inline JS code + +3. **Save the API** + + Use the *save* or *create* buttons to save the changes and activate the Virtual Endpoint middleware. + + + + + The Tyk Classic API Designer does not provide options to configure `use_session` or `proxy_on_error`, but you can do this from the Raw Definition editor. + + + +### Tyk Operator + +The process for configuring a virtual endpoint using Tyk Operator is similar to that explained in configuring the middleware in the Tyk Classic API Definition + +The example API Definition below configures an API to listen on path `/httpbin` and forwards requests upstream to `http://httpbin.org`. The Virtual Endpoint middleware has been configured for requests to the `GET /virtual` endpoint. For any call made to this endpoint, Tyk will invoke the function `myVirtualHandler` that is base64 encoded in the `function_source_uri` field. This virtual endpoint does not require access to the session data and will not proxy the request on to the upstream if there is an error when processing the `myVirtualHandler` function. + +```yaml {linenos=true, linenostart=1, hl_lines=["23-35"]} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: test-config-data-test +spec: + name: test-config-data-test + protocol: http + proxy: + listen_path: /httpbin/ + target_url: http://httpbin.org + strip_listen_path: true + active: true + use_keyless: true + enable_context_vars: true + version_data: + default_version: Default + not_versioned: false + versions: + Default: + name: Default + use_extended_paths: true + extended_paths: + virtual: + - function_source_type: blob + response_function_name: myVirtualHandler + function_source_uri: "ZnVuY3Rpb24gbXlWaXJ0dWFsSGFuZGxlciAocmVxdWVzdCwgc2Vzc2lvbiwgY29uZmlnKSB7ICAgICAgCiAgdmFyIHJlc3BvbnNlT2JqZWN0ID0gewogICAgQm9keTogIlRISVMgSVMgQSAgVklSVFVBTCBSRVNQT05TRSIsCiAgICBIZWFkZXJzOiB7CiAgICAgICJmb28taGVhZGVyIjogImJhciIsCiAgICAgICJtYXAtaGVhZGVyIjogSlNPTi5zdHJpbmdpZnkoY29uZmlnLmNvbmZpZ19kYXRhLm1hcCksCiAgICAgICJzdHJpbmctaGVhZGVyIjogY29uZmlnLmNvbmZpZ19kYXRhLnN0cmluZywKICAgICAgIm51bS1oZWFkZXIiOiBKU09OLnN0cmluZ2lmeShjb25maWcuY29uZmlnX2RhdGEubnVtKQogICAgfSwKICAgICAgQ29kZTogMjAwCiAgfQogIHJldHVybiBUeWtKc1Jlc3BvbnNlKHJlc3BvbnNlT2JqZWN0LCBzZXNzaW9uLm1ldGFfZGF0YSkKfQ==" + path: /virtual + method: GET + use_session: false + proxy_on_error: false + config_data: + string: "string" + map: + key: 3 + num: 4 +``` + +Decoding the value in `function_source_uri` we can see that the JavaScript code is: + +```javascript +function myVirtualHandler (request, session, config) { + var responseObject = { + Body: "THIS IS A VIRTUAL RESPONSE", + Headers: { + "foo-header": "bar", + "map-header": JSON.stringify(config.config_data.map), + "string-header": config.config_data.string, + "num-header": JSON.stringify(config.config_data.num) + }, + Code: 200 + } + return TykJsResponse(responseObject, session.meta_data) +} +``` + +This function will terminate the request without proxying it to the upstream, returning HTTP 200 as follows: + +```bash +HTTP/1.1 200 OK +Date: Wed, 14 Aug 2024 15:37:46 GMT +Foo-Header: bar +Map-Header: {"key":3} +Num-Header: 4 +Server: tyk +String-Header: string +Content-Length: 27 +Content-Type: text/plain; charset=utf-8 + +THIS IS A VIRTUAL RESPONSE +``` + +If, however, we introduce an error to the JavaScript, such that Tyk fails to process the function, we will receive an HTTP 500 Internal Server Error as follows: + +```bash +HTTP/1.1 500 Internal Server Error +Date: Wed, 14 Aug 2024 15:37:46 GMT +Server: tyk +Content-Type: application/json +Content-Length: 99 + +{ +"error": "Error during virtual endpoint execution. Contact Administrator for more details." +} +``` + +If we set `proxy_on_error` to `true` and keep the error in the Javascript, the request will be forwarded to the upstream and Tyk will return the response received from that service. + +## Examples + +### Accessing Tyk data objects + +In this example, we demonstrate how you can access different [external Tyk objects](/api-management/plugins/javascript#accessing-external-and-dynamic-data) (API request, session key, API definition). + +1. Enable the Virtual Endpoint middleware on an endpoint of your API and paste this JavaScript into the API Designer (or save in a file and reference it from the middleware config): + +```javascript +function myFirstVirtualHandler (request, session, config) { + log("Virtual Test running") + + log("Request Body: " + request.Body) + log("Session: " + JSON.stringify(session.allowance)) + log("Config: " + JSON.stringify(config.APIID)) + log("param-1: " + request.Params["param1"]) // case sensitive + log("auth Header: " + request.Headers["Authorization"]) // case sensitive + + var responseObject = { + Body: "VIRTUAL ENDPOINT EXAMPLE #1", + Headers: { + "x-test": "virtual-header", + "x-test-2": "virtual-header-2" + }, + Code: 200 + } + + return TykJsResponse(responseObject, session.meta_data) +} +log("Virtual Test initialised") +``` + +2. Make a call to your API endpoint passing a request body, a value in the `Authorization` header and a query parameter `param1`. + +3. The virtual endpoint will terminate the request and return this response: + +```bash +HTTP/1.1 200 OK +Date: Thu, 29 Feb 2024 17:39:00 GMT +Server: tyk +X-Test: virtual-header +X-Test-2: virtual-header-2 +Content-Length: 27 +Content-Type: text/plain; charset=utf-8 + +VIRTUAL ENDPOINT EXAMPLE #1 +``` + +4. The gateway logs will include: + +```text +time="" level=info msg="Virtual Test running" prefix=jsvm type=log-msg +time="" level=info msg="Request Body: " prefix=jsvm type=log-msg +time="" level=info msg="Session: " prefix=jsvm type=log-msg +time="" level=info msg="Config: " prefix=jsvm type=log-msg +time="" level=info msg="param-1: " prefix=jsvm type=log-msg +time="" level=info msg="auth Header: " prefix=jsvm type=log-msg +``` + +### Accessing custom attributes in the API Definition + +You can add [custom attributes](/api-management/plugins/javascript#adding-custom-attributes-to-the-api-definition) to the API definition and access these from within your Virtual Endpoint. + +1. Add the following custom attributes to your API definition: + +```json +{ + "string": "string", + "map": { + " key": 3 + }, + "num": 4 +} +``` + +2. Enable the Virtual Endpoint middleware on an endpoint of your API and paste this JavaScript into the API Designer (or save in a file and reference it from the middleware config): + +```js +function mySecondVirtualHandler (request, session, config) { + var responseObject = { + Body: "VIRTUAL ENDPOINT EXAMPLE #2", + Headers: { + "foo-header": "bar", + "map-header": JSON.stringify(config.config_data.map), + "string-header": config.config_data.string, + "num-header": JSON.stringify(config.config_data.num) + }, + Code: 200 + } + return TykJsResponse(responseObject, session.meta_data) +} +``` + +3. Make a call to your API endpoint. + +4. The virtual endpoint will terminate the request and return this response: + +```bash +HTTP/1.1 200 OK +Date: Thu, 29 Feb 2024 17:29:12 GMT +Foo-Header: bar +Map-Header: {" key":3} +Num-Header: 4 +Server: tyk +String-Header: string +Content-Length: 26 +Content-Type: text/plain; charset=utf-8 + +VIRTUAL ENDPOINT EXAMPLE #2 +``` + +### Advanced example + +In this example, every line in the script gives an example of a functionality usage, including: + +- how to get form param +- how to get to a specific key inside a JSON variable +- the structure of the request object +- using `TykMakeHttpRequest` to make an HTTP request from within the virtual endpoint, and the json it returns - `.Code` and `.Body`. + +```js +function myVirtualHandlerGetHeaders (request, session, config) { + rawlog("Virtual Test running") + + //Usage examples: + log("Request Session: " + JSON.stringify(session)) + log("API Config:" + JSON.stringify(config)) + + log("Request object: " + JSON.stringify(request)) + log("Request Body: " + JSON.stringify(request.Body)) + log("Request Headers:" + JSON.stringify(request.Headers)) + log("param-1:" + request.Params["param1"]) + + log("Request header type:" + typeof JSON.stringify(request.Headers)) + log("Request header:" + JSON.stringify(request.Headers.Location)) + + + //Make api call to upstream target + newRequest = { + "Method": "GET", + "Body": "", + "Headers": {"location":JSON.stringify(request.Headers.Location)}, + "Domain": "http://httpbin.org", + "Resource": "/headers", + "FormData": {} + }; + rawlog("--- before get to upstream ---") + response = TykMakeHttpRequest(JSON.stringify(newRequest)); + rawlog("--- After get to upstream ---") + log("response type: " + typeof response); + log("response: " + response); + usableResponse = JSON.parse(response); + var bodyObject = JSON.parse(usableResponse.Body); + + var responseObject = { + //Body: "THIS IS A VIRTUAL RESPONSE", + Body: "yo yo", + Headers: { + "test": "virtual", + "test-2": "virtual", + "location" : bodyObject.headers.Location + }, + Code: usableResponse.Code + } + + rawlog("Virtual Test ended") + return TykJsResponse(responseObject, session.meta_data) +} +``` + +### Running the Advanced example + +You can find a Tyk Classic API definition [here](https://gist.github.com/letzya/5b5edb3f9f59ab8e0c3c614219c40747) that includes the advanced example, with the JS encoded `inline` within the middleware config for the `GET /headers` endpoint. + +Create a new Tyk Classic API using that API definition and then run the following command to send a request to the API endpoint: + +```bash +curl http://tyk-gateway:8080/testvirtualendpoint2/headers -H "location: /get" -v +``` + +This should return the following: + +```bash +Trying 127.0.0.1... +TCP_NODELAY set +Connected to tyk-gateway (127.0.0.1) port 8080 (#0) +GET /testvirtualendpoint2/headers HTTP/1.1 +Host: tyk-gateway:8080 +User-Agent: curl/7.54.0 +Accept: */* +location: /get + +HTTP/1.1 200 OK +Date: Fri, 08 Jun 2018 21:53:57 GMT +**Location: /get** +Server: tyk +Test: virtual +Test-2: virtual +Content-Length: 5 +Content-Type: text/plain; charset=utf-8 + +Connection #0 to host tyk-gateway left intact +yo yo +``` + +### Checking the Tyk Gateway Logs + +The `log` and `rawlog` commands in the JS function write to the Tyk Gateway logs. If you check the logs you should see the following: + +```text +[Jun 13 14:45:21] DEBUG jsvm: Running: myVirtualHandlerGetHeaders +Virtual Test running +[Jun 13 14:45:21] INFO jsvm-logmsg: Request Session: {"access_rights":null,"alias":"","allowance":0,"apply_policies":null,"apply_policy_id":"","basic_auth_data":{"hash_type":"","password":""},"certificate":"","data_expires":0,"enable_detail_recording":false,"expires":0,"hmac_enabled":false,"hmac_string":"","id_extractor_deadline":0,"is_inactive":false,"jwt_data":{"secret":""},"last_check":0,"last_updated":"","meta_data":null,"monitor":{"trigger_limits":null},"oauth_client_id":"","oauth_keys":null,"org_id":"","per":0,"quota_max":0,"quota_remaining":0,"quota_renewal_rate":0,"quota_renews":0,"rate":0,"session_lifetime":0,"tags":null} type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: API Config:{"APIID":"57d72796c5de45e649f22da390d7df43","OrgID":"5afad3a0de0dc60001ffdd07","config_data":{"bar":{"y":3},"foo":4}} type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request object: {"Body":"","Headers":{"Accept":["*/*"],"Location":["/get"],"User-Agent":["curl/7.54.0"]},"Params":{"param1":["I-am-param-1"]},"URL":"/testvirtualendpoint2/headers"} type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request Body: "" type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request Headers:{"Accept":["*/*"],"Location":["/get"],"User-Agent":["curl/7.54.0"]} type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: param-1:I-am-param-1 type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request header type:[object Object] type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request header: ["/get"] type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request location type: object type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request location type: string type=log-msg +[Jun 13 14:45:21] INFO jsvm-logmsg: Request location: /get type=log-msg +--- before get to upstream --- +--- After get to upstream --- +[Jun 13 14:45:22] INFO jsvm-logmsg: response type: string type=log-msg +[Jun 13 14:45:22] INFO jsvm-logmsg: response: {"Code":200,"Body":"{\"headers\":{\"Accept-Encoding\":\"gzip\",\"Connection\":\"close\",\"Host\":\"httpbin.org\",\"Location\":\"/get\",\"User-Agent\":\"Go-http-client/1.1\"}}\n","Headers":{"Access-Control-Allow-Credentials":["true"],"Access-Control-Allow-Origin":["*"],"Content-Length":["133"],"Content-Type":["application/json"],"Date":["Wed, 13 Jun 2018 13:45:21 GMT"],"Server":["gunicorn/19.8.1"],"Via":["1.1 vegur"]},"code":200,"body":"{\"headers\":{\"Accept-Encoding\":\"gzip\",\"Connection\":\"close\",\"Host\":\"httpbin.org\",\"Location\":\"/get\",\"User-Agent\":\"Go-http-client/1.1\"}}\n","headers":{"Access-Control-Allow-Credentials":["true"],"Access-Control-Allow-Origin":["*"],"Content-Length":["133"],"Content-Type":["application/json"],"Date":["Wed, 13 Jun 2018 13:45:21 GMT"],"Server":["gunicorn/19.8.1"],"Via":["1.1 vegur"]}} type=log-msg +Virtual Test ended +[Jun 13 14:45:22] DEBUG JSVM Virtual Endpoint execution took: (ns) 191031553 +``` + +### Aggregating upstream calls using batch processing + +One of the most common use cases for virtual endpoints is to provide some form of aggregate data to your users, combining the responses from multiple upstream service calls. This virtual endpoint function will do just that using the batch processing function from the [JavaScript API](/api-management/plugins/javascript#javascript-api) + +```js +function batchTest(request, session, config) { + // Set up a response object + var response = { + Body: "", + Headers: { + "test": "virtual-header-1", + "test-2": "virtual-header-2", + "content-type": "application/json" + }, + Code: 200 + } + + // Batch request + var batch = { + "requests": [ + { + "method": "GET", + "headers": { + "x-tyk-test": "1", + "x-tyk-version": "1.2", + "authorization": "1dbc83b9c431649d7698faa9797e2900f" + }, + "body": "", + "relative_url": "http://httpbin.org/get" + }, + { + "method": "GET", + "headers": {}, + "body": "", + "relative_url": "http://httpbin.org/user-agent" + } + ], + "suppress_parallel_execution": false + } + + log("[Virtual Test] Making Upstream Batch Request") + var newBody = TykBatchRequest(JSON.stringify(batch)) + + // We know that the requests return JSON in their body, lets flatten it + var asJS = JSON.parse(newBody) + for (var i in asJS) { + asJS[i].body = JSON.parse(asJS[i].body) + } + + // We need to send a string object back to Tyk to embed in the response + response.Body = JSON.stringify(asJS) + + return TykJsResponse(response, session.meta_data) + +} +log("Batch Test initialised") +``` + diff --git a/api-management/troubleshooting-debugging.mdx b/api-management/troubleshooting-debugging.mdx new file mode 100644 index 0000000000..6fb9c77919 --- /dev/null +++ b/api-management/troubleshooting-debugging.mdx @@ -0,0 +1,1636 @@ +--- +title: "Troubleshooting and Debugging" +description: "Tyk troubleshooting and debugging gateway, streams, pump, dashboard" +keywords: "troubleshooting, debugging, Open Source, Self-Managed, Tyk Cloud, API Gateway" +sidebarTitle: "Troubleshooting" +--- + +## Gateway + +1. ##### Users receive 499 error in the Gateway + + **Cause** + + The Gateway receives closed client responses from the upstream client. There are number of different configuration settings that could bring about this issue. + + **Solution** + + For a standard web app, used by standard HTTP clients 499 errors are not a problem. + ​ + + However, in some specific cases, depending on the service you provide, your clients can have their own fixed constraints. + For example, if you are building an API used by IoT devices, and those devices internally have a strict 2 second timeout for HTTP calls and your service responding with > 2 seconds. In this case a lot of 499 errors may mean that a lot of clients are malfunctioning, and you should investigate this behavior. + + On the other hand, sometimes a client closing the connection before reading the server response is expected functionality. Taking the same example as above, you may have some IoT sensor, which just pushes data to your servers in "fire and forgot" mode, and does not care about the server response. In this case a 499 error is completely expected behavior. + + +2. ##### Users receive 502 error in the Gateway + + **Cause** + + The Gateway received an invalid response from the upstream server. There are number of different configuration settings that could bring about this issue. + + **Solution** + + Try using the following settings in your tyk.conf file: + + ```{.copyWrapper} + enable_detailed_recording: false, + enable_jsvm: false, + ``` + + + And the following key-value pairs should be set in the relevant API definition: + + ```{.copyWrapper} + proxy.service_discovery.use_nested_query = false + proxy.service_discovery.use_target_list = true + proxy.service_discovery.endpoint_returns_list = true + proxy.service_discovery.data_path = ""Address” + proxy.service_discovery.port_data_path = “ServicePort"" + ``` + + See [Tyk Gateway configuration](/tyk-oss-gateway/configuration) and [Tyk Gateway API](/api-management/gateway-config-tyk-classic) for further information regarding API definition settings. + +3. ##### Gateway proxy error "context canceled" + + In some cases you can see "proxy error: context canceled" error message in the Gateway logs. + The error itself means that the connection was closed unexpectedly. + It can happen for various reasons, and in some cases it is totally fine: for example client can have unstable mobile internet. + + When it happens on the high load, it can be a lot of different reasons. + For example your OS is running out of system limits, like number of opened sockets, and to validate it, you need to try your system limits. + See [this guide](/planning-for-production). + + Additionally, it can be CPU bottleneck: you can't process more than your machine can do. + And note that it is not only about the actual utilization %, it is also about context switches it has to do. + E.g. having one job which consume 100% of your CPU/cores vs having a few thousands jobs, causing CPU constantly switch between them. + Such problems cause internal request processing queues, which cause latency growth (highly recommend measure it). + And in some cases latency can grow so big, that some clients can just disconnect/timeout because of it. + + Additionally, highly recommend read the following blog post https://tyk.io/blog/performance-tuning-your-tyk-api-gateway/. + For example, you can trade memory for performance, and context switch reduction by tuning garbage collector to run less frequently: see `Tuning Tyk’s Garbage Collector` section. + + + Also note that it is not Tyk or Golang specific. + The problem described above will happen with any webserver on high scale. + So in general if you see a lot of "context" errors on high load, use it as a sign that the process is really struggling with the given load, and you need scale it up, either vertically or horizontally. + +4. ##### Invalid memory address or nil pointer dereference error + + **Cause** + + There are a number of reasons, most commonly, an API may have been configured incorrectly in some way (for instance, it may have been set up without an organization). The error itself is a specific to Go language which Tyk was written in and could also suggest that alterations made to the code by the user could also be the culprit. + + **Solution** + + Make sure that API definitions are set up correctly. Information on how to do this with the Tyk Gateway API can be found in the following links: + + * [API Definition Object Details](/api-management/gateway-config-tyk-classic) + * [API Management](/tyk-gateway-api) + +5. ##### Users receive this error message when attempting to make API calls to an existing key. + + **Cause** + When the token was created, most probably it was configured without the `meta_data` key. + + **Solution** + The user will need to add the key-value pair `meta_data: {}` to their key as per the [Tyk Gateway REST API Documentation](/tyk-gateway-api). + +6. ##### There was a problem proxying the request + + **Cause** + + The upstream server may have returned an empty response or cut the response off early so it was unable to complete the proxying process. A proxy error means actual connectivity issues between Tyk and the target host (i.e., a connection-level issue with the downstream server misbehaving for some reason). + + Expired TLS certificates may also cause issues. + + **Solution** + + Users are advised to upgrade to the latest versions of any Tyk packages at their earliest convenience as a patch was released to resolve this issue. Packages are available to download from [Packagecloud.io][1]. See [Upgrading Tyk](/developer-support/upgrading) for details on upgrading to the latest version. It may also be worth checking if any TLS certificates associated with the domain have expired. + + [1]: https://packagecloud.io/tyk + +6. ##### Tyk Gateway Profiling + + In some cases, to identify tricky issues like concurrency or memory related issues, it may be required to get information about the Gateway process runtime. For example, memory or CPU usage details. + The Tyk Gateway is built using Go, and inherits its powerful profiling tools, specifically Google's [`pprof`](https://github.com/google/pprof/). + + The Tyk Gateway can generate various profiles in the `pprof` supported format, which you can analyze by yourself, using the `go tool pprof` command, or you can send the profiles to our support team for analysis. + + There are two way to get profiles: + + 1. Running the process with flags mentioned below which will gather information about the running process for the first 30 seconds, and will generate files containing profiling info: + + * `--memprofile` - memory profile, generates `tyk.mprof` file + * `--cpuprofile` - CPU usage profile, generates `tyk.prof` file + * `--blockprofile` - Blocking profile, generates `tyk.blockprof` file + * `--mutexprofile` - Mutex profile, generates `tyk.mutexprof` file + + 2. Running with the `--httpprofile` flag, or set `enable_http_profiler` to `true` in tyk.conf, which will run a special `/debug/pprof/` public web page, containing dynamic information about the running process, and where you can download various profiles: + + * goroutine - stack traces of all current goroutines + * heap - a sampling of all heap allocations + * threadcreate - stack traces that led to the creation of new OS threads + * block - stack traces that led to blocking on synchronisation primitives + * mutex - stack traces of holders of contended mutexes + +##### Support Information + + + When contacting support, you may be asked to supply extra information and supply log files, etc, so we can quickly handle your request. Questions may include: + + * "Can you send us your log files" + * "Can you change the logging detail level" + * "What version of Tyk are you on" + * "What profiling information can I get" + + + This page will let you know how to get the above info to us. + + **Log Files** + + **Where do I find my log files?** + + The Gateway will log its output to `stderr` and `stdout`. In a typical installation, these will be handled or redirected by the service manager running the process, and depending on the Linux distribution, will either be output to `/var/log/` or `/var/log/upstart`. + + Tyk will try to output structured logs, and so will include context data around request errors where possible. + + **How do I increase Logging Verbosity?** + + You can set the logging verbosity in two ways: + + 1. Via an Environment Variable to affect all Tyk components + 2. Just for the Gateway via your `tyk.conf` config file + + **Setting via Environment Variable** + The environment variable is `TYK_LOGLEVEL`. + + + By default, the setting is `info`. You also have the following options: + + * `debug` + * `warn` + * `error` + + You will be advised by support which setting to change the logging level to. + + **For the Gateway** + + You can set the logging level in your `tyk.conf` by adding the following: + + ```{.copyWrapper} + "log_level": "info", + ``` + + By default, the setting is `info`. You also have the following options: + + * `debug` + * `warn` + * `error` + + You will be advised by support which setting to change the logging level to. + + **Tyk Version** + + For support requests it is beneficial to provide more information about your Gateway build. These pinpoint the exact Gateway build that is in use. + + - Since Gateway version `5.0.8` or `5.2.3` you can inspect detailed build information by running `tyk version`. The information also includes the Go version it was built with, the operating system and architecture. + + - If you're running an an older version than the above, `tyk --version` prints out the release version for your Gateway binary. + + The binary is installed in `/opt/tyk-gateway/tyk` by default. If your binary is not available in your `PATH` environment, invoke it from there. + + **Profile Information** + + You can provide various profile information for us in [pprof format](https://github.com/google/pprof/). See [Gateway Profiling](#tyk-gateway-profiling) for more details. + +8. ##### API definition URL case sensitive + + For security reasons Tyk lowercases the URL before performing any pattern matching. + +9. ##### Gateway detected 0 APIs + + Tyk Gateway is not able to get API configs from the Tyk Portal. + If you configured your Gateway to be segmented, you would also need to assign tags and you must also tag the APIs in the API Designer to make sure that they load. + + * In the Pro edition that is a connectivity or misconfiguration issue + * In the Community edition, since you are not using the Dashboard we + assume that you use file-based APIs , so in this case it's because + API definition files are missing. + +10. ##### How to import existing keys into Tyk CE + + You can use an API to import existing keys that were not created in Tyk into Tyk's Gateway. + This doc explains how to do that with the Gateway's APIs directly. + + This example uses standard `authorization` header authentication, and assumes that the Gateway is located at `127.0.0.1:8080` and the Tyk secret is `352d20ee67be67f6340b4c0605b044b7` - update these as necessary to match your environment. + + To import a key called `mycustomkey`, save the JSON contents as `token.json` (see example below), then run the following Curl command: + + The Example `token.json` file + + ```{.json} + { + "allowance": 1000, + "rate": 1000, + "per": 60, + "expires": -1, + "quota_max": -1, + "quota_renews": 1406121006, + "quota_remaining": 0, + "quota_renewal_rate": 60, + "access_rights": { + "3": { + "api_name": "Tyk Test API", + "api_id": "3" + } + }, + "org_id": "53ac07777cbb8c2d53000002", + "basic_auth_data": { + "password": "", + "hash_type": "" + }, + "hmac_enabled": false, + "hmac_string": "", + "is_inactive": false, + "apply_policy_id": "", + "apply_policies": [ + "59672779fa4387000129507d", + "53222349fa4387004324324e", + "543534s9fa4387004324324d" + ], + "monitor": { + "trigger_limits": [] + } + } + ``` + + The import of the key to Tyk: + + ``` + curl http://127.0.0.1:8080/tyk/keys/mycustomkey -H 'x-tyk-authorization: 352d20ee67be67f6340b4c0605b044b7' -H 'Content-Type: application/json' -d @token.json + ``` + + Test the key after the import: + + ``` + curl http://127.0.0.1:8080/quickstart/headers -H 'Authorization: mycustomkey' + ``` + + See also the Keys section of the [Tyk Gateway API documentation](/tyk-gateway-api). + + +10. ##### Redis persistence using containers + + Use case: Keep my data persistent at Docker container restart + + The Multi-Cloud Redis container is ephemeral, it isn't configured for persistence because it would very quickly get very large (Docker containers in general should really be considered as ephemeral). + + If using Redis with Multi-Cloud we strongly recommend using an external Redis database. + + There are no settings for Redis available via environment variable, you would need to mount a new `redis.conf` into the container to customize the configuration, but again, we don't recommend it. + +11. ##### DRL not ready, skipping this notification + + **Description** + + You see the following `Log Warning:` + + `DRL not ready, skipping this notification` + + + **Cause** + + There can be a couple of reasons for seeing this error about the [Distributed Rate Limiter](/api-management/rate-limit#rate-limiting-layers): + + 1. When you have more than one installation of the Gateway with one configured to use DRL, and others not. + 2. When the Gateway is started and the DRL receives an event before it has finished initialising. + + **Solution** + + For cause **1**, ensure that all instances of the Tyk Gateway are configured to use DRL. + + For cause **2**, the error will disappear when the DRL has initialised. + +12. ##### "Index out of range“ error in logs + + **Description** + + Redis cluster users receive the aforementioned error message in their logs. The log stack may resemble the following: + + ``` + 2016/06/22 09:58:41 http: panic serving 10.0.0.1:37196: runtime error: index out of range + 2016/06/22 09:58:41 http: panic serving 10.0.0.1:37898: runtime error: index out of range + 2016/06/22 09:58:41 http: panic serving 10.0.0.1:38013: runtime error: index out of range + 2016/06/22 09:58:42 http: panic serving 10.0.0.1:39753: runtime error: index out of range + 2016/06/22 10:01:07 http: panic serving 10.0.0.1:34657: runtime error: invalid memory address or nil pointer dereference + 2016/06/22 10:01:07 http: panic serving 10.0.0.1:36801: runtime error: invalid memory address or nil pointer dereference + ``` + + **Cause** + + This is due to a bug that prevents the driver from picking up a random redis handle in single-instance connections such as pub/sub. The issue affects later patch releases of Tyk 2.2 and the first release of Tyk 2.3. + + **Solution** + + Users are advised to upgrade to the latest versions of any Tyk packages as a patch was released to resolve this issue. Packages are available to download from [Packagecloud.io](https://packagecloud.io/tyk) and further details on how to upgrade can be found [here](/developer-support/upgrading). + +13. ##### Hot restart a Tyk Gateway Process + + It is possible to hot-restart a Tyk Gateway process without dropping any connections. This can be useful if you need to load up a new configuration or change a configuration on a production server without losing any traffic. + + To hot-restart a Tyk Gateway process, you simply need to send a `SIGUSR2` signal to the process, for example: + + ```bash + > sudo kill -SIGUSR2 {gateway-pid} + ``` + + This will fork and load a new process, passing all open handles to the new server and wait to drain the old ones. + +14. ##### How to add Custom Certificates to Trusted Storage of Docker Images + + To add your custom Certificate Authority(CA) to your docker containers. You can mount your CA certificate directly into `/etc/ssl/certs` folder. + + Docker: + ```{.copyWrapper} + docker run -it tykio/tyk-gateway:latest \ + -v $(pwd)/myCA.pem:/etc/ssl/certs/myCA.pem + ``` + + Kubernetes - using Helm Chart and secrets: + ```yaml + extraVolumes: + - name: self-signed-ca + secret: + secretName: self-signed-ca-secret + extraVolumeMounts: + - name: self-signed-ca + mountPath: "/etc/ssl/certs/myCA.pem" + subPath: myCA.pem + ``` + +15. ##### How to change the logging output location + + It's not possible to segregate out the error locations in the `tyk.conf`, but you can modify the actual initialisation files to specify the log location, we supply initialisation scripts for `SysV`, `systemd` and `upstart`. + +16. ##### How to clear / invalidate API cache + + Use the REST API to clear the cache + + **OSS** + + ``` + DELETE /tyk/cache/{api-id} + ``` + + **Tyk Dashboard** + + ``` + DELETE /api/cache/{api-id} + ``` + +17. ##### How to find the Gateway logging output + + You are able to see a more detailed output in your Gateway log `/var/log` or `/var/log/upstart`. + +18. Gateway `proxy_default_timeout` vs `http_server_options.write_timeout` + + **Tyk to Upstream timeout** + + `proxy_default_timeout` specifies the amount of time that Tyk will wait for the upstream service to complete its reply to Tyk + + With gateways prior to release 5.0.7 `proxy_default_timeout` defaulted to zero which would result in the gateway waiting forever. This consumed resources and could lead to depletion of ephemeral sockets. + + Since gateway 5.0.7 `proxy_default_timeout` defaults to 30 seconds. + + When increasing `proxy_default_timeout` beyond 120 seconds it is necessary to consider the client to Tyk timeouts. + + **Client to Tyk timeouts** + + `http_server_options.write_timeout` specifies the number of seconds that Tyk will keep the client connection open for Tyk to write to it. If this is less than `proxy_default_timeout` then the connection to the client will be closed and the upstream reply will not be proxied back to the client if the upstream takes longer than `http_server_options.write_timeout` seconds. To avoid this please make sure that `http_server_options.write_timeout`is at least 1 second longer than `proxy_default_timeout` + + `http_server_options.read_timeout` specifies that number of seconds that Tyk will allow the client to complete sending the request to it. This usually doesn't need to be changed since a lot of data can be sent in the default of 120 seconds, but it can be changed if needed. + +## Gateway Error Response Status Codes + +Tyk Gateway responses include HTTP status codes that follow the [HTTP status code standard](https://datatracker.ietf.org/doc/html/rfc9110). They have three digits that describe the result of the request and the semantics of the response. +The first digit defines the class of response as shown in the [list](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) below: +- 1xx (Informational): The request was received, continuing process +- 2xx (Successful): The request was successfully received, understood, and accepted +- 3xx (Redirection): Further action needs to be taken in order to complete the request +- 4xx (Client Error): The request contains bad syntax or cannot be fulfilled +- 5xx (Server Error): The server failed to fulfill an apparently valid request + +Here we provide a list of all the error status codes (4xx and 5xx) that may be returned by the Tyk Gateway along with their corresponding messages and some guidance on the likely cause of the error. + +Tyk supports comprehensive [error response customization](/api-management/custom-error-responses), allowing you to configure the Gateway to return customized messages for different HTTP error codes. + + +| Code | Text | Recommended action | +| :--- | :-------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 400 | Access to this API has been disallowed | Check if the key has access to the right API version or definition. Check if the authentication key used is still valid. Check if the certificate used for authentication is present. Check if the authentication key is created and present in the database. You can use Gateway Keys APIs for confirmation. Check if API definition is using JWT auth and if auth header key and or value is empty or missing.| +| 400 | API is not OAuth2 | Check if OAuth2 is integrated into the API by auth tokens or using Tyk OAuth flow. | +| 400 | Attempted access with malformed header | Values not in basic auth format or auth data not encoded correctly. | +| 400 | Authorization Field Missing | Check if the authorization field is missing. Check if the OAuth authorization field is missing. | +| 400 | Batch request creation failed, request structure malformed | Attempted to construct unsafe requests. Check if request structure is in correct format. | +| 400 | Batch request malformed | Attempted to decode request but failed. Check if request structure is in correct format. | +| 400 | Bearer token malformed | Check if the OAuth authorization field is malformed. | +| 400 | Body do not contain password or username | Check if body contains both password and username. If not, then insert the correct login credentials. | +| 400 | Cannot parse form. Form malformed | Attempted to revoke token but could not parse the request form. Check if the request form is malformed. | +| 400 | Content length is not a valid Integer | Check the value provided in the Content-Length field in the header. | +| 400 | Couldn’t decode instruction | Attempted to decode policy record from an update request. Check if the request body is malformed and is valid. | +| 400 | Couldn’t decode OAS object | Attempted to import OAS Tyk API but failed to retrieve object from request. Check if request body is valid. | +| 400 | Error API not migrated | The supplied API definition is in OAS format. Please use the Tyk native format for this API. | +| 400 | Failed to create key, keys must have at least one Access Rights record set | Attempted to create a key with master keys disabled in configurations. | +| 400 | Failed to remove the key | Failed to delete requested key. Make sure orgID and keyname are correct. | +| 400 | Health checks are not enabled for this node | Enable health checks for the gateway. | +| 400 | Key not authorized | Check if OAuth key is present. Check if the OAuth client is not deleted. Check if there is a valid policy associated with the key/token used. Check if the policy associated with the key is not expired or if the owner is valid. Check if JWT default policies exist. | +| 400 | Key cannot be used without a certificate | Check if key contains a certificate. If not, add a certificate to the key. | +| 400 | Key must be used with an existent certificate | Check if the certificate on the key exist within the system. | +| 400 | Missing parameter api_id | Check if API_ID is missing. If so, fill in the api_ID field with the correct value. | +| 400 | OAuth client ID not found | Check if API_ID is missing. If so, fill in the api_ID field with the correct value. | +| 400 | OAuth client ID is empty | Check if OAuth client ID field is empty. If so, fill in with the correct client ID value. | +| 400 | OAuth is not enabled for this API | Check if OAuth is enabled for the API. | +| 400 | Policy access rights doesn’t contain API this OAuth client belongs to | Check if the policy rights contains the proper api_ID for the API. | +| 400 | Request apiID does not match that in Definition! For Update operations these must match | Attempted a PUT operation using different api_ID's. Make sure the api_ID's are the same. | +| 400 | Request field is missing | Check if the request field is missing. If so, fill in the request field. | +| 400 | Request ID does not match that in policy! For Update operations these must match | Attempted a PUT operation using different policy ID's. Make sure both policy ID's are the same. | +| 400 | Request is too large | The request body exceeds the configured size limit for the API endpoint. | +| 400 | Request with empty authorization header | Fill in authorization header for the request. | +| 400 | Spec field is missing | Attempted to trace a request but spec field is missing. Fill in the spec field. | +| 400 | The provided request is empty | Check if request in the GraphQL playground is correct. | +| 401 | Authorization Field Missing | Check if the authorization field is missing. Check if the OAuth authorization field is missing. | +| 401 | Header missing | Check if header field exist when making request. | +| 401 | Key has expired, please renew | Current key has expired. Please request for a new key. | +| 401 | OAuth Client Id Empty | Fill in the Client ID field. | +| 401 | OAuth Client Secret Empty | Client secret is empty. Insert the required client secret. | +| 401 | Request signature verification failed | Possible empty signature header or validation failed. | +| 401 | Wrong Password | Enter the correct password. Contact an administrator if further help is needed. | +| 403 | Access to this API has been disallowed | Request access to the API from an administrator. | +| 403 | Access to this resource has been disallowed | Request access to the resource from an administrator. | +| 403 | Attempted access with non-existent cert | Check if authentication certificate exist. | +| 403 | Attempted administrative access with invalid or missing key! | Check if there is correct security credentials of the Tyk API. | +| 403 | Certificate with SHA256 $certID not allowed | Certificate ID is nil or invalid. Please have a valid certificate. | +| 403 | Client authorize request in with invalid redirect URI | Check if Auth Redirect URI is malformed or use a valid redirect URI. | +| 403 | Client TLS certificate is required | Check if theres multiple APIs on the same domain with no certificates. | +| 403 | Certificate has expired | Please update the certificate with one that is currently valid and has not expired. | +| 403 | Depth limit exceeded | Exceeded the depth limit that has been applied. Check the key/policy global limits and quota section or the API limits and quota section. | +| 403 | Empty Signature Header | Fill in a signature for auth keys. | +| 403 | Empty Signature Path | Check if path for signature is empty. | +| 403 | Failed with 403 after $x-amount of requests over quota | Process request off thread with quota or process request live with rate limit or process request off thread with rate limit. | +| 403 | Found an empty user ID in predefined base claim user_id | Request with valid JWT/RSA or signature/empty user_id/sub claim, or signature/no base field or no sub or no id claim. | +| 403 | GraphQL Depth Limit Exceeded | Exceeded the depth limit that has been applied. Check the key/policy global limits and quota section or the API limits and quota section. | +| 403 | Invalid Token | Check if JWT token is valid and not malformed. | +| 403 | Invalid Signature Header | Insert correct signature header value. | +| 403 | Invalid Signature Path | Make sure signature path is correct and valid. | +| 403 | Key has expired, please renew | Create a new key. | +| 403 | Key not authorized: Unexpected signing method | Invalid JWT signature, JWT access with non-existent key. | +| 403 | Key not authorised: OAuth client access was revoked | Check if OAuth client exists. | +| 403 | Key not authorised: no matching policy | Request with invalid policy in JWT, or checking session and identity for valid key for openID. | +| 403 | No matching policy found in scope claim | Check if scope is wrong for JWT request. | +| 403 | Quota Exceeded | Quota limit has been exceeded. Check quota limit settings. | +| 403 | Run Go-plugin auth failed | Used an invalid token for authentication. Please use a valid token to authenticate. | +| 403 | This API version does not seem to exist | Attempted to extract version data from a request. Version does not exist when loading version data. | +| 403 | This organisation access has been disabled, please contact your API administrator | Organisation session is inactive. Contact API administrator. | +| 403 | This organisation quota has been exceeded, please contact your API administrator | Organisation's quota limit has been exceeded. Contact API administrator. | +| 403 | This organisation rate limit has been exceeded, please contact your API administrator | Organisation's rate limit has been exceeded. Contact API administrator. | +| 403 | TLS: bad certificate | Check if the certificates exist and have valid ID's. | +| 403 | Version Information not found | Checking version data from request. No default version has been set or found. | +| 404 | API not found | Checking if API exists when rotating OauthClient or if ApiSpec value is nil. | +| 404 | API for this refresh token not found | When invalidating OAuth refresh or if ApiSpec value is nil. | +| 404 | API ID not found | Check if API ID exists in the Gateway. | +| 404 | API not found | Check if API exists. | +| 404 | Bundle not found | No bundles found within the Gateway. | +| 404 | Certificate with given SHA256 fingerprint not found | No certificates exist in the certificate manager list. | +| 404 | Couldn't find organisation session in active API list | Attempted to update session object. However, spec for organisation is nil. Make sure to have the correct organisation ID. | +| 404 | Error getting oauth client | See if OAuth client id exists in the system. | +| 404 | Key not found | Failed to update hashed key. | +| 404 | No such organisation found in Active API list | Make sure organisation ID is correct. | +| 404 | OAuth client ID not found | Attempted to retrieve APIs for OAuth or client ID. Client ID was not found | +| 404 | OAuth client ID not found | Check if OAuth client ID exist in storage. Check if OAuth tokens or client details are valid. Failed to retrieve OAuth client list. Failed to revoke OAuth client list. | +| 404 | Org not found | Could not retrieve record of org ID or failed to delete org keys. Spec for org is nil, make sure orgID value is correct | +| 404 | Policy not found | Could not retrieve policy data. Make sure policy ID is correct. | +| 404 | There is no such key found | Check if key is already deleted. Check if hashed key has been deleted already. | +| 404 | Version Does Not Exist | Check if version path is filled and correct. | +| 405 | Malformed request body | Attempted a POST request with a malformed request body. Make sure the request body is valid. | +| 405 | Method not supported | Attempting to add a method that is not supported by our system. | +| 411 | Content length is required for this request | You need to provide the `Content-Length` field in the request header. | +| 429 | API Rate Limit Exceeded | Check the rate of the requests on the API level. Check the rate of requests on the API key (Auth token, certs, etc). | +| 499 | Client closed request | Check if the client closed the TCP connection | +| 500 | Cache invalidation failed | Attempted to scan or delete the cache, which failed, causing cache invalidation to fail. | +| 500 | Can't detect loop target | Verify target API exsists. Check if URL scheme is "tyk://". Refer to 404 errors | +| 500 | Could not write key data | Failed to update hashed key. Make sure key name is valid. | +| 500 | Delete failed | Attempted to delete policy with invalid filename. Attempted to delete API with invalid filename. Attempted to delete OAuth Client with incorrect OAuth client ID. | +| 500 | Due to enabled service policy source, please use the Dashboard API | Attempted to add/update a policy and rejected due to Policysource=service. Please use the Dashboard API. | +| 500 | Due to enabled use_dp_app_configs, please use Dashboard API | When trying to import OAS, when Dashboard config is set to true. Please use Dashboard API. | +| 500 | Error writing to key store | Attempted to update session with a new session. Make sure orgID is correct. | +| 500 | Failed to create file | When add/update policy, failed to create a file. Make sure the policy file path is correct | +| 500 | Failed to create key | Check if key already exist or if the key exists with a given certificate. Ensure security settings are correct | +| 500 | Failure in storing client data | Attempted to store data when creating a new OAuth client but failed. Make sure the storageID, or orgID is correct and valid. | +| 500 | Get client tokens failed | Failed to retrieve OAuth tokens. Make sure client ID is valid or keyName is valid. | +| 500 | Marshalling failed | Attempted to import printDef but failed. Marshalling of policy failed. Unmarshal object into the file failed when writing to file. | +| 500 | There was a problem proxying the request | Check if the target URL is unavailable to the Gateway. | +| 500 | Unmarshalling failed | Key creation failed. Failed to create OAuth client. Failed to update OAuth client. | +| 500 | Unsupported schema, unable to validate | Check if GraphQL schema is valid. | +| 500 | Upstream host lookup failed | Check if the target URL is not resolvable in DNS. | +| 503 | Service temporarily unavailable | Check if a circuit breaker middleware is enforced. | +| 503 | All hosts are down | Attempted to reverse proxy a URL rewrite to a scheme and host, but all the hosts in hostlist are down. | +| 504 | Upstream service reached hard timeout | Timeout awaiting response headers during a request round trip. | +| 507 | Status Insufficient Storage | Attempted to update an API through a POST request but failed to due insufficient storage. | + + +## Dashboard + +1. ##### Can't update policy. Please ensure at least one access rights setting is set + + **Description** + + Users receive this error when attempting to create a new Policy on the Dashboard. + + **Cause** + + The Access Rights field is a required setting for a policy. + + **Solution** + + Users should first [create a new API](/api-management/gateway-config-managing-classic#create-an-api) and then [create a new policy](/api-management/gateway-config-managing-classic#secure-an-api) with an existing API in the Access Rights. + +2. ##### Dashboard not showing any analytics data + + **Description** + + The user is unable to see analytics data from a particular time period in the Dashboard + + **Cause** + + Missing analytics data could be caused by a number of different reasons: + + * Gateway incorrectly configured + * Pump incorrectly configured + * Pump service not running + * Dashboard incorrectly configured + * MDCB incorrectly configured + * Browser caching stale data + + **Solution** + + **Gateway incorrectly configured** + + Ensure the Gateway `tyk.conf` has: + + * `enable_analytics` set to `true`. This sets the Gateway to record analytics data. + * `analytics_config.storage_expiration_time` set to a value larger than the Pump's `purge_delay`. This allows the analytics data to exist long enough in Redis to be processed by the Pump. + * `analytics_config.ignored_ips` set to `[]`. This ensures the Gateway will create analytics for requests from any IP address. + * `enforce_org_data_age` set to `false`. This prevents the data from being removed based on it reaching a certain age. + + **Pump incorrectly configured** + + Ensure the Pump `pump.conf` has: + + * `analytics_storage_type` set to `redis`. + * `analytics_storage_config` settings are set to the same Redis instance that the Gateway is connected to. + + **Pump service not running** + + Ensure the Pump service is running. + + **Dashboard incorrectly configured** + + Ensure the Dashboard `tyk_analytics.conf` has: + + * `mongo_url` set to the same MongoDB instance that the Pump is connected to. + + **MDCB incorrectly configured** + + For scenarios where MDCB is used, ensure the `sink.conf` has: + + * `analytics.mongo_url` set to the same MongoDB instance that the Dashboard is connected to. + * `forward_analytics_to_pump` set to the correct value for your solution. `false` if MDCB is directly recording the analytics itself, `true` if it is forwarding analytics data for the Pump to process. For the forwarding scenario, set the `storage` settings to the same Redis instance that the Pump is connected to. + + **Browser caching stale data** + + Try restarting your browser, or using a private session. + + You can also try restarting the Dashboard service. + + **Troubleshooting tip** + + Check if MongoDB contains analytics data by running the following query (but update the date parameter first): + + ```{.copyWrapper} + db.getCollection('tyk_analytics_aggregates').find({timestamp: {$gte: new ISODate("2016-09-26T23:59:00Z")}}) + ``` + + The query gets all aggregated analytics data from the date provided, so if you set it to yesterday you will get all data since yesterday. The data must be in the ISO format. + +3. ##### Fatal - Dashboard and portal domains cannot be the same + + **Description** + + The Tyk Dashboard service will not start and displays a fatal error as follows: + + ``` + FATAL Dashboard and portal domains cannot be the same. + Dashboard domain: tyk-dashboard.com, Portal domain: tyk-dashboard.com + ``` + + **Cause** + + Tyk's developer portal UI needs to run on either a different subdomain or different domain name to the dashboard UI. + + Tyk's Dashboard service may be run in a multi-tenant configuration, and each tenant may have their own developer portals. + + The Dashboard service determines which portal to load based on the `Host` header in the request by the browser. If this + conflicts with the hostname of the dashboard UI the dashboard service will not know whether to serve the dashboard or + developer portal. + + **Solution** + + Firstly, we will need to disable hostnames from within the Dashboard configuration file in order to get the dashboard + service started again. + + Change `host_config.enable_host_names` from `true` to `false` + ``` + "host_config": { + "enable_host_names": true, <------ CHANGE TO false + ... + ... + }, + ``` + + You should now be able to start the Dashboard service. + + Navigate to the Dashboard via it's public IP address and log-in. + + Change your portal domain to something different - e.g. `portal.tyk-dashboard.com` + + Edit the Dashboard configuration file to re-enable host names. + + Restart the Dashboard service. + +4. ##### Internal TIB SSO User unable to log in + + **Description** + + After creating an SSO Identity profile in the Tyk Dashboard, a user is unable to log in to the Dashboard or the Developer Portal + + **Cause** + + One potential cause is that the `DashboardCredential` setting has not been populated with the user's Tyk Dashboard API Access Credentials. + You can check this from: + + 1. From the Dashboard menu, select the Identity Management option + 2. Edit the profile you created + 3. Select the Raw Editor + 4. Check to see if the `DashboardCredential` setting is set + + DashboardCredentials + + + + **Workaround Solution** + + If, as above, the `DashboardCredential` setting is empty (`"DashboardCredential": ""`), you can manually add the user's Tyk Dashboard API Access Credentials by performing the following: + + 1. From the System Management > Users menu, select Actions > Edit from the user whose credentials you want to use + 2. Copy the **Tyk Dashboard API Access Credentials** value + + User API Access Credentials + + 3. Paste this into the Raw editor for the `DashboardCredential` setting. For example - `"DashboardCredential": "887dad0de40b4ff05b6b50739b311099"` + 4. Click **Update** + 5. The user should now be able to log in to the Dashboard/Portal + + + + + This issue is due to be fixed in an up coming release + + + +5. ##### Key object validation failed, most likely malformed input error + + **Description** + + The user is getting error as `Key object validation failed, most likely malformed input` when calling the Dashboard API. + + **Cause** + + Issue caused by invalid character passed in the JSON body of the request. + + **Solution** + + Validate the JSON using JSON validator. + + Further, please see [this community forum post](https://community.tyk.io/t/error-creating-new-api-through-dashboard-rest-api/1555/2) for additional guidance. + +6. ##### Port 5000 Errors in the Browser Console + + > **NOTE**: Port 5000 is no longer required from v2.9.3. + + **Description** + + You see a lot of `net::ERR_CONNECTION_REFUSED` errors in the browser console. + + **Cause** + + The Dashboard is trying to connect to `https://:5000/socket.io/?chan=ui_notifications` and you don't have port 5000 open. + + **Solution** + + Port 5000 is used for WebSocket connections for real-time Dashboard notifications. You can change the port by changing the default `notifications_listen_port` in your `tyk_analytics.conf`. Otherwise you can ignore the errors in the browser console. + + + +Port 5000 is only required if you need to enable the Tyk Gateway log viewer. + + + +7. ##### There was a problem updating your CNAME“ error in the Dashboard + + **Description** + + A user may find that they are unable to update a CNAME from within the Dashboard. The following error will appear in a pop-up: + + ``` + There was a problem updating your CNAME, please contact support + ``` + + **Cause** + + The UI for setting the domain name has a very strict validation, so it may just be rejecting this domain. + + **Solution** + + The best way to set the domain is to use the Tyk Dashboard Admin API, to obtain the organization object via a GET request and then update the object using a PUT request with the relevant CNAME added to the body of the request.[[1](https://tyk.io/docs/api-reference/organisations/list-all-organisations)] Restarting the process will then set the domain. + +8. ##### runtime error invalid memory address or nil pointer dereference + + **Description** + + When attempting to POST an OAuth Client to a newly generated API, user may receive the following stack trace: + + ``` + 2016/12/08 08:06:16 http: panic serving 172.18.0.4:46304: runtime error: invalid memory address or nil pointer dereference + goroutine 364279 [running]: + net/http.(*conn).serve.func1(0xc420569500) + panic(0xb0e780, 0xc420014040) + /usr/local/go/src/runtime/panic.go:458 +0x243 + main.createOauthClient(0xf58260, 0xc4203a41a0, 0xc4206764b0) + /home/tyk/go/src/github.com/lonelycode/tyk/api.go:1526 +0x64a + main.CheckIsAPIOwner.func1(0xf58260, 0xc4203a41a0, 0xc4206764b0) + /home/tyk/go/src/github.com/lonelycode/tyk/middleware_api_security_handler.go:24 +0x2ae + net/http.HandlerFunc.ServeHTTP(0xc420533e50, 0xf58260, 0xc4203a41a0, 0xc4206764b0) + /usr/local/go/src/net/http/server.go:1726 +0x44 + github.com/gorilla/mux.(*Router).ServeHTTP(0xc42061cdc0, 0xf58260, 0xc4203a41a0, 0xc4206764b0) + /home/tyk/go/src/github.com/gorilla/mux/mux.go:98 +0x255 + net/http.(*ServeMux).ServeHTTP(0xc420667290, 0xf58260, 0xc4203a41a0, 0xc4206764b0) + /usr/local/go/src/net/http/server.go:2022 +0x7f + net/http.serverHandler.ServeHTTP(0xc42000fc80, 0xf58260, 0xc4203a41a0, 0xc4206764b0) + /usr/local/go/src/net/http/server.go:2202 +0x7d + net/http.(*conn).serve(0xc420569500, 0xf58d20, 0xc42068bdc0) + /usr/local/go/src/net/http/server.go:1579 +0x4b7 + created by net/http.(*Server).Serve + /usr/local/go/src/net/http/server.go:2293 +0x44d + ``` + + **Cause** + + The API that the OAuth Client has been POSTed to either doesn't exist or hasn't had a chance to propagate throughout the system. + + **Solution** + + When creating a new OAuth Client, make sure that API it is created under exists. If the API was created recently, please wait a few minutes before attempting to create an OAuth Client under it. + +9. ##### ValueError No JSON object could be decoded" when running Dashboard Bootstrap script + + **Description** + + Users receive the following error message when attempting to run the bootstrap script in their Tyk instance: + + ``` + Traceback (most recent call last): + File """", line 1, in + File ""/usr/lib64/python2.7/json/__init__.py"", line 290, in load + **kw) + File ""/usr/lib64/python2.7/json/__init__.py"", line 338, in loads + return _default_decoder.decode(s) + File ""/usr/lib64/python2.7/json/decoder.py"", line 365, in decode + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + File ""/usr/lib64/python2.7/json/decoder.py"", line 383, in raw_decode + raise ValueError(""No JSON object could be decoded"") + ValueError: No JSON object could be decoded + ORGID: + Adding new user + Traceback (most recent call last): + File """", line 1, in + File ""/usr/lib64/python2.7/json/__init__.py"", line 290, in load + **kw) + File ""/usr/lib64/python2.7/json/__init__.py"", line 338, in loads + return _default_decoder.decode(s) + File ""/usr/lib64/python2.7/json/decoder.py"", line 365, in decode + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + File ""/usr/lib64/python2.7/json/decoder.py"", line 383, in raw_decode + raise ValueError(""No JSON object could be decoded"") + ValueError: No JSON object could be decoded + USER AUTH: + Traceback (most recent call last): + File """", line 1, in + File ""/usr/lib64/python2.7/json/__init__.py"", line 290, in load + **kw) + File ""/usr/lib64/python2.7/json/__init__.py"", line 338, in loads + return _default_decoder.decode(s) + File ""/usr/lib64/python2.7/json/decoder.py"", line 365, in decode + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + File ""/usr/lib64/python2.7/json/decoder.py"", line 383, in raw_decode + raise ValueError(""No JSON object could be decoded"") + ValueError: No JSON object could be decoded + NEW ID: + Setting password + DONE" + ``` + + **Cause** + + The bootstrap script requires a valid hostname and port number to generate a new login user. + + **Solution** + + Make sure that the correct hostname and port number used to run the bootstrap.sh script. An example command would be: `./bootstrap.sh new-tyk-instance.com:3000` + +10. ##### Dashboard bootstrap error + + Make sure you: + + Target the correct domain with your `bootstrap.sh` as it is very specific once you set up a Dashboard service with hostname set + + ```{.copyWrapper} + ./bootstrap.sh my-tyk-instance.com + + ``` + + Have checked the firewall rules in your instance and VPC to allow + port 3000 access. + +11. ##### How to find the policy ID for a created policy + + Open the Active Policies page in the Dashboard (System Management > Policies) and click **Edit** next to the name of the policy you've created. The policy ID should appear in the URL of the edit page that opens up. + +12. ##### How to Connect to DocumentDB with X.509 client cert + + As AWS DocumentDB runs with TLS enabled, we require a way to run it without disabling the TLS verification. + DocumentDB uses self-signed certs for verification, and provides a bundle with root certificates for this purpose, so we need a way to load this bundle. + + Additionally DocumentDB can't be exposed to the local machine outside of the Amazon Virtual Private Cloud (VPC), which means that even if verification is turned on, it will always fail since if we use a SSH tunnel or a similar method, the domain will differ from the original. Also, it can have [Mutual TLS](/api-management/implement-tls#secure-hosted-apis-with-mtls) enabled. + + So, in order to support it, we provide the following variables for both our [Tyk Analytics Dashboard](/tyk-dashboard/configuration) and [Tyk Pump](/api-management/tyk-pump#configuring-tyk-pump): + + * `mongo_ssl_ca_file` - path to the PEM file with trusted root certificates + * `mongo_ssl_pem_keyfile` - path to the PEM file which contains both client certificate and private key. This is required for Mutual TLS. + * `mongo_ssl_allow_invalid_hostnames` - ignore hostname check when it differs from the original (for example with SSH tunneling). The rest of the TLS verification will still be performed. + + + A working DocumentDB configuration looks like this (assuming that there is SSH tunnel, proxying to 27018 port). + + ```{.json} + "mongo_url": "mongodb://testest:testtest@127.0.0.1:27018/tyk_analytics?connect=direct", + "mongo_use_ssl": true, + "mongo_ssl_insecure_skip_verify": false, + "mongo_ssl_ca_file": "/rds-combined-ca-bundle.pem", + "mongo_ssl_allow_invalid_hostnames": true, + ``` + + **Capped Collections** + + If you are using DocumentDB, [capped collections](/api-management/dashboard-analytics/analytics-storage-management) are not supported. See [here](https://docs.aws.amazon.com/documentdb/latest/developerguide/mongo-apis.html) for more details. + +13. ##### How to disable an API + + You will need to GET the API from the Dashboard, then set `active` property to `false`, then PUT it back. + See [Dashboard API - API Definitions](https://tyk.io/docs/api-reference/apis/get-list-of-apis) for more details on how to GET and PUT an API definition. + +14. ##### How to Setup CORS + + **Upstream service supports CORS** + If your upstream service supports CORS already then Tyk should ignore **OPTIONS** methods as these are pre-flights sent by the browser. In order to do that you should select **Options passthrough**, and **NOT CHECK** CORS in Tyk. + + - If you not do allow **OPTIONS** to pass through, it will cause Tyk to dump the options request upstream and reply with the service's response so you'll get an error similar to `no 'access-control-allow-origin' header is present on the requested resource`. + + - If you check **CORS** as well you'll get an error similar to this: + ``` + Failed to load https://ORG_NAME.cloud.tyk.io/YOUR_API: The 'Access-Control-Allow-Origin' header + contains multiple values 'http://UPSTREAM', but only one is allowed. Origin 'http://UPSTREAM' + is therefore not allowed access. Have the server send the header with a valid value, or, if an + opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with + CORS disabled + ``` + This is because you have enabled CORS on the Api Definition and the upstream **also** supports CORS and so both add the header. + + + **Upstream does not handle CORS** + If your upstream does not handle CORS, you should let Tyk manage all CORS related headers and responses. In order to do that you should **enable CORS** in Tyk and **NOT ENABLE** Options pass through. + + To learn more, look for `CORS.options_passthrough` [here](/api-management/gateway-config-tyk-classic#cross-origin-resource-sharing-cors). + + + **CORS middleware is allowing headers which I did not allow** + This may be the case when you enable CORS but don't provide any headers explicitly (basically providing an empty array). In this case the CORS middleware will use some sensible defaults. + To allow all headers, you will need to provide `*` (although this is not recommended). + + The same can happen with Allowed Origins and Allowed Methods. Read more about it [here](/api-management/gateway-config-tyk-classic#cross-origin-resource-sharing-cors). + + **CORS middleware is blocking my authenticated request** + Please make sure that you did allow the authorization header name (e.g. `Authorization`) or else the request will be blocked by the CORS middleware. If you're having trouble on the developer portal with authenticated requests make sure to also allow the `Content-Type` header. + +15. ##### No Key information on the Dashboard + + Information relating to a given key doesn't automatically appear in the Dashboard for users who have switched from a Self-Managed installation to a Multi-Cloud setup. + + The stats for a key will never update in the Cloud for a Multi-Cloud installation. The Dashboard in this mode only sets the initial “master” values for a key and those keys are then propagated across the Multi-Cloud instances that are using them (for example, you may have multiple zones with independent Redis DBs) at which point they diverge from each other. + + To see the up to date stats for a token, the key must be queried via the Gateway API. + +16. ##### How to rename or move existing headers in a request + + To rename a header, or to move a value from one header to another (for example, moving an authentication token to a secondary place, or copying a value that gets replaced upstream) is easy with [context variables](/api-management/traffic-transformation/request-context-variables). Here is an example where we move the value of `X-Custom-Header` to a new header called `X-New-Custom-Header` in all requests. + + We do this by setting the following in our API Definition Version section: + ```{.copyWrapper} + "global_headers": { + "X-New-Custom-Header": "$tyk_context.headers_X_Custom_Header" + }, + "global_headers_remove": ["X-Custom-Header"], + ``` + + You can test the header with the following command. This assumes your API Authentication mode is set to open(keyless): + + ```{.copyWrapper} + curl -X GET \ + https://DOMAIN/LISTEN_PATH/get \ + -H 'content-type: application/json' \ + -H 'x-custom-header: Foo' \ + ``` + + + You can also do this via the Dashboard from the Endpoint Designer tab within the API Designer: + + rename header + +17. ##### How to run the Dashboard and portal on different ports + + Unfortunately its not possible to run the Dashboard and Portal on different ports, they must use the same port. + +18. ##### How to run two Gateways with docker-compose + + Managing a second Tyk Gateway with our [Tyk Install](/tyk-self-managed/install/docker) is a case of mounting the `tyk.conf` file into a new volume and declaring a new Gateway service but exposed on a different port. + You will need to make some minor modifications to `docker-compose.yml` and start your services as usual with `docker-compose up`. + + + + + This will only work with an appropriate license. The free license is for development purposes and would allow running Tyk's licensed platform with only one Gateway. If you want to test Tyk with more please contact us by email [info@tyk.io](mailto:info@tyk.io) and we will be happy to discuss your case and PoC requirements as well as providing a short period license. + + + + + **Add the following to `docker-compose.yml` (after the `tyk-gateway` definition)** + + ``` + tyk-gateway2: + image: tykio/tyk-gateway:latest + ports: + - "8081:8080" + networks: + - tyk + depends_on: + - tyk-redis + volumes: + ./confs/tyk.conf:/opt/tyk-gateway/tyk.conf + ``` + +19. ##### “Payload signature is invalid!“ error + + **Description** + + Users receive the error "Payload signature is invalid!” in their logs. + + **Cause** + + Users may not have enabled payload signatures in their settings after an upgrade. + + **Solution** + + See [System Payloads](/api-management/security-best-practices#sign-payloads) for more details. + +## Pump + +1. ##### Capturing detailed logs + + If you've seen the documentation for Tyk Dashboard's [log browser](/api-management/dashboard-analytics#activity-logs), then you'll also be wondering how to set up your Tyk configuration to enable detailed request logging. + + **What is detailed request logging?** + + When [detailed request logging](/api-management/logs/traffic-logs#detailed-recording) is enabled, Tyk will record the request and response in wire-format in the analytics database. This can be very useful when trying to debug API requests to see what went wrong for a user or client. + + This mode is configured in the gateway and can be enabled at the [system](/api-management/logs/traffic-logs#gateway-level), [API](/api-management/logs/traffic-logs#api-level) or [access key](/api-management/logs/traffic-logs#key-level) level. + + You will also need your Tyk Pump configured to move data into your preferred data store. + + **Disabling detailed recording for a particular pump** + + In some cases, you don't want to send the detailed request and response to a particular data store. + In order to do that, you can set `omit_detailed_recording` in your Tyk Pump configuration file to `true`. This will disable the detailed logging for a specific pump. + + For example, if we have an ElasticSearch, Kafka and CSV stores, and you want to save the detailed recording in all of them except Kafka you can use the following configuration: + + Enable detailed analytics on the Gateway `tyk.conf` using: + ```{.copyWrapper} + "enable_analytics" : true, + "analytics_config": { + "enable_detailed_recording": true + } + ``` + - Configure each pump on `pump.conf`. + - Add the `omit_detailed_recording` variable to the Kafka pump: + ```{.copyWrapper} + "pumps": { + "kafka": { + "type": "kafka", + "omit_detailed_recording":"true" + "meta": { + ... + } + }, + ... + }, + ``` + +2. ##### Connection dropped, connecting... + + **Description** + + Users may notice the following message in their logs for the Tyk Pump: + + ``` + [Jun 3 22:48:02] INFO elasticsearch-pump: Elasticsearch Index: tyk_analytics + [Jun 3 22:48:02] INFO main: Init Pump: Elasticsearch Pump + [Jun 3 22:48:02] INFO main: Starting purge loop @10(s) + [Jun 3 22:48:12] WARN redis: Connection dropped, connecting.. + [Jun 3 22:48:23] INFO elasticsearch-pump: Writing 1386 records + [Jun 3 22:50:11] INFO elasticsearch-pump: Writing 13956 records + ``` + + **Cause** + + This is normal behavior for the Tyk Pump. + + **Solution** + + N/A + +3. ##### Data Seen in Log Browser but No Reports + + **Description** + + You can see data in the log browser but the rest of the reports display nothing. + + **Solution** + + If your Pump is configured to use `mongo_selective_pump` (e.g. store data in a collection per organization), ensure that the [Dashboard configuration setting](/tyk-dashboard/configuration) `use_sharded_analytics` is set to `true`. + + The same applies in the reverse direction. If you are using `mongo-pump-aggregate` in your [pump configuration](/api-management/tyk-pump#configuring-tyk-pump), set `use_sharded_analytics` to false. + + This is because you've enabled `use_sharded_analytics` as per above and you're using the `mongo-pump-aggregate`, but you now also have to add a `mongo-pump-selective` in order to save individual requests to Mongo, which the Dashboard can read into the Log Browser. + +4. ##### No Elasticsearch node available + + **Description** + + Tyk Pump is configured to use Elasticsearch, but it does not work and shows `no Elasticsearch node available` message in log. + + ``` + tyk-pump[68354]: time="Aug 30 15:19:36" level=error msg="Elasticsearch connection failed: no Elasticsearch node available" + ``` + + **Cause** + + The `elasticsearch_url` configuration property in the `pump.conf` is missing the HTTP prefix e.g. + + ``` + "elasticsearch_url": "127.0.0.1:9200" + ``` + + **Solution** + + Ensure the HTTP prefix is present in the `elasticsearch_url` configuration property e.g. + + ``` + "elasticsearch_url": "http://127.0.0.1:9200" + ``` + +5. ##### Tyk Pump Panic “stack exceeds 1000000000-byte limit“ + + **Description** + + Users receive a the aforementioned error message in a stack trace in the Pump. + + **Cause** + + Users receive a the aforementioned error message in a stack trace in the Pump. + + **Solution** + + Users are advised to upgrade to the latest version of Tyk. They must also ensure that their Pump is configured with a `purge_delay` and an `optimisation_max_active` value that's greater than 0. Packages are available to download from [Packagecloud.io](https://packagecloud.io/tyk) and further details on how to upgrade can be found [here](/developer-support/upgrading) + +6. ##### Pump overloaded + + **Description** + + The Tyk Pump cannot deal with amount of analytics data generated by the Gateway. This means the Pump is unable to process all the analytics data within the purge period. + + **Cause** + + If there is excessive analytics data, the pump may become overwhelmed and not able to move the data from Redis to the target data store. + + **Solution** + + There are many ways to approach solving this problem. + + **Scale the Pump** + + Scale the Pump by either increasing the CPU capacity of the Pump host or by adding more Pump instances. + + By adding more instances you are spreading the load of processing analytics records across multiple hosts, which will increase processing capacity. + + **Disable detailed analytics recording** + + Set `analytics_config.enable_detailed_recording` to `false` in the Gateway configuration file `tyk.conf`. Detailed analytics records contain much more data and are more expensive to process, by disabling detailed analytics recording the Pump will be able to process higher volumes of data. + + **Reduce the Pump purge delay** + + Set `purge_delay` to a low value e.g. `1` in the Pump configuration file `pump.conf`. This value is the number of seconds the Pump waits between checking for analytics data. Setting it to a low value will prevent the analytics data set from growing too large as the pump will purge the records more frequently. + + **Reduce analytics record expiry time** + + Set `analytics_config.storage_expiration_time` to a low value e.g. `5` in the Gateway configuration file `tyk.conf`. This value is the number of seconds beyond which analytics records will be deleted from the database. The value must be higher than the `purge_delay` set for the Pump. This will allow for analytics records to be discarded in the scenario that the system is becoming overwhelmed. Note that this results in analytics record loss, but will help prevent degraded system performance. + +## Streams + +1. ##### Failure to connect to the event broker + + If Tyk Gateway is unable to establish a connection to the configured event broker (e.g., Kafka, MQTT), check the following: + - Verify that the broker connection details in the Tyk Dashboard are correct, including the hostname, port, and any required credentials. + - Ensure that the event broker is running and accessible from the Tyk Gateway instance. + - Check the network connectivity between the Tyk Gateway and the event broker. Use tools like telnet or nc to validate the connection. + +2. ##### Messages are not being published or consumed + + If messages are not being successfully published to or consumed from the event broker, consider the following: + - Verify that the topic or queue names are correctly configured in the Tyk Dashboard and match the expected values in the event broker. + - Check the Tyk Gateway logs for any error messages related to message publishing or consumption. Adjust the log level to "debug" for more detailed information. + - Validate that the message format and schema match the expectations of the consumer or producer. Inspect the message payloads and ensure compatibility. + +3. ##### Async API performance is poor or connections are being throttled + + If you observe performance issues or connection throttling with async APIs, consider the following: + - Review the configured rate limits and quotas for the async API. Adjust the limits if necessary to accommodate the expected traffic. + - Monitor the resource utilization of the Tyk Gateway instances and the event broker. Ensure that there is sufficient capacity to handle the load. + - Consider scaling the Tyk Gateway horizontally by adding more instances to distribute the traffic load. + +4. ##### What are best practices of using Tyk Streams + + - Use meaningful and descriptive names for your async APIs, topics, and subscriptions to improve readability and maintainability. + - Implement proper security measures, such as authentication and authorization, to protect your async APIs and restrict access to authorized clients only. + - Set appropriate rate limits and quotas to prevent abuse and ensure fair usage of the async API resources. + - Monitor the performance and health of your async APIs using Tyk's built-in analytics and monitoring capabilities. Set up alerts and notifications for critical events. + - Version your async APIs to manage compatibility and enable seamless updates without disrupting existing clients. + - Provide comprehensive documentation for your async APIs, including details on message formats, schemas and example payloads, to assist developers in integrating with your APIs effectively. + + +## Debugging Series + +### MongoDB + +Tyk uses Mongo as a database to store much of its analytical data. This means if you have a dashboard instance that is down, there’s a high chance that this is because of either Mongo being down or an issue with your dashboard connecting to Mongo. + +Here, we'll outline the following: + + - How to isolate Mongo as the root of the error + - The steps to take to help stop your system from going down. + +1. ##### Isolating Mongo as the fault + + Here are a few ways to identify Mongo as the source of the problem: + + 1. Analytics is not showing up on the dashboard + 2. When hitting the `/hello` endpoint, the dashboard is down + 3. The Mongo database size is hitting hardware resource limits. + +2. ##### Mongo status + + Similarly to Tyk, Mongo has a health check that we can run to get the status of our Mongo instance. This should be a starting point for debugging Mongo (depending on which system): + + - `Sudo systemctl status mongod` or `sudo service mongodb status` + - Logs under `/var/log/mongo/mongo.log` should also outline any outage + +3. ##### Mongo version + + Does Tyk support the version of Mongo that you’re using? Read more about that [here](/planning-for-production/database-settings#mongodb). + +4. ##### Capped collections + + Suppose a Mongo instance runs over a long period in addition to a lot of traffic in a Tyk system. In that case, the chances of the collections growing out of control are very real - especially the `tyk_analytics` collections. + + In some cases, `enable_detailed_logging: true` adds fuel to the fire, as this parameter should only be set temporarily during debugging. This configuration exists on the gateway and the API levels, so ensure this is off after debugging. + + We advise everyone to cap every collection in Mongo, as this prevents collections from growing out of control and bringing your dashboard down by hitting resource limits. + + You can determine each collection's cap size by visiting our [MongoDB sizing calculator](/planning-for-production/database-settings#mongodb-sizing-guidelines). + + Here’s more information on how and why you want to [cap your collections](https://www.mongodb.com/docs/manual/core/capped-collections/). + +5. ##### Size caps versus TTL-capped collections + + Are you trying to decide between capping your collections or by size? It depends on a couple of factors. Ultimately, both settings will get rid of older data, so it’s based on how far back you need to view it. + + Assuming you only need data for a few days, then using a TTL will be the best route, as it will only allow your collections to grow that wild over a short period. + + Alternatively, if you care about how big the collections grow and want to see longer-lived data, then capping by size is your best direction. This will limit the collection to developing within a controlled resource limit. And in the context of aggregate analytics, this collection will hold data for long periods. + + One thing to note here is that if you head down the TTL route, and if your environment has A LOT of traffic, then your collections can grow wild and fast, while a size-capped collection will always stay within a known size limit. + +6. ##### Handling overgrown, uncapped collections + + There are three ways to do this: + + 1. The first method is to delete (drop) the collection and create a new collection with a cap (commands below). + + ```bash + # This will drop a collection. When using this, cached data will not be deleted. + db..drop() + ``` + + ```bash + # Can use the below call. Drops the collection and removes any cache data + db..remove() + ``` + + 2. The second method is to rename the collection to a random name and then create a new collection with a cap. Then restart Mongo with a larger size (we do this because the overgrown collections still exist). This is to confirm that the collection size grew too large and dropped the Mongo connection. The renaming also helps conserve the existing data if you still need it (but it will be useless in the background unless you attempt the third method). + + 3. The third method is to delete (deleteMany() call below) the old data to trim down their collection size. Then, you can restart your instance to see if the connection goes up again. + + ```bash + # Will delete data off a collection that does NOT have a cap. Otherwise, it will throw an error. + db..deleteMany() + ``` + +7. ##### Secure Mongo connection + + You will use a secured connection to your Mongo instance in most production cases. Here are a few things to consider: + + - Verify there isn’t a network issue that stops your dashboard from connecting to Mongo. You can do this by hitting the dashboard server from your Mongo server (or vice versa) + + - Validate certificate and `.pem` files + + - Connect (command below) to Mongo with certificates + + ```bash + # Replace the above files with the correct parameters (proper file paths and host). + mongo --ssl --sslCAFile /opt/mongodb/ssl/ca.pem --sslPEMKeyFile /opt/mongodb/ssl/mongodb.pem --host 127.0.0.1 + ``` + - Verify Pump has the correct parameters to include your certificates + + - Verify your dashboard has the correct parameters relative to your environment: + + ```json + "mongo_url": "mongodb://localhost/tyk_analytics", + "mongo_use_ssl": true, + "mongo_ssl_ca_file": "/opt/mongodb/ssl/ca.pem", + "mongo_ssl_pem_keyfile": "/opt/mongodb/ssl/mongodb.pem", + "mongo_ssl_insecure_skip_verify": true + ``` + +8. ##### How to Cap analytics data storage + + What methods are available to enable me to manage my MongoDB analytics storage? + + [TTL Indexes](/api-management/dashboard-analytics/analytics-storage-management#ttl-indexes) + + [Capped Collections](/api-management/dashboard-analytics/analytics-storage-management#capped-collections) + + + + + Time based caps (TTL indexes) are incompatible with already configured size based caps. + + + + + +If you are using DocumentDB, capped collections are not supported. See [here](https://docs.aws.amazon.com/documentdb/latest/developerguide/mongo-apis.html) for more details. + + + +9. ##### MongoDB X.509 Client Authentication + + You can use the *MongoDB X509 Certificate* flow to authenticate the *Tyk Dashboard*, *Tyk Pump*, and *Tyk MDCB* with your *MongoDB* install. This is slightly different from AWS DocumentDB setup instructions. + + Before we get into the configuration, we need to understand the two key components: connection strings and certificates. + + 1. **Connection Strings** + + 1) You must specify a username (and password if needed) in the connection string. [Why do you need a username at all?](https://docs.mongodb.com/manual/tutorial/configure-x509-client-authentication/) + + 2) We must specify the following parameters: `?authSource=$external&authMechanism=MONGODB-X509"` + + **An example of a connection string would be:** + + ```bash + "mongodb://CN=tyk-mongo-client,OU=TykTest@:/?authSource=$external&authMechanism=MONGODB-X509" + ``` + + ##### Passwords + If you have to include a password, you can do it after the username in basic auth format: + + ```bash + "mongodb://CN=tyk-mongo-client,OU=TykTest,O=TykTest:mypassword@:/?authSource=$external&authMechanism=MONGODB-X509" + ``` + + ##### URL Encoding Protected Characters + Note that you must URL encode the `:` character into `%40`. So replace any `:` in the username field into the URL encoded version. + + 2. **Certificates** + + You'll need to provide two certificates to complete the X509 Client Authentication: + + **CA Cert** containing just the public key of the Certificate Authority (CA). + + **Client Cert** containing both the public and private keys of the client. + + ##### Configuration + + Here's what it looks like all put together: + + 1. **Tyk Dashboard** + + Your `tyk_analytics.conf` should include these fields at the root level: + + ```json + { + ... + "mongo_url": "mongodb://@:/?authSource=$external&authMechanism=MONGODB-X509", + "mongo_use_ssl": true, + "mongo_ssl_ca_file": "ca.pem", + "mongo_ssl_pem_keyfile": "client.pem" + } + ``` + + | Config File | Environment Variable | Type | Examples + | --- | -- | ---- | ---- | + | "mongo_url" | TYK_DB_MONGOURL | string | "mongodb://{username}@{host}:{port}/{db}?authSource=$external&authMechanism=MONGODB-X509" | + | "mongo_use_ssl" | TYK_DB_MONGOUSESSL | bool | true, false | + | "mongo_ssl_ca_file" | TYK_DB_MONGOSSLCAFILE | string | "certificates/ca.pem" | + | "mongo_ssl_pem_keyfile" | TYK_DB_MONGOSSLPEMKEYFILE | string | "certificates/key.pem" | + | "mongo_ssl_insecure_skip_verify" | TYK_DB_MONGOSSLINSECURESKIPVERIFY | bool | true, false | + | "mongo_ssl_allow_invalid_hostnames" | TYK_DB_MONGOSSLALLOWINVALIDHOSTNAMES | bool | true, false | + | "mongo_session_consistency" | TYK_DB_MONGOSESSIONCONSISTENCY | string | "strong", "eventual", or "monotonic". default is "strong" | + | "mongo_batch_size" | TYK_DB_MONGOBATCHSIZE | int | Default "2000", min "100" | + + 2. **Tyk Pump** + + Tyk offers three different MongoDB pumps (`mongo`, `mongo_aggregate`, and `mongo_selective`), each of which must be separately configured for X509 certificate authentication. + + The following fields must be set under the `meta` section of each pump (or set as environment variable): + + ```yaml + { + ... + "pumps": { + "mongo": { + "type": "mongo", + "meta": { + "collection_name": "tyk_analytics", + "mongo_url": "mongodb://CN=tyk-mongo-client,OU=TykTest@:/?authSource=$external&authMechanism=MONGODB-X509", + "mongo_use_ssl": true, + "mongo_ssl_ca_file": "ca.pem", + "mongo_ssl_pem_keyfile": "client.pem" + } + } + } + } + ``` + + In addition to the other configs, these are the ones related to MongoDB: + + | Config File | Type | Examples + | -- | -- | -- + "mongo_url" | string | "mongodb://{username}@{host}:{port}/{db}?authSource=$external&authMechanism=MONGODB-X509" | + "mongo_use_ssl" | bool | true, false | + "mongo_ssl_ca_file" | string | "certificates/ca.pem" | + “mongo_ssl_pem_keyfile" | string | "certificates/key.pem" | + "mongo_ssl_insecure_skip_verify" | bool | true, false | + "mongo_ssl_allow_invalid_hostnames" | bool | true, false | + + 3. **Tyk MDCB** + + As of Tyk MDCB v1.8.0, you have been able to secure Tyk MDCB with MongoDB using X509 Certificate Authentication flow. + + The config settings are exactly the same as the Tyk Dashboard steps, just nested one level deeper: + + **Example Config:** + ```json + { + ... + "analytics": { + "mongo_url": "mongodb://CN=tyk-mongo-client,OU=TykTest@:/?authSource=$external&authMechanism=MONGODB-X509", + "mongo_use_ssl": true, + "mongo_ssl_ca_file": "ca.pem", + "mongo_ssl_pem_keyfile": "client.pem" + } + } + ``` + | Config File | Environment Variable | Type | Examples + | --- | -- | ---- | ---- | + "analytics.mongo_url" | TYK_MDCB_ANALYTICSCONFIG_MONGOURL | string | "mongodb://{username}@{host}:{port}/{db}?authSource=$external&authMechanism=MONGODB-X509" + "analytics.mongo_use_ssl" | TYK_MDCB_ANALYTICSCONFIG_MONGOUSESSL | bool | true, false | + "analytics.mongo_ssl_ca_file" | TYK_MDCB_ANALYTICSCONFIG_MONGOSSLCAFILE | string | "certificates/ca.pem" | + "analytics.mongo_ssl_pem_keyfile" | TYK_MDCB_ANALYTICSCONFIG_MONGOSSLPEMKEYFILE | string | "certificates/key.pem" | + "analytics.mongo_ssl_insecure_skip_verify" | TYK_MDCB_ANALYTICSCONFIG_MONGOSSLINSECURESKIPVERIFY | bool | true, false | + "analytics.mongo_ssl_allow_invalid_hostnames" | TYK_MDCB_ANALYTICSCONFIG_MONGOSSLALLOWINVALIDHOSTNAMES | bool | true, false | + "analytics.mongo_session_consistency" | TYK_MDCB_ANALYTICSCONFIG_MONGOSESSIONCONSISTENCY | string | "strong", "eventual", or "monotonic". default is "strong" | + "analytics.mongo_batch_size" | TYK_MDCB_ANALYTICSCONFIG_MONGOBATCHSIZE | int | Default "2000", min "100" | + +### Tyk Self-Managed + +This guide should help a user of Tyk Self-Managed in debugging common issues. A helpful way to go about this is by: + +1. Isolating your components to see where the error is coming from +2. Enabling debug logs to ensure you get all the information you need + +1. ##### Gateway `/hello` endpoint + + Querying the gateway's `/hello` health endpoint is the quickest way to determine the status of your Tyk instance. You can find more information in our docs about the [Gateway Liveness health check](/planning-for-production/ensure-high-availability/health-check). + + This endpoint is important as it allows the user to isolate the problem's origin. At a glance, the `/hello` endpoint reports the Gateways connectivity to Redis, and the control plane components eg. Tyk Dashboard, Tyk Multi-Data Center Bridge (MDCB), and Tyk Cloud. + + ```json + { + "status": "pass", + "version": "v5.0", + "description": "Tyk GW", + "details":{ + "dashboard":{ + "status": "pass", + "componentType": "system", + "time": "2023-01-13T14:45:00Z" + }, + "redis":{ + "status": "pass", + "componentType": "datastore", + "time": "2023-01-13T14:45:00Z" + } + }, + "rpc": { + "status": "pass", + "componentType": "system", + "time": "2023-01-13T14:45:00Z" + } + } + ``` + + If the Dashboard or RPC connectivity fails (control plane components), the Gateway will still function based on the last received configurations from those components. However, if Redis fails, Gateway will go down since it is a hard dependency. + +#### Debug Logs + +Setting the log level to debug will allow for more descriptive logs that will give a better context around any issue you might be facing. For example, here are the different outputs you receive when calling an Open Keyless API with `info` and `debug` log-level modes. + +Here is the output when using `info` as the log level: + +```bash +tyk-pump | time="Jan 24 14:39:19" level=info msg="Purged 1 records..." prefix=mongo-pump +tyk-pump | time="Jan 24 14:39:19" level=info msg="Purged 1 records..." prefix=mongo-pump-selective +tyk-mongo | 2023-01-24T14:39:19.228+0000 I NETWORK [listener] connection accepted from 172.20.0.2:51028 #19 (19 connections now open) +tyk-pump | time="Jan 24 14:39:19" level=info msg="Completed upserting" collection="tyk_analytics_aggregates" prefix=mongo-pump-aggregate +tyk-pump | time="Jan 24 14:39:19" level=info msg="Purged 1 records..." prefix=mongo-pump-aggregate +``` + +Here is a more detailed output of the same call when using `debug` as the log level: + +```bash +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Started proxy" +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Stripping proxy listen path: /api1/" +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Upstream path is: /get" +tyk-gateway | time="Jan 24 14:32:19" level=debug msg=Started api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 mw=ReverseProxy org_id=63ca963f6888c7000191890e ts=1674570739659369736 +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Upstream request URL: /get" api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 mw=ReverseProxy org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Outbound request URL: http://httpbin.org/get" api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 mw=ReverseProxy org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Creating new transport" api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 mw=ReverseProxy org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Out request url: http://httpbin.org/get" api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 mw=ReverseProxy org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Request is not cacheable" mw=ResponseCacheMiddleware +tyk-gateway | time="Jan 24 14:32:19" level=debug msg=Finished api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 mw=ReverseProxy ns=316559477 org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Upstream request took (ms): 316.639871" +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Checking: 63ca963f6888c7000191890e" api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="no cached entry found, returning 7 days" api_id=63666619de884d0563ee3ccc67d57929 api_name=api1 org_id=63ca963f6888c7000191890e +tyk-gateway | time="Jan 24 14:32:19" level=debug msg="Done proxy" +tyk-pump | time="Jan 24 14:32:20" level=info msg="Purged 0 records..." prefix=mongo-pump-aggregate +tyk-pump | time="Jan 24 14:32:20" level=info msg="Purged 1 records..." prefix=mongo-pump-selective +tyk-pump | time="Jan 24 14:32:20" level=info msg="Completed purging the records" collection="tyk_analytics" number of records=1 prefix=mongo-pump +tyk-pump | time="Jan 24 14:32:20" level=info msg="Purged 1 records..." prefix=mongo-pump +tyk-mongo | 2023-01-24T14:32:20.398+0000 I NETWORK [listener] connection accepted from 172.20.0.3:54712 #19 (19 connections now open) +tyk-pump | time="Jan 24 14:32:20" level=info msg="Completed upserting" collection="tyk_analytics_aggregates" prefix=mongo-pump-aggregate +tyk-pump | time="Jan 24 14:32:20" level=info msg="Purged 1 records..." prefix=mongo-pump-aggregate + +``` + +As shown above, the `debug` log level mode provides more information which will help during your debugging stage, i.e when the API call was started, when it was finished, how long it took for the call to finish, the endpoint that was called, the upstream that was called, the organization that the API belongs to, and more. + +1. ##### Gateway Debug Settings + + If you’re using a `*.conf` for your configuration parameters: + + ```json + "log_level": "debug" + ``` + + If you’re using environment variables for your configuration: + + ```bash + TYK_GW_LOGLEVEL=debug + ``` + + If you're using Tyk Helm Charts. Add the following items to your `values.yaml`: + + ```yaml + extraEnvs: + - name: TYK_LOGLEVEL + value: debug + ``` + +2. ##### Dashboard Debug Settings + + If you’re using a `*.conf` for your configuration parameters: + + ```json + "log_level": "debug" + ``` + + If you’re using environment variables for your configuration: + + ``` + TYK_LOGLEVEL=debug + ``` + + If you're using Tyk Helm Charts. Add the following items to your `values.yaml`: + + ```yaml + extraEnvs: + - name: TYK_LOGLEVEL + value: debug + ``` + + You can find the full [log levels](/api-management/logs/application-logs) in our documentation. + +#### Versions + +You can access all Tyk release information on the [release notes](/developer-support/release-notes/overview) overview page. + +We recommend always using the [Long-Term Support (LTS) release](/developer-support/release-types/long-term-support) for stability and long term support. + +##### Non-LTS versions +Tyk is backwards compatible, upgrading to newer versions won't turn on new features or change the behavior of your existing environment. + +For the best experience when experimenting with Tyk and exploring its latest capabilities, you can use our latest version. You can access all Tyk releases on the [release notes summary](/developer-support/release-notes/overview) page. + +#### Dashboard + +The Dashboard front-end (GUI included) uses [Tyk Dashboard API](/tyk-dashboard-api) to retrieve data to display or update. This means you can use the [developer tools on your browser](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/What_are_browser_developer_tools) to access the API and its information. Looking into the API details, the URL, the headers, the payload and the response can help you investigate the source of the issue and replicate it with API calls using an HTTP client such as [cURL](https://curl.se/) or [Postman](https://www.postman.com/). +As a next step to this investigation, if that specific endpoint exists also in [Tyk Gateway API](/tyk-gateway-api), you can compare the responses from both gateway and dashboard requests. + +##### Isolating + +As mentioned above, errors can happen in any of the components of your Tyk deployment, and as such, one of the critical things you'll pick up during your debugging phase is isolating these environments. + +##### Dashboard Level + +When debugging an issue, in order to isolate the gateway from the Dashboard, try to call the same API ednpoint on both Tyk Dashboard and Tyk Gateway +If it works with the gateway API only, then the issue is likely to be in the Dashboard. It could be that you need to set in the Dashboard some [configuration parameters](/tyk-dashboard/configuration) (using the config file or via environment variables). + +##### Gateway or API level + +Are you making calls against your gateway or API, and it's not working? Try isolating the gateway from everything else. Often you'll see that the gateway or API aren't at fault and that it's something else; it can be the load balancer you have in your environment blocking the call from ever reaching it. + +In the case of the API error-ing out, you can also isolate it by: + +- Creating a generic Httpbin API and calling it + - If this works, then the API configuration or the backend is at fault +- Changing the target URL of the API + - The upstream API can be at fault +- Assuming your API has a plugin, take away the plugin and test the API + - The error most likely exists in the plugin +- If the error exists in your plugin, try taking out certain parts of the code and testing it with minimal logic + - This means that part of your code with integrated logic is incorrect +- Is the target URL the same in another one of your APIs? + - The gateway sees the API as duplicated and changes the new target URL causing the gateway to error. + +You will eventually hit the point of error by further isolating parts of your API. + diff --git a/api-management/tyk-pump.mdx b/api-management/tyk-pump.mdx new file mode 100644 index 0000000000..b12189318a --- /dev/null +++ b/api-management/tyk-pump.mdx @@ -0,0 +1,449 @@ +--- +title: "Tyk Pump" +description: "Install and configure Tyk Pump, the component that moves analytics data out of Redis to persistent storage and external systems." +keywords: "Tyk Pump, Analytics, Redis, MongoDB, SQL, Configuration" +sidebarTitle: "Tyk Pump" +--- + +## What Is Tyk Pump? + +Tyk Gateway writes a [traffic log](/api-management/logs/traffic-logs), a structured record of the request and response, for every API call it processes, into Redis. Redis is a temporary, high-throughput buffer, not a long-term store, so something has to move those traffic logs out before they expire. + +Tyk Pump is the [open source](https://github.com/TykTechnologies/tyk-pump) component that does this. It reads traffic logs out of Redis and writes them to one or more configured destinations, which Tyk calls pumps. Each pump has a type, such as `mongo` or `sql`, from the [Pump Type Catalog](#pump-type-catalog) below: depending on the type, it can forward traffic logs unaltered, or compute aggregated analytics or metrics from them. A single Tyk Pump process can run several pumps at once, so the same traffic data can be sent to multiple destinations in parallel. + + +For new integrations, we recommend [OpenTelemetry tracing](/api-management/traces), since it gives you richer per-request detail without the Redis and Tyk Pump hop, and [OpenTelemetry metrics](/api-management/logs-metrics#opentelemetry-metrics) instead. + + +**A Note on Terminology** + +Throughout this documentation, "Tyk Pump" (capitalized) always refers to the component itself. "A pump" or "pump type" refers to one of its configured destinations, such as the `mongo` or `hybrid` pump type. + + +Tyk Pump serves four distinct outcomes, covered in full elsewhere in the documentation: + +- **Populating Tyk Dashboard's built-in Traffic Analytics UI and Log Browser.** These have no other source of data. This is the primary, ongoing purpose of Tyk Pump. See [Dashboard Analytics](/api-management/dashboard-analytics). +- **Forwarding traffic logs, unaltered, to external systems** such as Splunk or Datadog. See [External Data Sinks](/api-management/logs/external-data-sinks). +- **Exposing metrics derived from traffic logs** to Prometheus, StatsD, or DogStatsD. See [Metrics Pumps](/api-management/metrics/metrics-pumps). +- **Storing the results of Tyk Gateway's own uptime tests**, periodic health checks against upstream hosts, for Tyk Dashboard's separate Uptime Tests reporting screen. Unlike the other three, the dedicated uptime pump doesn't work with traffic log data. See [Uptime Tests](/planning-for-production/ensure-high-availability/uptime-tests#monitoring-uptime-tests-in-tyk-dashboard). + +This page covers what applies to Tyk Pump regardless of which of those goals you're pursuing: installation, the shape of `pump.conf`, and the full catalog of available pump types. + + +Tyk Pump is not configurable in Tyk Cloud. + + + +## Architecture + +The architecture differs depending on your deployment model: + + + + +Tyk Enterprise Pump Architecture + + + + +Tyk Open Source Pump Architecture + + + + +Tyk Pump is flexible: you can run multiple pumps in a single instance to write the same traffic data to several destinations at once. It's also scalable, both horizontally and vertically. + +The figure below shows each Tyk Pump instance ("1", "2", and "n") running two pumps concurrently, labeled Pump Backend (i) and (ii), with `pump_type: "mongo"` and `pump_type: "elasticsearch"` respectively. + +| Configuration and Scaling of Tyk Pump | +| :-- | +| Figure 1: An architecture diagram illustrating horizontal scaling of "n" instances of Tyk Pump, each running two pumps. | + +Tyk Pump can be horizontally scaled without causing duplicate data, provided your configuration follows one of the supported combinations below: + +| Configuration | Supported | +| :-- | :-------: | +| Single pump instance, single backend | ✅ | +| Single pump instance, multiple backends | ✅ | +| Multiple pump instances, same backend(s) | ✅ | +| Multiple pump instances, different backend(s) | ❌ | + +You can apply filters to control which records go to which destination: see [Sharding Analytics to Different Data Sinks](#sharding-analytics-to-different-data-sinks). + +## Installing Tyk Pump + +Tyk Pump is installed as part of a Tyk Self-Managed or Tyk Open Source deployment. See the [Tyk Self-Managed installation options](/tyk-self-managed/install) or [Tyk Open Source installation](/apim/open-source/installation) for platform-specific instructions (Docker, Kubernetes, Linux packages). + +Set the `TYK_PMP_OMITCONFIGFILE` environment variable to omit `pump.conf` entirely and configure Tyk Pump purely from environment variables. This is particularly useful in Docker, since the Tyk Pump image ships with a default configuration file with pre-loaded pumps. + +## Configuring Tyk Pump + +Tyk Pump is configured through a JSON file, conventionally called `pump.conf`, or through equivalent environment variables: + +```json +{ + "analytics_storage_type": "redis", + "analytics_storage_config": { ... }, + "purge_delay": 1, + "purge_chunk": 0, + "storage_expiration_time": 60, + "log_level": "info", + "log_format": "text", + "health_check_endpoint_name": "health", + "health_check_endpoint_port": 8083, + "enable_http_profiler": false, + "statsd_connection_string": "", + "statsd_prefix": "", + "max_record_size": 0, + "uptime_pump_config": { ... }, + "dont_purge_uptime_data": false, + "pumps": { ... } +} +``` + + +For the full field-by-field reference, environment variable names, and default values for every pump type, see the [Tyk Pump configuration reference](/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables). For the fields contained in a traffic log itself, see [Traffic Log Field Reference](/api-management/logs/traffic-logs#traffic-log-field-reference). + + +### Connecting to Redis + +`analytics_storage_config` is the Redis instance Tyk Pump reads traffic logs from, the same Redis that Tyk Gateway writes to. For single-node, Cluster, and Sentinel configuration, connection pool tuning, and TLS, see [Configure Redis](/tyk-configuration-reference/redis-cluster-sentinel), which covers Tyk Gateway, Tyk Dashboard, and Tyk Pump together. + +### Purge Configuration + +```json +{ + "purge_delay": 1, + "purge_chunk": 0, + "storage_expiration_time": 60 +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `purge_delay` | `1` | Seconds Tyk Pump waits between checking Redis for new analytics data and purging it. | +| `purge_chunk` | Unset (all in one pass) | Maximum traffic logs to pull from Redis at a time. If set, `storage_expiration_time` is used to reset the traffic log's TTL instead. | +| `storage_expiration_time` | `60` seconds | TTL applied to traffic logs in Redis. Only takes effect if `purge_chunk` is set. | + +### Logging and Health Checks + +```json +{ + "log_level": "info", + "log_format": "text", + "health_check_endpoint_name": "health", + "health_check_endpoint_port": 8083, + "enable_http_profiler": false +} +``` + +| Field | Default | Description | +| :-- | :-- | :-- | +| `log_level` | `info` | `info`, `debug`, `error`, or `warn`. | +| `log_format` | `text` | `text`, `json`, or `legacy`. From Tyk Pump 1.16.0, `text` and `json` use RFC3339 timestamps and map the `msg` field to `message`; `legacy` preserves the previous timestamp format and field names. | +| `health_check_endpoint_name` | `health` | Path for the HTTP health check endpoint. | +| `health_check_endpoint_port` | `8083` | Port for the HTTP health check endpoint. Returns `{"status": "ok"}` with a `200` response while Tyk Pump is running. | +| `enable_http_profiler` | `false` | Exposes Go profiling information to support debugging, the same way as [Tyk Gateway](/api-management/troubleshooting-debugging). | + +### Instrumentation + +```json +{ + "statsd_connection_string": "", + "statsd_prefix": "" +} +``` + +`statsd_connection_string` and `statsd_prefix` configure Tyk Pump to send its own operational metrics to a StatsD server, separate from any traffic-derived metrics sent by the [`statsd`/`dogstatsd` Metrics Pumps](/api-management/metrics/metrics-pumps). Only takes effect when `TYK_INSTRUMENTATION=1` is set. See [Configuring StatsD](/api-management/logs-metrics#configuring-statsd) for details. + +### Uptime Test Results + +```json +{ + "uptime_pump_config": { ... }, + "dont_purge_uptime_data": false +} +``` + +`uptime_pump_config` stores the results of Tyk Gateway's own uptime tests, periodic health checks against upstream hosts. It's unrelated to traffic logs and configured independently of `pumps`. See [Uptime Tests](/planning-for-production/ensure-high-availability/uptime-tests#monitoring-uptime-tests-in-tyk-dashboard) for configuration. + + +`dont_purge_uptime_data` defaults to `false`, which starts an Uptime Pump on every Tyk Pump launch, even if you haven't configured a real `uptime_pump_config` target. If it can't connect to a persistent store, **Tyk Pump exits immediately, taking down every other pump in that instance too.** + +**Set `dont_purge_uptime_data: true` if you're not using the Uptime Pump.** + + +### Declaring Pumps + +At least one pump must be configured in the `pumps` object: + +```json +{ + "pumps": { + "": { + "type": "", + "meta": { + ... + }, + ... + } + } +} +``` + +`` is more than a label: it's also the environment variable prefix for that pump, `TYK_PMP_PUMPS__`. For example, naming a pump `PROD` lets you configure it with `TYK_PMP_PUMPS_PROD_TYPE`, `TYK_PMP_PUMPS_PROD_META_...`, and so on, whether or not it's also declared in `pump.conf`. If you omit `type` from the JSON block entirely, Tyk Pump also falls back to using `` itself as the type. + +Beyond that, `` is logged once, when Tyk Pump starts up, and then dropped: at runtime, warnings, errors, and metrics identify a pump by its type's display name (for example, "Mongo Pump"), not by this name. If you run multiple instances of the same type, choose names that help you tell them apart in configuration, but expect their ongoing logs to look identical. + +The `type` is selected from the [Pump Type Catalog](#pump-type-catalog) and determines the specific `meta` configuration; there is also [common configuration](#common-pump-settings) that is applicable to all pump types. + +## Common Pump Settings + +Every pump entry accepts the following settings, alongside its `type` and `meta`: + +```json +{ + "pumps": { + "": { + "type": "", + "meta": { + ... + }, + "filters": { + ... + }, + "timeout": 0, + "omit_detailed_recording": false, + "max_record_size": 0, + "ignore_fields": [], + "raw_request_decoded": false, + "raw_response_decoded": false + } + } +} +``` + +| Field | Description | +| :-- | :-- | +| `type` | Selects the pump type to use, from the [Pump Type Catalog](#pump-type-catalog) below. | +| `meta` | Pump-type-specific settings. | +| `filters` | Restricts which records this pump receives, by API, Organisation, or response code. See [Sharding Analytics to Different Data Sinks](#sharding-analytics-to-different-data-sinks). | +| `timeout` | Maximum time to wait for a write to complete, in seconds. Defaults to `0` (wait indefinitely). See [Pump Timeout](#pump-timeout). | +| `omit_detailed_recording` | Excludes the `raw_request` and `raw_response` fields from this pump's records. See [Omit Detailed Recording](#omit-detailed-recording). | +| `max_record_size` | Caps the size, in bytes, of the `raw_request` and `raw_response` fields. See [Max Record Size](#max-record-size). | +| `ignore_fields` | Excludes specific traffic log fields from this pump's records. See [Ignore Fields](#ignore-fields). | +| `raw_request_decoded` / `raw_response_decoded` | Base64-decodes the raw request or response before writing. See [Decode Raw Request and Raw Response](#decode-raw-request-and-raw-response). | + +### Sharding Analytics to Different Data Sinks + +In a multi-Organisation deployment, each Organisation, team, or environment might have a preferred destination. The `filters` field, available on every pump, lets you control which records are sent to which pump using an allowlist and a blocklist: + +```json +"filters":{ + "api_ids":[], + "org_ids":[], + "response_codes":[], + "skip_api_ids":[], + "skip_org_ids":[], + "skip_response_codes":[] +} +``` + +- `api_ids`, `org_ids`, and `response_codes` act as an allowlist: only matching records are sent to this pump. +- `skip_api_ids`, `skip_org_ids`, and `skip_response_codes` act as a blocklist: matching records are never sent to this pump. +- The blocklist always takes priority over the allowlist. + +For example, this configuration sends all analytics for `org1` and `org2` to a CSV file, and everything except `api_id_1` to Elasticsearch: + +```json +"csv": { + "type": "csv", + "filters": { + "org_ids": ["org1", "org2"] + }, + ... +}, +"elasticsearch": { + "type": "elasticsearch", + "filters": { + "skip_api_ids": ["api_id_1"] + }, + ... +} +``` + +### Pump Timeout + +By default, Tyk Pump waits indefinitely for each write operation to complete (`timeout: 0`). You can configure an optional `timeout`, in seconds, per pump: + +```json +"mongo": { + "type": "mongo", + "timeout": 5, + ... +} +``` + +If a pump's write operation takes longer than the [purge loop interval](#purge-configuration) (`purge_delay`) and no timeout is configured, Tyk Pump logs: `Pump PMP_NAME is taking more time than the value configured of purge_delay. You should try to set a timeout for this pump.` + +If a timeout is configured and the pump is still falling behind, it logs: `Pump PMP_NAME is taking more time than the value configured of purge_delay. You should try lowering the timeout configured for this pump.` + +### Omit Detailed Recording + +`omit_detailed_recording`, set on a pump, stops that pump from writing the `raw_request` and `raw_response` fields for every record. Defaults to `false`. + +### Max Record Size + +`max_record_size` caps the size, in bytes, of the `raw_request` and `raw_response` fields written by a pump. Defaults to `0` (unlimited). Set it at the top level of `pump.conf` to apply a default to every pump: + +```json +{ + "max_record_size": 1000, + "pumps": { ... } +} +``` + +Or set it on a specific pump instead, which takes precedence over the global default for that pump only: + +```json +"csv": { + "type": "csv", + "max_record_size": 1000, + ... +} +``` + +### Ignore Fields + +`ignore_fields` lists fields, by JSON tag, to exclude when a pump writes its record. Useful for keeping sensitive or unneeded data out of a specific destination: + +```json +"csv": { + "type": "csv", + "ignore_fields": ["api_id", "api_version"], + ... +} +``` + +### Decode Raw Request and Raw Response + +When [detailed recording](/api-management/logs/traffic-logs#detailed-recording) is configured in Tyk Gateway, the full request and response will be base64-encoded and stored in the [`raw_request`](/api-management/logs/traffic-logs#param-raw-request) and [`raw_response`](/api-management/logs/traffic-logs#param-raw-response) fields respectively in the traffic log. + +The pump can optionally remove the base64-encoding before writing the record to the target, which avoids the need for post-processing by your analytics package. + +Decoding of request and response are individually controlled using `raw_request_decoded` and `raw_response_decoded` (both default to `false`). + +```json +"csv": { + "type": "csv", + "raw_request_decoded": true, + "raw_response_decoded": true + ... +} +``` + + +Do not decode the request and response data when transferring the unaggregated traffic logs to the control plane's persistent storage for Tyk Dashboard's Log Browser. The Dashboard automatically base64-decodes this data when displaying a record; if this decoding has already been performed by Tyk Pump, the result will be nonsense in the Log Browser display. Only decode for destinations that read the data directly, such as an external sink. + + +## Pump Type Catalog + +Tyk Pump's pump types fall into three categories, each covered in more detail on its own page. Every pump type is configured the same way: an entry under `pumps` with a `type` and a `meta` block. + + +This doesn't include the uptime pump, covered separately under [Uptime Test Results](#uptime-test-results) above. + + +### Dashboard Analytics Pumps + +Dashboard Analytics pumps transfer, and in some cases aggregate, traffic logs to the persistent storage. Tyk Dashboard's Log Browser and Traffic Analytics screens require different data and hence separate pumps. + +When using a combined control and data plane deployment (such as with the `tyk-stack` chart), you only need [Control Plane Pumps](#control-plane-pumps). When using a distributed deployment with separate control and data planes, connected via Tyk MDCB, you also need a [Data Plane Pump](#data-plane-pump) to get the data to the control plane. + +#### Control Plane Pumps + +| Type | Purpose | Storage Type | Configuration | +| :-- | :-- | :-- | :-- | +| `mongo` | Log Browser | MongoDB | [Standard Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#standard-mongo-pump) | +| `mongo-pump-selective` | Log Browser, split per Organisation | MongoDB | [Per-Organisation Mongo Pump](/api-management/dashboard-analytics/control-plane-pumps#per-organisation-mongo-pump) | +| `mongo-pump-aggregate` | Traffic Analytics graphs | MongoDB | [Mongo Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#mongo-aggregate-pump) | +| `sql` | Log Browser | SQL | [Standard SQL Pump](/api-management/dashboard-analytics/control-plane-pumps#standard-sql-pump) | +| `sql_aggregate` | Traffic Analytics graphs | SQL | [SQL Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-aggregate-pump) | +| `mongo-graph` | GraphQL-specific traffic logs: types, fields, and errors requested | MongoDB | [Mongo GraphQL Pump](/api-management/dashboard-analytics/control-plane-pumps#mongo-graphql-pump) | +| `sql-graph` | GraphQL-specific traffic logs: types, fields, and errors requested | SQL | [SQL GraphQL Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-graphql-pump) | +| `sql-graph-aggregate` | Aggregated GraphQL-specific analytics, mirroring `sql_aggregate` | SQL | [SQL GraphQL Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-graphql-aggregate-pump) | +| `mongo-mcp` | MCP (Model Context Protocol) tool-call traffic logs, mirroring `mongo` | MongoDB | [Mongo MCP Pump](/api-management/dashboard-analytics/control-plane-pumps#mongo-mcp-pump) | +| `mongo-mcp-aggregate` | Aggregated MCP-specific analytics, mirroring `mongo-pump-aggregate` | MongoDB | [Mongo MCP Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#mongo-mcp-aggregate-pump) | +| `sql-mcp` | MCP tool-call traffic logs, mirroring `sql` | SQL | [SQL MCP Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-mcp-pump) | +| `sql-mcp-aggregate` | Aggregated MCP-specific analytics, mirroring `sql_aggregate` | SQL | [SQL MCP Aggregate Pump](/api-management/dashboard-analytics/control-plane-pumps#sql-mcp-aggregate-pump) | + + +Unlike Mongo, SQL has an aggregate GraphQL pump; there's no `mongo-graph-aggregate` equivalent. + + +#### Data Plane Pump + +For separate control and data planes you must transfer the traffic logs from the data plane (where they are generated) to the control plane (where they are used by Tyk Dashboard). A `hybrid` pump should be deployed in each data plane to transfer the records to Tyk MDCB. + +| Type | Purpose | Configuration | +| :-- | :-- | :-- | +| `hybrid` | Forwards traffic logs, or aggregated analytics, from a data plane to Tyk MDCB | [Data Plane Pump](/api-management/dashboard-analytics/data-plane-pump) | + +As explained in the dedicated [Data Plane Pump](/api-management/dashboard-analytics/data-plane-pump) section, once data reaches Tyk MDCB, it must be transferred to the control plane's persistent storage via one of two options: Tyk MDCB's own built-in writer or a [Control Plane Pump](#control-plane-pumps): an ordinary Tyk Pump instance running one of the pump types listed above. + + +The `hybrid` pump can create aggregated analytics from MCP proxy and standard REST API traffic logs, but cannot create aggregated analytics for GraphQL API traffic. + + +### External Data Sink Pumps + +Forward traffic logs, unaltered, to external tools. Considered the legacy approach: for new integrations, use [OpenTelemetry tracing](/api-management/traces) instead. + +| Type | Description | +| :-- | :-- | +| [`csv`](/api-management/logs/external-data-sinks#csv) | Writes traffic logs to local CSV files | +| [`elasticsearch`](/api-management/logs/external-data-sinks#elasticsearch) | Indexes traffic logs in Elasticsearch | +| [`graylog`](/api-management/logs/external-data-sinks#graylog) | Forwards traffic logs to Graylog | +| [`influx`](/api-management/logs/external-data-sinks#influxdb) | Writes traffic logs to InfluxDB v1 | +| [`influx2`](/api-management/logs/external-data-sinks#influx2) | Writes traffic logs to InfluxDB v2 | +| [`kafka`](/api-management/logs/external-data-sinks#kafka) | Publishes traffic logs to a Kafka topic | +| [`kinesis`](/api-management/logs/external-data-sinks#kinesis) | Publishes traffic logs to an Amazon Kinesis stream | +| [`logzio`](/api-management/logs/external-data-sinks#logz-io) | Forwards traffic logs to Logz.io | +| [`moesif`](/api-management/logs/external-data-sinks#moesif) | Forwards traffic logs to Moesif | +| [`resurfaceio`](/api-management/logs/external-data-sinks#resurface-io) | Forwards traffic logs to Resurface.io | +| [`segment`](/api-management/logs/external-data-sinks#segment) | Forwards traffic logs to Segment | +| [`splunk`](/api-management/logs/external-data-sinks#splunk) | Forwards traffic logs to Splunk | +| [`sqs`](/api-management/logs/external-data-sinks#sqs) | Publishes traffic logs to an Amazon SQS queue | +| [`stdout`](/api-management/logs/external-data-sinks#stdout) | Writes traffic logs to standard output, useful for container log collectors | +| [`syslog`](/api-management/logs/external-data-sinks#syslog) | Forwards traffic logs to a syslog server | +| [`timestream`](/api-management/logs/external-data-sinks#timestream) | Writes traffic logs to Amazon Timestream | + +### Metrics Pumps + +Derive per-request metrics from traffic logs, rather than forwarding the full record. Considered the legacy approach: for new integrations, use [OpenTelemetry metrics](/api-management/logs-metrics#opentelemetry-metrics) instead. + +| Type | Description | +| :-- | :-- | +| [`dogstatsd`](/api-management/metrics/metrics-pumps#dogstatsd-datadog) | Sends per-request metrics in the DogStatsD format, for example to Datadog | +| [`prometheus`](/api-management/metrics/metrics-pumps#prometheus) | Exposes an HTTP endpoint for Prometheus to scrape | +| [`statsd`](/api-management/metrics/metrics-pumps#statsd) | Sends per-request metrics to a StatsD server | + + +Some pump types accept a `filters` object to restrict which Organisations or APIs they receive data for; see [Sharding Analytics to Different Data Sinks](#sharding-analytics-to-different-data-sinks) above. + + +## Demo Mode + +Tyk Pump can generate synthetic analytics data and send it to your configured pumps, without needing any live API traffic. This is useful for previewing Tyk Dashboard's Traffic Analytics and Log Browser with realistic-looking data, for example while evaluating Tyk, or for testing a new pump configuration in isolation before pointing it at live traffic. + +Demo mode is controlled by command-line flags when starting Tyk Pump, not by `pump.conf` or environment variables. `--demo` is required to enable it; the rest are optional and customize the generated data: + +| Flag | Default | Description | +| :-- | :-- | :-- | +| `--demo=` | Required | Enables demo mode for the given Organisation ID. | +| `--demo-api=` | Random ID | The `API_ID` to record for all demo transactions. | +| `--demo-api-version=` | - | The API version to record for demo transactions. | +| `--demo-days=` | `30` | How many days of demo data to generate. | +| `--demo-records-per-hour=` | `0` (random 300-500/hour) | Records generated per hour. | +| `--demo-future-data` | `false` | Generates data forward from now instead of backward. | +| `--demo-track-path` | `false` | Enables request path tracking in demo data. Overridden by an Aggregate Pump's own [`track_all_paths`](/api-management/dashboard-analytics/control-plane-pumps#mongo-aggregate-pump), if set to `true`. | diff --git a/api-management/upstream-authentication.mdx b/api-management/upstream-authentication.mdx new file mode 100644 index 0000000000..6f507d778f --- /dev/null +++ b/api-management/upstream-authentication.mdx @@ -0,0 +1,34 @@ +--- +title: "Upstream Authentication" +description: "Authenticating Tyk Gateway with upstream services" +keywords: "security, upstream authentication, gateway to upstream, OAuth, mTLS, Basic Auth" +sidebarTitle: "Overview" +--- + +## Introduction + +Tyk Gateway sits between your clients and your services, securely routing requests and responses. For each API proxy that you expose on Tyk, you can configure a range of different methods that clients must use to identify (authenticate) themselves to Tyk Gateway. These are described in detail in the [Client Authentication](/api-management/client-authentication) section. + +In the same way as you use Client Authentication to securely confirm the identity of the API clients, your upstream services probably need to securely confirm the identity of their client - namely Tyk. This is where Tyk's flexible **Upstream Authentication** capability comes in. + +When using Tyk, you can choose from a range of authentication methods for each upstream API: +- [Mutual TLS](/api-management/upstream-authentication/mtls) +- [Token-based authentication](/api-management/upstream-authentication/auth-token) +- [Request signing](/api-management/upstream-authentication/request-signing) +- [Basic Authentication](/api-management/upstream-authentication/basic-auth) +- [OAuth 2.0](/api-management/upstream-authentication/oauth) + - [OAuth 2.0 Client Credentials](/api-management/upstream-authentication/oauth#oauth-client-credentials) + - [OAuth 2.0 Password Grant](/api-management/upstream-authentication/oauth#oauth-resource-owner-password-credentials) + + + + + Upstream Basic Authentication and OAuth 2.0 support are only available to licensed users, via the Tyk Dashboard. These features are not available to open source users. + + + + + +Note that OAuth 2.0 Password Grant is prohibited in the [OAuth 2.0 Security Best Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13#section-3.4") but is supported by Tyk for use with legacy upstream services. + + diff --git a/api-management/upstream-authentication/auth-token.mdx b/api-management/upstream-authentication/auth-token.mdx new file mode 100644 index 0000000000..b2506cb53b --- /dev/null +++ b/api-management/upstream-authentication/auth-token.mdx @@ -0,0 +1,24 @@ +--- +title: "Upstream Authentication using Auth Token" +description: "How to authenticate upstream service using auth token" +keywords: "security, upstream authentication, gateway to upstream, auth token" +sidebarTitle: "Auth Token" +--- + +## Token-based authentication + +Token-based authentication (also referred to as Auth Token) is a method whereby the client is identified and authenticated by the server based on a key/token they present as a credential with each request. Typically the token is issued by the server to a specific client. + +The server determines how the key should be provided - typically in a request header, cookie or query parameter. + +Tyk supports [Auth Token](/api-management/authentication/bearer-token) as a method for authenticating **clients** with the **Gateway** - you can use Tyk Gateway or Dashboard to generate access *keys* for an Auth Token protected API as explained in the [documentation](/api-management/access-control/overview). The client must then provide the *key* in the appropriate parameter for each request. + +If your **upstream service** is protected using Auth Token then similarly, Tyk will need to provide a token, issued by the upstream, in the request. + +### How to use Upstream Token-based Authentication +Typically Auth Token uses the `Authorization` header to pass the token in the request. + +Tyk's [Request Header Transform](/api-management/traffic-transformation/request-headers) middleware can be configured to add this header to the request prior to it being proxied to the upstream. To enhance security by restricting visibility of the access token, the key/token can be stored in a [key-value store](/tyk-self-managed/install), with only the reference included in the middleware configuration. + + + diff --git a/api-management/upstream-authentication/basic-auth.mdx b/api-management/upstream-authentication/basic-auth.mdx new file mode 100644 index 0000000000..2694ee1d71 --- /dev/null +++ b/api-management/upstream-authentication/basic-auth.mdx @@ -0,0 +1,127 @@ +--- +title: "Upstream Authentication using Basic Auth" +description: "How to authenticate upstream service basic authentication" +keywords: "security, upstream authentication, gateway to upstream, basic auth" +sidebarTitle: "Basic Auth" +--- + +## Availability + +| Component | Editions | +| :----------- | :---------- | +| Gateway and Dashboard | Enterprise | + +## Basic Authentication + +Basic Authentication is a standard authentication mechanism implemented by HTTP servers, clients and web browsers. This makes it an excellent access control method for smaller APIs. + +An API request made using Basic Authentication will have an `Authorization` header that contains the client's credentials in the form: `Basic `. + +The `` are a base64 encoded concatenation of a client username and password, joined by a single colon `:`. + +Tyk supports Basic Authentication as a method for authenticating **clients** with the **Gateway** - you can use Tyk Gateway or Dashboard to create Basic Auth users, as explained in the [documentation](/api-management/authentication/basic-authentication#registering-basic-authentication-user-credentials-with-tyk). + +If your **upstream service** is protected using Basic Authentication then similarly, Tyk will need to provide user credentials, registered with the upstream, in the request. + +### How to use Upstream Basic Authentication + +If your upstream service requires that Tyk authenticates using Basic Authentication, you will first need to obtain a valid username and password from the server. To enhance security by restricting visibility of the credentials, these can be stored in a [key-value store](/tyk-self-managed/install), with only references included in the API definition. + +If the incoming request from the client already has credentials in the `Authorization` header, then Tyk will replace those with the basic auth credentials before proxying onwards to the upstream. + +Sometimes a non-standard upstream server might require the authentication credentials to be provided in a different header (i.e. not `Authorization`). With Tyk, you can easily configure a custom header to be used for the credentials if required. + +Upstream Basic Authentication is only supported by Tyk OAS APIs. If you are using Tyk Classic APIs, you could create the client credential offline and add the `Authorization` header using the [Request Header Transform](/api-management/traffic-transformation/request-headers) middleware. + +#### Configuring Upstream Basic Auth in the Tyk OAS API definition + +Upstream Authentication is configured per-API in the Tyk extension (`x-tyk-api-gateway`) within the Tyk OAS API definition by adding the `authentication` section within the `upstream` section. + +Set `upstream.authentication.enabled` to `true` to enable upstream authentication. + +For Basic Authentication, you will need to add the `basicAuth` section within `upstream.authentication`. + +This has the following parameters: +- `enabled` set this to `true` to enable upstream basic authentication +- `username` is the username to be used in the request *credentials* +- `password` is the password to be used in the request *credentials* +- `header.enabled` must be set to `true` if your upstream expects the *credentials* to be in a custom header, otherwise it can be omitted to use `Authorization` header +- `header.name` is the custom header to be used if `header.enabled` is set to `true` + +Note that if you use the [Tyk API Designer](#configuring-upstream-basic-auth-using-the-api-designer) in Tyk Dashboard it will always configure the `header` parameter - even if you are using the default `Authorization` value. + +For example: + +```json {hl_lines=["43-54"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-upstream-basic-auth", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "servers": [ + { + "url": "http://localhost:8181/example-upstream-basic-auth/" + } + ], + "security": [], + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "components": { + "securitySchemes": {} + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-upstream-basic-auth", + "state": { + "active": true + } + }, + "server": { + "listenPath": { + "strip": true, + "value": "/example-upstream-basic-auth/" + } + }, + "upstream": { + "url": "https://httpbin.org/basic-auth/myUsername/mySecret", + "authentication": { + "enabled": true, + "basicAuth": { + "password": "mySecret", + "username": "myUsername", + "enabled": true, + "header": { + "enabled": true, + "name": "Authorization" + } + } + } + } + } +} +``` + +In this example upstream authentication has been enabled (line 44). Requests will be proxied to the `GET /basic-auth` endpoint at httpbin.org using the credentials in lines 46 and 47 (username: myUsername, password: mySecret). These credentials will be combined, base64 encoded and then provided in the `Authorization` header, as required by the httpbin.org [documentation](https://httpbin.org/#/Auth/get_basic_auth__user___passwd_"). + +The configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the Upstream Basic Authentication feature. + +#### Configuring Upstream Basic Auth using the API Designer + +Upstream Authentication is configured from the **Settings** tab of the Tyk OAS API Designer, where there is a dedicated section within the **Upstream** section. + +Select **Basic Auth** from the choice in the **Authentication Method** drop-down, then you can provide the client credentials and header name. + +Tyk OAS API Designer showing Upstream Basic Auth configuration options + +
diff --git a/api-management/upstream-authentication/mtls.mdx b/api-management/upstream-authentication/mtls.mdx new file mode 100644 index 0000000000..e37010e54d --- /dev/null +++ b/api-management/upstream-authentication/mtls.mdx @@ -0,0 +1,456 @@ +--- +title: "Upstream Authentication using Mutual TLS" +description: "How to authenticate upstream service using mutual tls" +keywords: "security, upstream authentication, gateway to upstream, mTLS, mutual tls" +sidebarTitle: "Mutual TLS" +--- + +## Mutual TLS (mTLS) + +If your upstream API is protected with [mutual TLS](/api-management/implement-tls#secure-hosted-apis-with-mtls) then Tyk must provide a certificate when connecting to the upstream service and also will need to verify the certificate presented by the upstream. This ensures secure communication between Tyk and your upstream services. + +When Tyk performs an mTLS handshake with an upstream, it needs to know: + +- which client certificate Tyk should use to identify itself +- which public key (certificate) that Tyk should use to verify the identity of the upstream + +We use a system of [mapping certificates](#mapping-certificates-to-domains) to upstreams based on their host domain. This is used for both the [client certificate](#upstream-client-certificates) and, optionally, for the [upstream public key](#upstream-server-certificates) if we want to use specific certificates to protect against compromised certificate authorities (CAs). + +#### Upstream mTLS for Tyk middleware and plugins + +If upstream mTLS certificates are configured for an API, they will not be used for direct proxies to the upstream and will also automatically be used for any HTTP requests made from the [JavaScript Virtual Endpoint](/api-management/traffic-transformation/virtual-endpoints) middleware. They will **not** be used for HTTP requests from custom plugins. + + +#### Upstream mTLS for Tyk Cloud + +All Tyk Cloud users can secure their upstream services with mTLS + +### Mapping certificates to domains + +Tyk maintains mappings of certificates to domains (which can include the port if a non-standard HTTP port is used). Separate maps can be declared globally, to be applied to all APIs, and at the API level for more granular control. The granular API level mapping takes precedence if both are configured. Within each mapping, both default and specific maps can be defined, giving ultimate flexibility. + +When Tyk performs an mTLS handshake with an upstream, it will check if there are certificates mapped to the domain: + +- first it will check in the API definition for a specific certificate +- then it will check in the API definition if there is a default certificate +- then it will check at the Gateway level for a specific certificate +- then it will check at the Gateway level for a default certificate + +Certificates are identified in the mapping using a [certificate reference](/api-management/certificates#certificate-management). In practice this is typically the certificate ID assigned by the Tyk Certificate Store, for example: `{"": ""}`. + +When mapping a certificate to a domain: + +- do not include the protocol (e.g. `https://`) +- include the port if a non-standard HTTP port is in use +- you can use the `*` wildcard - either in place of the whole domain or as part of the domain name + +For example, to map a certificate with Id `certId` to an upstream service located at `https://api.production.myservice.com:8443` you could map the certificate as: + +- `{"api.production.myservice.com:8443": "certId"}` +- `{"*.production.myservice.com:8443": "certId"}` +- `{"api.*.myservice.com:8443": "certId"}` + +Note that when using the wildcard (`*`) to replace part of the domain name, it can only represent one fragment so, using our example, you would not achieve the same mapping using `{"*.myservice.com:8443": "certId"}`. + + +A *default* certificate to be used for all upstream requests can be mapped by replacing the specific domain with the wildcard, for example `{"*", "certId"}`. + + +### Upstream client certificates + +Tyk can be configured to proxy requests to a single API on to different upstream hosts (for example via load balancing, API versions or URL rewrite middleware). You can configure Tyk to present specific client certificates to specific hosts, and you can specify a default certificate to be usedfor all upstream hosts. + +The upstream service uses the public key (from the certificate presented by Tyk) to verify the signed data, confirming that Tyk possesses the corresponding private key. + +All certificates are retrieved from the [Tyk Certificate Store](/api-management/certificates#certificate-management) when the proxy occurs. + +#### Mapping client certificates at the Gateway level + +You can map certificates to domains using the [security.certificates.upstream](/tyk-oss-gateway/configuration#security-certificates-upstream) field in your Gateway configuration file. + +Mapping a certificate to domain `*` will ensure that this certificate will be used in all upstream requests where no other certificate is mapped (at Gateway or API level). + +#### Mapping client certificates at the API level + +You can map certificates to domains using the [upstream.mutualTLS](/api-management/gateway-config-tyk-oas#mutualtls) object (Tyk Classic: `upstream_certificates`) in your API definition. + +Mapping a certificate to domain `*` will ensure that this certificate will be used in all upstream requests where no other certificate is mapped in the API definition. + + +### Upstream server certificates + +Tyk will verify the certificate received from the upstream by performing the following checks: + +- Check that it's issued by a trusted CA +- Check that the certificate hasn't expired +- Verify the certificate's digital signature using the public key from the certificate + + + + + Tyk will look in the system trust store for the server that is running Tyk Gateway (typically `/etc/ssl/certs`). If you are using self-signed certificates, store them here so that Tyk can verify the upstream service. + + + +If you want to restrict the public keys that can be used by the upstream service, then you can use [certificate pinning](/api-management/upstream-authentication/mtls#certificate-pinning) to store a list of certificates that Tyk will use to verify the upstream. + +#### Certificate Pinning + +Tyk provides the facility to allow only certificates generated from specific public keys to be accepted from the upstream services during the mTLS exchange. This is called "certificate pinning" because you *pin* a specific public certificate to an upstream service (domain) and Tyk will only use this to verify connections to that domain. This helps to protect against compromised certificate authorities. You can pin one or more public keys per domain. + +The public keys must be stored in PEM format in the [Tyk Certificate Store](/api-management/certificates#certificate-management). + +##### Configuring Certificate Pinning at the Gateway level + +If you want to lock down the public certificates that can be used in mTLS handshakes for specific upstream domains across all APIs, you can pin public certificates to domains using the [security.pinned_public_keys](/tyk-oss-gateway/configuration#security-pinned_public_keys) field in your Gateway configuration file. + +This accepts a map of domain addresses to certificates in the same way as for the client certificates. Wildcards are supported in the domain addresses. Pinning one or more certificates to domain `*` will ensure that only these certificates will be used to verify the upstream service during the mTLS handshake. + +##### Configuring Certificate Pinning at the API level + +Restricting the certificates that can be used by the upstream for specific APIs is simply a matter of registering a map of domain addresses to certificates in the [upstream.certificatePinning](/api-management/gateway-config-tyk-oas#certificatepinning) object in the API definition (Tyk Classic: `pinned_public_keys`). + + +### Overriding mTLS for non-production environments + +When you are developing or testing an API, your upstream might not have the correct certificates that are deployed for your production service. This could cause problems when integrating with Tyk. + +You can use the [`upstream.tlsTransport.insecureSkipVerify`](/api-management/gateway-config-tyk-oas#tlstransport) option in the API definition (Tyk Classic: `proxy.transport.ssl_insecure_skip_verify`) to instruct Tyk to ignore the certificate verification stage for a specific API. + +If you want to ignore upstream certificate verification for all APIs deployed on Tyk, you can use the [proxy_ssl_insecure_skip_verify](/tyk-oss-gateway/configuration#proxy_ssl_insecure_skip_verify) option in the Tyk Gateway configuration. + +These are labelled *insecure* with good reason and should never be configured in production. + + +### Using Tyk Dashboard to configure upstream mTLS + +Using the Tyk Dashboard, you can enable upstream mTLS from the **Upstream** section in the API Designer: + +Enable upstream mTLS + +Click on **Attach Certificate** to open the certificate attachment window: + +Attach a certificate to an API + +This is where you can define the upstream **Domain Name** and either select an existing certificate from the Tyk Certificate Store, or upload a new certificate to the store. + +If you want to [pin the public certificates](/api-management/upstream-authentication/mtls#certificate-pinning) that can be used by Tyk when verifying the upstream service, then you should enable **Public certificates** and attach certificates in the same manner as for the client certificates: + +Enable public key pinning + +For details on managing certificates with Tyk, please see the [certificate management](/api-management/certificates#certificate-management) documentation. + +For Tyk Classic APIs, the **Upstream Certificates** controls are on the **Advanced Options** tab of the Tyk Classic API Designer. + + +### Using Tyk Operator to configure mTLS + + + + +Configure upstream mTLS client certificates using the `mutualTLS` field in the `TykOasApiDefinition` object when using Tyk Operator, for example: + +```yaml{hl_lines=["12-18"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 + kind: TykOasApiDefinition + metadata: + name: petstore + namespace: default + spec: + tykOAS: + configmapRef: + name: petstore + namespace: default + keyName: petstore.json + mutualTLS: + enabled: true + domainToCertificateMapping: + - domain: "petstore.com" + certificateRef: petstore-domain + - domain: "petstore.co.uk" + certificateRef: petstore-uk-domain +``` + + + + +Tyk Operator supports certificate pinning in the Tyk OAS custom resource, allowing you to secure your API by pinning a public key stored in a secret to a specific domain. + +Example of public keys pinning + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm + namespace: default +data: + test_oas.json: |- + { + "info": { + "title": "httpbin with certificate pinning", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "httpbin with certificate pinning", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://httpbin.org/" + }, + "server": { + "listenPath": { + "value": "/httpbin/", + "strip": true + } + } + } + } +--- +apiVersion: v1 +kind: Secret +metadata: + name: domain-secret +type: kubernetes.io/tls # The secret needs to be a type of kubernetes.io/tls +data: + tls.crt: + tls.key: "" +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: "oas-pinned-public-keys" +spec: + tykOAS: + configmapRef: + keyName: test_oas.json + name: cm + certificatePinning: + enabled: true + domainToPublicKeysMapping: + - domain: "httpbin.org" + publicKeyRefs: + - domain-secret +``` + +This example demonstrates how to enable certificate pinning for the domain `httpbin.org` using a public key stored in a Kubernetes secret (`domain-secret`). + + + + +### Using Tyk Operator to configure mTLS for Tyk Classic APIs + + + +When using Tyk Classic APIs with Tyk Operator, you can configure upstream client certificates for mTLS using one of the following fields within the ApiDefinition object: + +- **upstream_certificate_refs**: Configure using certificates stored within Kubernetes secret objects. +- **upstream_certificates**: Configure using certificates stored within Tyk Dashboard's certificate store. + +**upstream_certificate_refs** + +The `upstream_certificate_refs` field can be used to configure certificates for different domains. References can be held to multiple secrets which are used for the domain mentioned in the key. Currently "*" is used as a wildcard for all the domains + +The example listed below shows that the certificate in the secret, *my-test-tls*, is used for all domains. + +```yaml +# First apply this manifest using the command +# "kubectl apply -f config/samples/httpbin_upstream_cert.yaml" +# +# The operator will try to create the ApiDefinition and will succeed but will log an error that a certificate is missing +# in the cluster for an upstream +# +# Generate your public-private key pair , for test you can use the following command to obtain one fast: +# "openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out tls.crt -keyout tls.key" +# +# Run the following command to obtain the values that must be put inside the yaml that contians the secret resource: +# "kubectl create secret tls my-test-tls --key="tls.key" --cert="tls.crt" -n default -o yaml --dry-run=client" +# +# Apply your TLS certificate using the following command: (we already have an example one in our repo) +# "kubectl apply -f config/sample/simple_tls_secret.yaml" +# +# NOTE: the upstream_certificate_refs can hold references to multiple secrets which are used for the domain +# mentioned in the key (currently "*" is used as a wildcard for all the domains) +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + upstream_certificate_refs: + "*": my-test-tls + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default +``` + +A secret can be created and output in yaml format using the following command: + +```bash +kubectl create secret tls my-test-tls --key="keyfile.key" --cert="certfile.crt" -n default -o yaml --dry-run=client +kubectl apply -f path/to/your/tls_secret.yaml +``` + +**upstream_certificates** + +The `upstream_certificates` field allows certificates uploaded to the certificate store in Tyk Dashboard to be referenced in the Api Definition: + +```yaml +# Skip the concatenation and .pem file creation if you already have a certificate in the correct format + +# First generate your public-private key pair , for test use you can use the following command to obtain one fast: +# "openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out tls.crt -keyout tls.key" + +# Concatenate the above files to obtain a .pem file which we will upload using the dashboard UI +# "cat tls.crt tls.key > cert.pem" + +# Upload it to the tyk certificate store using the dashboard + +# Fill in the manifest with the certificate id (the long hash) that you see is given to it in the dashboard +# (in place of "INSERT UPLOADED CERTIFICATE NAME FROM DASHBOARD HERE") +# Optional: Change the domain from "*" to something more specific if you need to use different +# upstream certificates for different domains + +# Then apply this manifest using the command +# "kubectl apply -f config/samples/httpbin_upstream_cert_manual.yaml" + +# The operator will try create the ApiDefinition and will succeed and it will have the requested domain upstream certificate +# in the cluster for an upstream + +# NOTE: the upstream_certificate can hold multiple domain-certificateName pairs +# (currently "*" is used as a wildcard for all the domains) + +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + upstream_certificates: + "*": #INSERT UPLOADED CERTIFICATE NAME FROM DASHBOARD HERE# + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default +``` + + + + +When using Tyk Classic APIs with Tyk Operator you can configure certificate pinning using one of the following fields within the ApiDefinition object: + +- **pinned_public_keys**: Use public keys uploaded via the Certificate API. +- **pinned_public_keys_refs**: Uses public keys configured from Kubernetes secret objects. + +###### pinned_public_keys + +Use the `pinned_public_keys` mapping to pin public keys to specific domains, referencing public keys that have been uploaded to Tyk Certificate storage via the Certificate API. + +```yaml +pinned_public_keys: + "foo.com": "", + "*": "," +``` + +Each `key-id` value should be set to the ID returned from uploading the public key via the Certificate API. Multiple public keys can be specified by separating their IDs by a comma. + +
+ +###### pinned_public_keys_refs + +The `pinned_public_keys_refs` mapping should be used to configure pinning of public keys sourced from Kubernetes secret objects for different domains. + +Each key should be set to the name of the domain and the value should refer to the name of a Kuberenetes secret object that holds the corresponding public key for that domain. + +Wildcard domains are supported and "*" can be used to denote all domains. + + + +**Caveats** + +- Only *kubernetes.io/tls* secret objects are allowed. +- Please use the *tls.crt* field for the public key. +- The secret that includes a public key must be in the same namespace as the ApiDefinition object. + + + +The example below illustrates a scenario where the public key from the Kubernetes secret object, *httpbin-secret*, is used for all domains, denoted by the wildcard character '*'. In this example the *tls.crt* field of the secret is set to the actual public key of *httpbin.org*. Subsequently, if you any URL other than https://httpbin.org is targetted (e.g. https://github.com/) a *public key pinning error* will be raised for that particular domain. This is because the public key of *httpbin.org* has been configured for all domains. + +```yaml +# ApiDefinition object 'pinned_public_keys_refs' field uses the following format: +# spec: +# pinned_public_keys_refs: +# "domain.org": # the name of the Kubernetes Secret Object that holds the public key for the 'domain.org'. +# +# In this way, you can refer to Kubernetes Secret Objects through 'pinned_public_keys_refs' field. +# +# In this example, we have an HTTPS upstream target as `https://httpbin.org`. The public key of httpbin.org is obtained +# with the following command: +# $ openssl s_client -connect httpbin.org:443 -servername httpbin.org 2>/dev/null | openssl x509 -pubkey -noout +# +# Note: Please set tls.crt field of your secret to actual public key of httpbin.org. +# +# We are creating a secret called 'httpbin-secret'. In the 'tls.crt' field of the secret, we are specifying the public key of the +# httpbin.org obtained through above `openssl` command, in the decoded manner. +# +apiVersion: v1 +kind: Secret +metadata: + name: httpbin-secret +type: kubernetes.io/tls +data: + tls.crt: # Use tls.crt field for the public key. + tls.key: "" +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-certificate-pinning +spec: + name: httpbin - Certificate Pinning + use_keyless: true + protocol: http + active: true + pinned_public_keys_refs: + "*": httpbin-secret + proxy: + target_url: https://httpbin.org + listen_path: /pinning + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default +``` +
+
+ +
+ diff --git a/api-management/upstream-authentication/oauth.mdx b/api-management/upstream-authentication/oauth.mdx new file mode 100644 index 0000000000..c3d14aab17 --- /dev/null +++ b/api-management/upstream-authentication/oauth.mdx @@ -0,0 +1,269 @@ +--- +title: "Upstream Authentication using OAuth" +description: "How to authenticate upstream service using oauth" +keywords: "security, upstream authentication, gateway to upstream, oauth" +sidebarTitle: "OAuth 2.0" +--- + +## Availability + +| Component | Editions | +| :----------- | :---------- | +| Gateway and Dashboard | Enterprise | + +## Upstream OAuth 2.0 + +OAuth 2.0 is an open standard authorization protocol that allows services to provide delegated and regulated access to their APIs; critically the user credentials are not shared with the upstream service, instead the client authenticates with a separate Authentication Server which issues a time-limited token that the client can then present to the upstream (Resource Server). The upstream service validates the token against the Authentication Server before granting access to the client. + +The Authentication Server (auth server) has the concept of an OAuth Client - this is equivalent to the client's account on the auth server. There are different ways that a client can authenticate with the auth server, each with their own advantages and disadvantages for different use cases. + +The auth server is often managed by a trusted third party Identity Provider (IdP) such as Okta or Auth0. + +Tyk supports OAuth 2.0 as a method for authenticating **clients** with the **Gateway** - you can use Tyk's own auth server functionality via the [Tyk OAuth 2.0](/api-management/authentication/oauth-2) auth method or obtain the access token via a third party auth server and use the [JWT Auth](/basic-config-and-security/security/authentication-authorization/json-web-tokens) method. + +If your **upstream service** is protected using OAuth 2.0 then similarly, Tyk will need to obtain a valid access token to provide in the request to the upstream. + +Tyk supports two different OAuth grant types for connecting to upstream services: +- [Client credentials](#oauth-client-credentials) +- [Resource owner password credentials](#oauth-resource-owner-password-credentials) + +#### OAuth client credentials + +The client credentials grant relies upon the client providing an id and secret (the *client credentials*) to the auth server. These are checked against the list of OAuth Clients that it holds and, if there is a match, it will issue an access token that instructs the Resource Server which resources that client is authorized to access. For details on configuring Tyk to use Upstream Client Credentials see [below](#configuring-upstream-oauth-20-client-credentials-in-the-tyk-oas-api-definition). + +#### OAuth resource owner password credentials + +The resource owner password credentials grant (also known simply as **Password Grant**) is a flow where the client must provide both their own credentials (client Id and secret) and a username and password identifying the resource owner to the auth server to obtain an access token. Thus the (upstream) resource owner must share credentials directly with the client. This method is considered unsafe and is prohibited in the [OAuth 2.0 Security Best Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13#section-3.4") but is supported by Tyk for use with legacy upstream services. For details on configuring Tyk to use Upstream Password Grant see [below](#configuring-upstream-oauth-20-password-grant-in-the-tyk-oas-api-definition). + +### How to use Upstream OAuth 2.0 for Authentication + +If your upstream service requires that Tyk authenticates via an OAuth auth server, you will first need to obtain credentials for the OAuth Client created in the auth server. You select which grant type to use and provide the required credentials in the API definition. + +To enhance security by restricting visibility of the credentials, these can be stored in a [key-value store](/tyk-self-managed/install), with only references included in the API definition. + +Some auth servers will return *additional metadata* with the access token (for example, the URL of the upstream server that should be addressed using the token if this can vary per client). Tyk can accommodate this using the optional `extraMetadata` field in the API definition. The response from the auth server will be parsed for any fields defined in `extraMetadata`; any matches will be saved to the request context where they can be accessed from other middleware (for our example, the URL rewrite middleware could be used to [transform the upstream target URL](/transform-traffic/url-rewriting)). + +#### Configuring Upstream OAuth 2.0 Client Credentials in the Tyk OAS API definition + +Upstream Authentication is configured per-API in the Tyk extension (`x-tyk-api-gateway`) within the Tyk OAS API definition by adding the `authentication` section within the `upstream` section. + +Set `upstream.authentication.enabled` to `true` to enable upstream authentication. + +For OAuth 2.0 Client Credentials, you will need to add the `oauth` section within `upstream.authentication`. + +This has the following parameters: +- `enabled` set this to `true` to enable upstream OAuth authentication +- `allowedAuthorizeTypes` should include the value `clientCredentials` +- `clientCredentials` should be configured with: + - `tokenUrl` is the URL of the `/token` endpoint on the *auth server* + - `clientId` is the client ID to be provided to the *auth server* + - `clientSecret` is the client secret to be provided to the *auth server* + - `scopes` is an optional array of authorization scopes to be requested + - `extraMetadata` is an optional array of additional fields to be extracted from the *auth server* response + - `header.enabled` must be set to `true` if your upstream expects the credentials to be in a custom header, otherwise it can be omitted to use `Authorization` header + - `header.name` is the custom header to be used if `header.enabled` is set to `true` + +Note that if you use the [Tyk API Designer](/api-management/upstream-authentication/basic-auth#configuring-upstream-basic-auth-using-the-api-designer) in Tyk Dashboard it will always configure the `header` parameter - even if you are using the default `Authorization` value. + +For example: + +```json {hl_lines=["43-62"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-upstream-client-credentials", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "servers": [ + { + "url": "http://localhost:8181/example-upstream-client-credentials/" + } + ], + "security": [], + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "components": { + "securitySchemes": {} + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-upstream-client-credentials", + "state": { + "active": true + } + }, + "server": { + "listenPath": { + "strip": true, + "value": "/example-upstream-client-credentials/" + } + }, + "upstream": { + "url": "https://httpbin.org/", + "authentication": { + "enabled": true, + "oauth": { + "enabled": true, + "allowedAuthorizeTypes": [ + "clientCredentials" + ], + "clientCredentials": { + "header": { + "enabled": true, + "name": "Authorization" + }, + "tokenUrl": "http:///token", + "clientId": "client123", + "clientSecret": "secret123", + "scopes": ["scope1"], + "extraMetadata": ["instance_url"] + } + } + } + } + } +} +``` + +In this example upstream authentication has been enabled (line 44). The authentication method to be used is indicated in lines 46 (OAuth) and 48 (client credentials). When a request is made to the API, Tyk will request an access token from the *authorization server* at `http://` providing client credentials and the scope `scope1`. + +Tyk will parse the response from the *authorization server* for the key `instance_url`, storing any value found in the *request context* were it can be accessed by other middleware as `$tyk_context.instance_url` (note the rules on accessing [request context variables from middleware](/api-management/traffic-transformation/request-context-variables)). + +On receipt of an access token from the *authorization server*, Tyk will proxy the original request to the upstream server (`https://httpbin.org/`) passing the access token in the `Authorization` header. + +If you replace the `upstream.url` and *authorization server* details with valid details, then the configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the Upstream OAuth 2.0 Client Credentials feature. + +#### Configuring Upstream OAuth 2.0 Client Credentials using the API Designer + +Upstream Authentication is configured from the **Settings** tab of the Tyk OAS API Designer, where there is a dedicated section within the **Upstream** section. + +Select **OAuth** from the choice in the **Authentication Method** drop-down, then you can provide the header name, authorization server token URL and select **Client Credentials** to reveal the configuration for the credentials to be passed to the auth server. + +Tyk OAS API Designer showing Upstream OAuth client credentials configuration options + +#### Configuring Upstream OAuth 2.0 Password Grant in the Tyk OAS API definition + +Upstream Authentication is configured per-API in the Tyk extension (`x-tyk-api-gateway`) within the Tyk OAS API definition by adding the `authentication` section within the `upstream` section. + +Set `upstream.authentication.enabled` to `true` to enable upstream authentication. + +For OAuth 2.0 Resource Owner Password Credentials (*Password Grant*), you will need to add the `oauth` section within `upstream.authentication`. + +This has the following parameters: +- `enabled` set this to `true` to enable upstream OAuth authentication +- `allowedAuthorizeTypes` should include the value `password` +- `password` should be configured with: + - `tokenUrl` is the URL of the `/token` endpoint on the *auth server* + - `clientId` is the client ID to be provided to the *auth server* + - `clientSecret` is the client secret to be provided to the *auth server* + - `username` is the Resource Owner username to be provided to the *auth server* + - `password` is the Resource Owner password to be provided to the *auth server* + - `scopes` is an optional array of authorization scopes to be requested + - `extraMetadata` is an optional array of additional fields to be extracted from the *auth server* response + - `header.enabled` must be set to `true` if your upstream expects the credentials to be in a custom header, otherwise it can be omitted to use `Authorization` header + - `header.name` is the custom header to be used if `header.enabled` is set to `true` + +Note that if you use the [Tyk API Designer](/api-management/upstream-authentication/basic-auth#configuring-upstream-basic-auth-using-the-api-designer) in Tyk Dashboard it will always configure the `header` parameter - even if you are using the default `Authorization` value. + +For example: + +```json {hl_lines=["43-64"],linenos=true, linenostart=1} +{ + "info": { + "title": "example-upstream-password-grant", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "servers": [ + { + "url": "http://localhost:8181/example-upstream-password-grant/" + } + ], + "security": [], + "paths": { + "/anything": { + "get": { + "operationId": "anythingget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "components": { + "securitySchemes": {} + }, + "x-tyk-api-gateway": { + "info": { + "name": "example-upstream-password-grant", + "state": { + "active": true + } + }, + "server": { + "listenPath": { + "strip": true, + "value": "/example-upstream-password-grant/" + } + }, + "upstream": { + "url": "https://httpbin.org/", + "authentication": { + "enabled": true, + "oauth": { + "enabled": true, + "allowedAuthorizeTypes": [ + "password" + ], + "password": { + "header": { + "enabled": true, + "name": "Authorization" + }, + "tokenUrl": "http:///token", + "clientId": "client123", + "clientSecret": "secret123", + "username": "user123", + "password": "pass123", + "scopes": ["scope1"], + "extraMetadata": ["instance_url"] + } + } + } + } + } +} +``` + +In this example upstream authentication has been enabled (line 44). The authentication method to be used is indicated in lines 46 (OAuth) and 48 (password grant). When a request is made to the API, Tyk will request an access token from the *authorization server* at `http://` providing client credentials, resource owner credentials and the scope `scope1`. + +Tyk will parse the response from the *authorization server* for the key `instance_url`, storing any value found in the *request context* were it can be accessed by other middleware as `$tyk_context.instance_url` (note the rules on accessing [request context variables from middleware](/api-management/traffic-transformation/request-context-variables)). + +On receipt of an access token from the *authorization server*, Tyk will proxy the original request to the upstream server (`https://httpbin.org/`) passing the access token in the `Authorization` header. + +If you replace the `upstream.url` and *authorization server* details with valid details, then the configuration above is a complete and valid Tyk OAS API Definition that you can import into Tyk to try out the Upstream OAuth 2.0 Password Grant feature. + +#### Configuring Upstream OAuth 2.0 Password Grant using the API Designer + +Upstream Authentication is configured from the **Settings** tab of the Tyk OAS API Designer, where there is a dedicated section within the **Upstream** section. + +Select **OAuth** from the choice in the **Authentication Method** drop-down, then you can provide the header name, authorization server token URL and select **Resource Owner Password Credentials** to reveal the configuration for the credentials to be passed to the auth server. + +Tyk OAS API Designer showing Upstream OAuth password grant configuration options + + + +Any error encountered in the communication with the OAuth server will generate an `UpstreamOAuthError` event. This event can be used to trigger an event handler, for example you could use a [webhook](/api-management/gateway-events) to alert the system administrator of the issue. + diff --git a/api-management/upstream-authentication/request-signing.mdx b/api-management/upstream-authentication/request-signing.mdx new file mode 100644 index 0000000000..3a921c3422 --- /dev/null +++ b/api-management/upstream-authentication/request-signing.mdx @@ -0,0 +1,133 @@ +--- +title: "Upstream Authentication using Request Signing" +description: "How to authenticate upstream service using request signing" +keywords: "security, upstream authentication, gateway to upstream, request signing" +sidebarTitle: "Request Signing" +--- + +## Request signing + +Request Signing is an access token method that adds another level of security where the client generates a unique signature that identifies the request temporally to ensure that the request is from the requesting user, using a secret key that is never broadcast over the wire. + +Tyk can apply either the symmetric Hash-Based Message Authentication Code (HMAC) or asymmetric Rivest-Shamir-Adleman (RSA) algorithms when generating the signature for a request to be sent upstream. For HMAC, Tyk supports different options for the hash length. + +The following algorithms are supported: + +| Hashing algorithm | Tyk identifier used in API definition | +| :------------------- | :--------------------------------------- | +| HMAC SHA1 | `hmac-sha1` | +| HMAC SHA256 | `hmac-sha256` | +| HMAC SHA384 | `hmac-sha384` | +| HMAC SHA512 | `hmac-sha512` | +| RSA SHA256 | `rsa-sha256` | + +This feature is implemented using [Draft 10](https://tools.ietf.org/html/draft-cavage-http-signatures-10) RFC. The signatures generated according to this standard are temporal - that is, they include a time stamp. If there is no `Date` header in the request that is to be proxied to the upstream, Tyk will add one. + +### Configuring Request Signing in the API definition +Upstream Authentication is configured per-API in the Tyk Vendor Extension by adding the authentication section within the `upstream` section. + +For Request Signing, you must configure [upstream.authentication.upstreamRequestSigning](/api-management/gateway-config-tyk-oas#upstreamrequestsigning), providing the following settings: + +- the `signatureHeader` in which the signature should be sent (typically `Authorization`) +- the `algorithm` to be used to generate the signature (from the table above) +- the `secret` to be used in the encryption operation +- optional `headers` that Tyk should include in the string that is encrypted to generate the signature +- the `keyId` that the upstream will use to identify Tyk as the client (used for HMAC encryption) +- the `certificateId` that the upstream will use to identify Tyk as the client (used for RSA encryption) + +The Tyk Classic equivalent is [request_signing](/api-management/gateway-config-tyk-classic#upstream-authentication). + +### Configuring Request Signing with Tyk Operator + +When using Tyk Operator, the `certificateId` and `secret` are encapsulated in Kubernetes references: +- `certificateRef`: references a Secret containing the private and secret key. +- `secretRef`: references a Kubernetes Secret that holds the secret used in the encryption operation. + +For example: + +```yaml{linenos=true, linenostart=1, hl_lines=["66-73"]} + apiVersion: v1 + data: + secretKey: cGFzc3dvcmQxMjM= + kind: Secret + metadata: + name: upstream-secret + namespace: default + type: Opaque + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: booking + namespace: default + data: + test_oas.json: |- + { + "info": { + "title": "bin", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "bin", + "state": { + "active": true, + "internal": false + } + }, + "server": { + "listenPath": { + "strip": true, + "value": "/bin/" + } + }, + "upstream": { + "url": "http://httpbin.org/", + "authentication": { + "requestSigning": { + "enabled": true, + "signatureHeader": "Signature", + "algorithm": "hmac-sha256", + "keyId": "random-key-id", + "headers": [], + "secret": "" + } + } + } + } + } + --- + apiVersion: tyk.tyk.io/v1alpha1 + kind: TykOasApiDefinition + metadata: + name: booking + namespace: default + spec: + tykOAS: + configmapRef: + namespace: default + name: booking + keyName: test_oas.json + upstreamRequestSigning: + certificateRef: "" + secretRef: + namespace: default + name: upstream-secret + secretKey: secretKey + algorithm: "hmac-sha256" + keyId: "" + ``` +In this example, a Tyk OAS API was created using the `upstreamRequestSigning` field. It can be broken down as follows: +- `upstreamRequestSigning`: This defines the settings for Upstream Request Signing. in the example manifest, it configures Upstream Request Signing using the `booking` API. + - `certificateRef`: References a Secret containing the private and secret key for signing client API requests. This should be used if `secretRef` is not specified. + - `secretRef`: References a Kubernetes Secret that holds the secret key for signing client requests. + - `algorithm`: Specifies the algorithm used for signing. + - For `secretRef`, supported algorithms include: `hmac-sha1`, `hmac-sha256`, `hmac-sha384`, and `hmac-sha512`. + - For `certificateRef`, the required algorithm is `rsa-sha256`. + - `keyId`: A user-defined key assumed to be available on the upstream service. This is used in the `SignatureHeader` and should be included when using `certificateRef`. It is required when using the RSA algorithm. + +
+ diff --git a/api-management/user-management.mdx b/api-management/user-management.mdx new file mode 100644 index 0000000000..50af3efabd --- /dev/null +++ b/api-management/user-management.mdx @@ -0,0 +1,76 @@ +--- +title: "User Management with Tyk Dashboard" +description: "Learn how to manage users, teams, permissions, and Role-Based Access Control (RBAC) in the Tyk Dashboard" +keywords: "Dashboard, User Management, RBAC, Role Based Access Control, User Groups, Teams, Permissions, API Ownership, SSO, Single Sign On, Multi Tenancy" +sidebarTitle: "Overview" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + +## Introduction + +Tyk Dashboard provides you with the ability to manage Users, Teams and Permissions enabling organizations to maintain robust control over access and visibility. These capabilities empower teams to manage large-scale API portfolios, mitigate risks of unauthorized access, and reduce operational complexity. + +In this section, we delve into the following key topics: + +1. **[Managing Users](/platform-management/dashboard-users)**: + Streamlining user administration by creating, updating, and deactivating accounts within the Tyk Dashboard using both the UI and API, including password management, password policy and user search. +2. **[Managing User Permissions](/platform-management/user-permissions)**: + Configuring and enforcing role-based access control for users within the Tyk Dashboard, using both the API and UI. +3. **[Managing User Groups/Teams](/platform-management/user-groups)**: + Organizing users into groups or teams to simplify role assignment, permissions management, and collaborative workflows within the Tyk Dashboard. +4. **[Configuring API Ownership](/platform-management/api-ownership)**: + Applying role-based access control to APIs to govern visibility and manageability for specific teams or users. +5. **[Managing Users across Multiple Tyk Organizations](#manage-tyk-dashboard-users-in-multiple-organizations)**: + Administering user access and roles across multiple organizations, ensuring consistent and secure management in multi-tenant setups. +6. **[Single Sign-On](#single-sign-on-integration)**: + Integrating and configuring Single Sign-On (SSO) solutions to streamline authentication and enhance security across the Tyk Dashboard. + +
+ + +The availability of some features described in this section depends on your license. +
+For further information, please check our [price comparison](https://tyk.io/price-comparison/) or consult our sales and expert engineers: + +
+ + +## Understanding "User" in Tyk + +In the context of Tyk, a User refers to an individual responsible for managing, configuring, and maintaining the Tyk API Gateway and its related components. These users interact with the Tyk Dashboard and API to control various aspects such as API management, user permissions, security policies, and organizational settings. This term does not refer to end-users or consumers of the APIs managed through Tyk but specifically to administrators and developers operating the Tyk ecosystem. + +## Initial Admin User Creation + +When you start the Tyk Dashboard the first time, the bootstrap process creates an initial "user" for you with admin permissions, which allows them access to control and configure everything in the Dashboard (via the UI or Tyk Dashboard API). + + +## Manage Tyk Dashboard Users in Multiple Organizations + +If you have deployed multiple [Tyk Organizations](/dashboard-admin-api#organizations), you may have users that need access to more than one Organization (known as a "multi-org user"). **This functionality requires a specific Tyk license.** + +To support multi-org users, you must first enable the feature in your Dashboard configuration by setting either of the following to `true`: + - `"enable_multi_org_users"` in `tyk_analytics.conf` + - `TYK_DB_ENABLEMULTIORGUSERS` environment variable + +You then must create users in both Organizations with identical credentials. + +During the login flow the user will see an additional page asking them to pick which available Organization they wish to log into. Once logged in, the user will have an additional drop-down in the top right navigation menu allowing them to switch between Organizations quickly. + + + +A user that does not belong to an Organization is sometimes referred to as an *unbounded user*. These users have visibility across all Organizations, but should be granted read-only access. + + + +## Single Sign-On Integration + +Tyk Identity Broker (TIB) enables Single Sign-On (SSO) for Tyk Dashboard, allowing users to authenticate with an external identity provider instead of a separate Dashboard account. + + +By default, users who log in via SSO are granted admin permissions. You must configure either default permissions or a default user group to prevent this. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso#unregistered-user-login) for full details on configuring SSO behavior, user permissions, and user group mapping. + + +For full SSO documentation including setup guides for specific identity providers, see: +- [SSO into Tyk Dashboard](/tyk-identity-broker/dashboard-sso) +- [Tyk Identity Broker Overview](/tyk-identity-broker/overview) diff --git a/apim.mdx b/apim.mdx new file mode 100644 index 0000000000..4e45d615a2 --- /dev/null +++ b/apim.mdx @@ -0,0 +1,53 @@ +--- +title: "Tyk API Management Deployment Options" +description: "A guide to choosing the best Tyk deployment option for your API management needs" +keywords: "Tyk API Management, Licensing, Open Source, Self-Managed, Tyk Cloud, API Gateway" +sidebarTitle: "Deployment Options" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; +import SelfManagedLicensingInclude from '/snippets/self-managed-licensing-include.mdx'; + +Tyk API Platform offers various deployment options, consisting of both [open source and proprietary](/tyk-stack) +components. + +Choosing the right one for your organization depends on your specific requirements and preferences. +
Don’t hesitate to contact us for assistance + +| | [Open Source](/tyk-open-source) | [Self-Managed](/tyk-self-managed/install) | [Cloud](https://account.cloud-ara.tyk.io/signup) +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|-------------------|--------- +| API Gateway Capabilities
  • Rate Limiting
  • Authentication
  • API Versioning
  • Granular Access Control
  • GraphQL
  • and [much more](/tyk-open-source)
| ✅ |✅ |✅ +| [Version Control](/api-management/automations/sync) Integration | - |✅ |✅ +| [API Analytics Exporter](/api-management/tyk-pump) | ✅ |✅ |✅ +| [Tyk Dashboard](/api-management/dashboard-configuration) | - |✅ |✅ +| [Single Sign On (SSO)](/tyk-identity-broker/dashboard-sso) | - |✅ |✅ +| [RBAC and API Teams](/api-management/user-management) | - |✅ |✅ +| [Universal Data Graph](/api-management/data-graph#overview) | - |✅ |✅ +| [Multi-Tenant](/dashboard-admin-api#organizations) | - |✅ |✅ +| [Multi-Data Center](/api-management/mdcb#managing-geographically-distributed-gateways-to-minimize-latency-and-protect-data-sovereignty) | - |✅ |✅ +| [Developer Portal](/portal/overview/intro) | - |✅ |✅ +| [Developer API Analytics](/api-management/dashboard-analytics#traffic-analytics) | - |✅ |✅ +| Hybrid Deployments | - |- |✅ +| Fully-Managed SaaS | - |- |✅ +| [HIPAA, SOC2, PCI](https://tyk.io/governance-and-auditing/) | ✅ |✅ | - + + +## Licensing + +### Self-managed (On-Prem) + + + +### Cloud (Software as a Service / SaaS) + +Tyk cloud is a fully managed service that makes it easy for API teams to create, secure, publish and maintain APIs at any scale, anywhere in the world. Tyk Cloud includes everything you need to manage your global API ecosystem. + +Get your free account [here](https://tyk.io/sign-up/). + +### Open Source (OSS) + +The Tyk Gateway is the backbone of all our solutions and can be deployed for free, forever. It offers various [installation options](/apim/open-source/installation) to suit different needs. + +Visit the [OSS section](/tyk-open-source) for more information on it and other open source components. + +Explore the various open and closed source [Tyk components](/tyk-stack) that are part of the Tyk platform solutions. diff --git a/apim/open-source/installation.mdx b/apim/open-source/installation.mdx new file mode 100644 index 0000000000..2ea4af732b --- /dev/null +++ b/apim/open-source/installation.mdx @@ -0,0 +1,686 @@ +--- +title: "Installation Options for Tyk Gateway" +description: "This page serves as a comprehensive guide to installing Tyk Gateway Open Source" +keywords: "installation, migration, open source" +sidebarTitle: "Installation Options" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; + +## Introduction + +The backbone of all our products is our open source Gateway. You can install our Open Source / Community Edition on the following platforms: + + + + + +**Read time: 2 mins** + +Install with Docker. + + + +**Read time: 10 mins** + +Install with K8s. + + + +**Read time: 10 mins** + +Install with Ansible. + + + +**Read time: 10 mins** + +Install on RHEL / CentOS. + + + +**Read time: 10 mins** + +Install on Debian / Ubuntu. + + + +**Read time: 10 mins** + +Visit our Gateway GitHub Repo. + + + + + +## Install Tyk Gateway with Kubernetes + +The main way to install the Open Source *Tyk Gateway* in a Kubernetes cluster is via Helm charts. +We are actively working to add flexibility and more user flows to our chart. Please reach out +to our teams on support or the community forum if you have questions, requests or suggestions for improvements. + +Get started with our [Quick Start guide](#quick-start-with-helm-chart) or go to [Tyk Open Source helm chart](/product-stack/tyk-charts/tyk-oss-chart) for detailed installation instructions and configuration options. + +### Quick Start with Helm Chart + +At the end of this quick start, Tyk Gateway should be accessible through the service `gateway-svc-tyk-oss-tyk-gateway` at port `8080`. +The following guides provide instructions to install Redis and Tyk Open Source with default configurations. It is intended for a quick start only. For production, you should install and configure Redis separately. + +#### Prerequisites + +1. [Kubernetes 1.19+](https://kubernetes.io/docs/setup/) +2. [Helm 3+](https://helm.sh/docs/intro/install/) + +#### Steps for Installation + +1. **Install Redis and Tyk** + +```bash +NAMESPACE=tyk-oss +APISecret=foo +REDIS_BITNAMI_CHART_VERSION=19.0.2 + +helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ +helm repo update + +helm upgrade tyk-redis oci://registry-1.docker.io/bitnamicharts/redis -n $NAMESPACE --create-namespace --install --version $REDIS_BITNAMI_CHART_VERSION --set image.repository=bitnamilegacy/redis + +helm upgrade tyk-oss tyk-helm/tyk-oss -n $NAMESPACE --create-namespace \ + --install \ + --set global.secrets.APISecret="$APISecret" \ + --set global.redis.addrs="{tyk-redis-master.$NAMESPACE.svc.cluster.local:6379}" \ + --set global.redis.passSecret.name=tyk-redis \ + --set global.redis.passSecret.keyName=redis-password +``` + +2. **Done!** + +Now Tyk Gateway should be accessible through service `gateway-svc-tyk-oss-tyk-gateway` at port `8080`. + +You are now ready to [create an API](/api-management/gateway-config-managing-classic#create-an-api). + +For the complete installation guide and configuration options, please see [Tyk OSS Helm Chart](/product-stack/tyk-charts/tyk-oss-chart). + +### Configure Legacy Tyk Headless Helm Chart + + +`tyk-headless` chart is deprecated. Please use our Tyk Chart for Tyk Open Source at [tyk-oss](#quick-start-with-helm-chart) instead. + +We recommend all users migrate to the `tyk-oss` Chart. Please review the [Configuration](#quick-start-with-helm-chart) section of the new helm chart and cross-check with your existing configurations while planning for migration. + + + +This is the preferred (and easiest) way to install the Tyk OSS Gateway on Kubernetes. +It will install Tyk gateway in your Kubernetes cluster where you can add and manage APIs directly or via the *Tyk Operator*. + +#### Prerequisites + +The following are required for a Tyk OSS installation: +1. Redis - required for all the Tyk installations and must be installed in the cluster or reachable from inside K8s. + You can find instructions for a simple Redis installation below. +2. MongoDB/SQL - Required only if you choose to use the MongoDB/SQL Tyk pump with your Tyk OSS installation. The same goes for any + [other pump](/api-management/logs/external-data-sinks) you choose to use. +3. Helm - Tyk Helm supports the Helm 3+ version. + +#### Steps for Installation + +As well as our official OSS Helm repo, you can also find it in [ArtifactHub](https://artifacthub.io/packages/helm/tyk-helm/tyk-headless). +[Open in ArtifactHub](https://artifacthub.io/packages/helm/tyk-helm/tyk-headless) + +If you are interested in contributing to our charts, suggesting changes, creating PRs, or any other way, +please use [GitHub Tyk-helm-chart repo](https://github.com/TykTechnologies/tyk-helm-chart/tree/master/tyk-headless) + +1. **Add Tyk official Helm repo** + +```bash +helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ +helm repo update +``` + +2. **Create a namespace for Tyk deployment** + +```bash +kubectl create namespace tyk +``` + +3. **Getting values.yaml** + +Before we proceed with installation of the chart you may need to set some custom values. +To see what options are configurable on a chart and save those options to a custom `values.yaml` file run: + +```bash +helm show values tyk-helm/tyk-headless > values.yaml +``` + +Some of the necessary configuration parameters will be explained in the next steps. + +4. **Installing Redis** + +* Recommended: via *Bitnami* chart - For Redis, you can use these rather excellent chart provided by Bitnami. +Copy the following commands to add it: + + ```bash + helm repo add bitnami https://charts.bitnami.com/bitnami + helm install tyk-redis bitnami/redis -n tyk --version 19.0.2 --set image.repository=bitnamilegacy/redis + ``` + + + +Please make sure you are installing Redis versions that are supported by Tyk. Please refer to Tyk docs to get a list of [supported versions](/tyk-configuration-reference/redis-cluster-sentinel#supported-versions). + + + +Follow the notes from the installation output to get connection details and password. + +``` + Redis(TM) can be accessed on the following DNS names from within your cluster: + + tyk-redis-master.tyk.svc.cluster.local for read/write operations (port 6379) + tyk-redis-replicas.tyk.svc.cluster.local for read-only operations (port 6379) + + export REDIS_PASSWORD=$(kubectl get secret --namespace tyk tyk-redis -o jsonpath="{.data.redis-password}" | base64 --decode) +``` + +The DNS name of your Redis as set by Bitnami is `tyk-redis-master.tyk.svc.cluster.local:6379` +You can update them in your local `values.yaml` file under `redis.addrs` and `redis.pass` +Alternatively, you can use `--set` flag to set it in the Tyk installation. For example `--set redis.pass=$REDIS_PASSWORD` + +**For evaluation only: Use *simple-redis* chart** + + + +Another option for Redis, to get started quickly, is to use our *simple-redis* chart. +Please note that these provided charts must never be used in production or for anything +but a quick start evaluation only. Use Bitnami Redis or Official Redis Helm chart in any other case. +We provide this chart, so you can quickly deploy *Tyk gateway*, but it is not meant for long-term storage of data. + + + +```bash +helm install redis tyk-helm/simple-redis -n tyk +``` + +5. **Installing Tyk Open Source Gateway** + +```bash +helm install tyk-ce tyk-helm/tyk-headless -f values.yaml -n tyk + ``` + +Please note that by default, Gateway runs as `Deployment` with `ReplicaCount` as 1. You should not update this part because multiple instances of OSS gateways won't sync the API Definition. + +#### Installation Video + +See our short video on how to install the Tyk Open Source Gateway. +Please note that this video shows the use of the Github repository since it was recorded before the official repo was available, However, +it's very similar to the above commands. + + + +#### Pump Installation +By default pump installation is disabled. You can enable it by setting `pump.enabled` to `true` in `values.yaml` file. +Alternatively, you can use `--set pump.enabled=true` while doing Helm install. + +**Quick Pump configuration(Supported from tyk helm v0.10.0)** +*1. Mongo Pump* + +To configure the Mongo pump, make the following changes in `values.yaml` file: +1. Set `backend` to `mongo`. +2. Set connection string in `mongo.mongoURL`. + +*2. Postgres Pump* + +To configure the Postgres pump, make the following changes in `values.yaml` file: +1. Set `backend` to `postgres`. +2. Set connection string parameters in `postgres` section. + +#### Optional - Using TLS +You can turn on the TLS option under the gateway section in your local `values.yaml` file which will make your Gateway +listen on port 443 and load up a dummy certificate. +You can set your own default certificate by replacing the file in the `certs/` folder. + +#### Optional - Mounting Files +To mount files to any of the Tyk stack components, add the following to the mounts array in the section of that component. + +For example: + ```bash + - name: aws-mongo-ssl-cert + filename: rds-combined-ca-bundle.pem + mountPath: /etc/certs +``` + +#### Optional - Tyk Ingress +To set up an ingress for your Tyk Gateways see our [Tyk Operator GitHub repository](https://github.com/TykTechnologies/tyk-operator). + + +## Install Tyk Gateway with Ansible + +### Prerequisites + +1. [Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) is required to run the following commands. +2. Ensure port `8080` is open: this is used in this guide for Gateway traffic (the API traffic to be proxied). + +### Steps for Installation +1. Clone the [tyk-ansible](https://github.com/TykTechnologies/tyk-ansible) repository + +```bash +$ git clone https://github.com/TykTechnologies/tyk-ansible +``` + +2. `cd` into the directory +```.bash +$ cd tyk-ansible +``` + +3. Run the init script to initialize the environment + +```bash +$ sh scripts/init.sh +``` + +4. Modify the `hosts.yml` file to update SSH variables to your server(s). For more information about the host file, visit the [Ansible inventory documentation] (https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html) + +5. Run ansible-playbook to install `tyk-ce` + +```bash +$ ansible-playbook playbook.yaml -t tyk-ce -t redis +``` + +You can choose to not install Redis by removing the `-t redis`. However, Redis is a requirement and needs to be installed for the gateway to run. + +### Supported Distributions +| Distribution | Version | Supported | +| --------- | :---------: | :---------: | +| Amazon Linux | 2 | ✅ | +| CentOS | 8 | ✅ | +| CentOS | 7 | ✅ | +| Debian | 10 | ✅ | +| Debian | 9 | ✅ | +| RHEL | 9 | ✅ | +| RHEL | 8 | ✅ | +| RHEL | 7 | ✅ | +| Ubuntu | 21 | ✅ | +| Ubuntu | 20 | ✅ | +| Ubuntu | 18 | ✅ | +| Ubuntu | 16 | ✅ | + +### Variables +- `vars/tyk.yaml` + +| Variable | Default | Comments | +| --------- | :---------: | --------- | +| secrets.APISecret | `352d20ee67be67f6340b4c0605b044b7` | API secret | +| secrets.AdminSecret | `12345` | Admin secret | +| redis.host | | Redis server host if different than the host url | +| redis.port | `6379` | Redis server listening port | +| redis.pass | | Redis server password | +| redis.enableCluster | `false` | Enable if Redis is running in cluster mode | +| redis.storage.database | `0` | Redis server database | +| redis.tls | `false` | Enable if Redis connection is secured with SSL | +| gateway.service.host | | Gateway server host if different than the host url | +| gateway.service.port | `8080` | Gateway server listening port | +| gateway.service.proto | `http` | Gateway server protocol | +| gateway.service.tls | `false` | Set to `true` to enable SSL connections | +| gateway.sharding.enabled | `false` | Set to `true` to enable filtering (sharding) of APIs | +| gateway.sharding.tags | | The tags to use when filtering (sharding) Tyk Gateway nodes. Tags are processed as OR operations. If you include a non-filter tag (e.g. an identifier such as `node-id-1`, this will become available to your Dashboard analytics) | + +- `vars/redis.yaml` + +| Variable | Default | Comments | +| --------- | :---------: | --------- | +| redis_bind_interface | `0.0.0.0` | Binding address of Redis | + +Read more about Redis configuration [here](https://github.com/geerlingguy/ansible-role-redis). + +## Install Tyk Gateway with Ubuntu + +The Tyk Gateway can be installed following different installation methods including *Ansible* and *Shell*. Please select by clicking the tab with the installation path most suitable for you. + +### Install Tyk Gateway On Ubuntu Through Shell + +#### + +| Distribution | Version | Supported | +| --------- | :---------: | :---------: | +| Debian | 11 | ✅ | +| Ubuntu | 20 | ✅ | +| Ubuntu | 18 | ✅ | +| Ubuntu | 16 | ✅ | + +#### Prerequisites + +1. Ensure port `8080` is open: this is used in this guide for Gateway traffic (the API traffic to be proxied). + +#### Steps for Installation + +1. **Install Redis** + +```console +$ sudo apt-get install -y redis-server +``` + +2. **First import the public key as required by Ubuntu APT** + +```console +$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927 +``` + +3. **Run Installation Scripts via our PackageCloud Repositories** + +From [https://packagecloud.io/tyk/tyk-gateway](https://packagecloud.io/tyk/tyk-gateway) you have the following options: + +* Via the correct package for your Ubuntu version. We have packages for the following: + * Xenial + * Trusty + * Precise + +* Via Quick Installation Instructions. You can use: + * [Manual Instructions](https://packagecloud.io/tyk/tyk-gateway/install#manual-deb) + * [Chef](https://packagecloud.io/tyk/tyk-gateway/install#chef) + * [Puppet](https://packagecloud.io/tyk/tyk-gateway/install#puppet) + * [CI and Build Tools](https://packagecloud.io/tyk/tyk-gateway/ci) + +4. **Configure The Gateway** + +You can set up the core settings for the Tyk Gateway with a single setup script, however for more involved deployments, you will want to provide your own configuration file. + + + +You need to replace `` for `--redishost=` with your own value to run this script. + + + + +```console +$ sudo /opt/tyk-gateway/install/setup.sh --listenport=8080 --redishost= --redisport=6379 --domain="" +``` + +What you've done here is tell the setup script that: + +* `--listenport=8080`: Listen on port `8080` for API traffic. +* `--redishost=`: The hostname for Redis. +* `--redisport=6379`: Use port `6379` for Redis. +* `--domain=""`: Do not filter domains for the Gateway, see the note on domains below for more about this. + +In this example, you don't want Tyk to listen on a single domain. It is recommended to leave the Tyk Gateway domain unbounded for flexibility and ease of deployment. + +5. **Starting Tyk** + +The Tyk Gateway can be started now that it is configured. Use this command to start the Tyk Gateway: +```console +$ sudo service tyk-gateway start +``` + +### Install Tyk Gateway On Ubuntu Through Ansible + +#### Supported Distributions + +| Distribution | Version | Supported | +| --------- | :---------: | :---------: | +| Debian | 11 | ✅ | +| Ubuntu | 20 | ✅ | +| Ubuntu | 18 | ✅ | +| Ubuntu | 16 | ✅ | + +#### Prerequisites + +Before you begin the installation process, make sure you have the following: +- [Git](https://git-scm.com/download/linux) - required for getting the installation files. +- [Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) is required to run the following commands. +- Ensure port `8080` is open: this is used in this guide for Gateway traffic (the API traffic to be proxied). + +#### Steps for Installation + +1. **Clone the [tyk-ansible](https://github.com/TykTechnologies/tyk-ansible) repository** + +```bash +$ git clone https://github.com/TykTechnologies/tyk-ansible +``` + +2. **`cd` into the directory** +```bash +$ cd tyk-ansible +``` + +3. **Run initalisation script to initialise environment** + +```bash +$ sh scripts/init.sh +``` + +4. Modify `hosts.yml` file to update ssh variables to your server(s). You can learn more about the hosts file [here](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html) + +5. **Run ansible-playbook to install `tyk-gateway-ce`** + +```bash +$ ansible-playbook playbook.yaml -t tyk-gateway-ce -t redis +``` + + +Installation flavors can be specified by using the -t {tag} at the end of the ansible-playbook command. In this case we are using: +-`tyk-gateway-ce`: Tyk Gateway with CE config +-`redis`: Redis database as Tyk Gateway dependency + + + +#### Variables + +- `vars/tyk.yaml` + +| Variable | Default | Comments | +| --------- | :---------: | --------- | +| secrets.APISecret | `352d20ee67be67f6340b4c0605b044b7` | API secret | +| secrets.AdminSecret | `12345` | Admin secret | +| redis.host | | Redis server host if different than the hosts url | +| redis.port | `6379` | Redis server listening port | +| redis.pass | | Redis server password | +| redis.enableCluster | `false` | Enable if redis is running in cluster mode | +| redis.storage.database | `0` | Redis server database | +| redis.tls | `false` | Enable if redis connection is secured with SSL | +| gateway.service.host | | Gateway server host if different than the hosts url | +| gateway.service.port | `8080` | Gateway server listening port | +| gateway.service.proto | `http` | Gateway server protocol | +| gateway.service.tls | `false` | Set to `true` to enable SSL connections | +| gateway.sharding.enabled | `false` | Set to `true` to enable filtering (sharding) of APIs | +| gateway.sharding.tags | | The tags to use when filtering (sharding) Tyk Gateway nodes. Tags are processed as OR operations. If you include a non-filter tag (e.g. an identifier such as `node-id-1`, this will become available to your Dashboard analytics) | + +- `vars/redis.yaml` + +| Variable | Default | Comments | +| --------- | :---------: | --------- | +| redis_bind_interface | `0.0.0.0` | Binding address of Redis | + +Read more about Redis configuration [here](https://github.com/geerlingguy/ansible-role-redis). + + +## Install Tyk Gateway on Red Hat (RHEL / CentOS) + +The Tyk Gateway can be installed following different installation methods including *Shell* and *Ansible*. Please select by clicking the tab with the installation path most suitable for you. + +### Install Tyk Gateway Through Shell + +#### Supported Distributions + +| Distribution | Version | Supported | +| --------- | :---------: | :---------: | +| CentOS | 8 | ✅ | +| CentOS | 7 | ✅ | +| RHEL | 9 | ✅ | +| RHEL | 8 | ✅ | +| RHEL | 7 | ✅ | + +#### Prerequisites + +Before you begin the installation process, make sure you have the following: + +* Ensure port `8080` is open for Gateway traffic (the API traffic to be proxied). +* The Tyk Gateway has a [dependency](/tyk-configuration-reference/redis-cluster-sentinel#supported-versions) on Redis. Follow the steps provided by Red Hat to make the installation of Redis, conducting a [search](https://access.redhat.com/search/?q=redis) for the correct version and distribution. + +#### Steps for Installation +1. **Create Tyk Gateway Repository Configuration** + +Create a file named `/etc/yum.repos.d/tyk_tyk-gateway.repo` that contains the repository configuration settings for YUM repositories `tyk_tyk-gateway` and `tyk_tyk-gateway-source` used to download packages from the specified URLs. This includes GPG key verification and SSL settings, on a Linux system. + +Make sure to replace `el` and `8` in the config below with your Linux distribution and version: +```bash +[tyk_tyk-gateway] +name=tyk_tyk-gateway +baseurl=https://packagecloud.io/tyk/tyk-gateway/el/8/$basearch +repo_gpgcheck=1 +gpgcheck=0 +enabled=1 +gpgkey=https://packagecloud.io/tyk/tyk-gateway/gpgkey +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +metadata_expire=300 + +[tyk_tyk-gateway-source] +name=tyk_tyk-gateway-source +baseurl=https://packagecloud.io/tyk/tyk-gateway/el/8/SRPMS +repo_gpgcheck=1 +gpgcheck=0 +enabled=1 +gpgkey=https://packagecloud.io/tyk/tyk-gateway/gpgkey +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +metadata_expire=300 +``` + +Update your local yum cache by running: +```bash +sudo yum -q makecache -y --disablerepo='*' --enablerepo='tyk_tyk-gateway' +``` + +2. **Install Tyk Gateway** + +Install the Tyk Gateway using yum: +```bash +sudo yum install -y tyk-gateway +``` + + +You may be asked to accept the GPG key for our two repos and when the package installs, hit yes to continue. + + + +3. **Start Redis** + +If Redis is not running then start it using the following command: +```bash +sudo service redis start +``` +4. **Configuring The Gateway** + +You can set up the core settings for the Tyk Gateway with a single setup script, however for more complex deployments you will want to provide your own configuration file. + + + +Replace `` in `--redishost=` with your own value to run this script. + + + +```bash +sudo /opt/tyk-gateway/install/setup.sh --listenport=8080 --redishost= --redisport=6379 --domain="" +``` + +What you've done here is told the setup script that: + +* `--listenport=8080`: Listen on port `8080` for API traffic. +* `--redishost=`: The hostname for Redis. +* `--redisport=6379`: Use port `6379` for Redis. +* `--domain=""`: Do not filter domains for the Gateway, see the note on domains below for more about this. + +In this example, you don't want Tyk to listen on a single domain. It is recommended to leave the Tyk Gateway domain unbounded for flexibility and ease of deployment. + +5. **Start the Tyk Gateway** + +The Tyk Gateway can be started now that it is configured. Use this command to start the Tyk Gateway: +```bash +sudo service tyk-gateway start +``` + +### Install Tyk Gateway Through Ansible + +#### Supported Distributions + +| Distribution | Version | Supported | +| --------- | :---------: | :---------: | +| CentOS | 8 | ✅ | +| CentOS | 7 | ✅ | +| RHEL | 9 | ✅ | +| RHEL | 8 | ✅ | +| RHEL | 7 | ✅ | + +#### Prerequisites +Before you begin the installation process, make sure you have the following: + +1. [Git](https://git-scm.com/download/linux) - required for getting the installation files. +2. [Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) - required for running the commands below. +3. Ensure port `8080` is open: this is used in this guide for Gateway traffic (the API traffic to be proxied). + +#### Steps for Installation + +1. **Clone the [tyk-ansible](https://github.com/TykTechnologies/tyk-ansible) repository** + +```bash +$ git clone https://github.com/TykTechnologies/tyk-ansible +``` + +2. **`cd` into the directory** +```bash +$ cd tyk-ansible +``` + +3. **Run the initalisation script to initialise your environment** + +```bash +$ sh scripts/init.sh +``` + +4. Modify the `hosts.yml` file to update ssh variables to your server(s). You can learn more about the hosts file [here](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html) + +5. **Run ansible-playbook to install `tyk-gateway-ce`** + +```bash +$ ansible-playbook playbook.yaml -t tyk-gateway-ce -t redis +``` + + +Installation flavors can be specified by using the -t {tag} at the end of the ansible-playbook command. In this case we are using: + -`tyk-gateway-ce`: Tyk Gateway with CE config + -`redis`: Redis database as Tyk Gateway dependency + + + +#### Variables +- `vars/tyk.yaml` + +| Variable | Default | Comments | +| --------- | :---------: | --------- | +| secrets.APISecret | `352d20ee67be67f6340b4c0605b044b7` | API secret | +| secrets.AdminSecret | `12345` | Admin secret | +| redis.host | | Redis server host if different than the hosts url | +| redis.port | `6379` | Redis server listening port | +| redis.pass | | Redis server password | +| redis.enableCluster | `false` | Enable if redis is running in cluster mode | +| redis.storage.database | `0` | Redis server database | +| redis.tls | `false` | Enable if redis connection is secured with SSL | +| gateway.service.host | | Gateway server host if different than the hosts url | +| gateway.service.port | `8080` | Gateway server listening port | +| gateway.service.proto | `http` | Gateway server protocol | +| gateway.service.tls | `false` | Set to `true` to enable SSL connections | +| gateway.sharding.enabled | `false` | Set to `true` to enable filtering (sharding) of APIs | +| gateway.sharding.tags | | The tags to use when filtering (sharding) Tyk Gateway nodes. Tags are processed as OR operations. If you include a non-filter tag (e.g. an identifier such as `node-id-1`, this will become available to your Dashboard analytics) | + +- `vars/redis.yaml` + +| Variable | Default | Comments | +| --------- | :---------: | --------- | +| redis_bind_interface | `0.0.0.0` | Binding address of Redis | + +Read more about Redis configuration [here](https://github.com/geerlingguy/ansible-role-redis). + +## Install Tyk Gateway on Killercoda + +[Killercoda](https://killercoda.com/about) gives you instant access to a real Linux or Kubernetes command-line environment via your browser. +You can try this [Killercoda Tyk scenario](https://killercoda.com/tyk-tutorials/scenario/Tyk-install-OSS-docker-compose) to walk through the installation of our Open Source Gateway using Docker Compose (the exact same flow shown above). + diff --git a/basic-config-and-security/security/authentication-authorization/hmac-signatures.mdx b/basic-config-and-security/security/authentication-authorization/hmac-signatures.mdx new file mode 100644 index 0000000000..b5411c0dd3 --- /dev/null +++ b/basic-config-and-security/security/authentication-authorization/hmac-signatures.mdx @@ -0,0 +1,147 @@ +--- +title: "Sign Requests with HMAC" +description: "How to configure HMAC Signatures in Tyk" +keywords: "Authentication, HMAC" +sidebarTitle: "HMAC Signatures" +--- + +## Introduction + +Hash-Based Message Authentication Code (HMAC) Signing is an access token method that adds another level of security by forcing the requesting client to also send along a signature that identifies the request temporally to ensure that the request is from the requesting user, using a secret key that is never broadcast over the wire. + +Tyk currently implements the latest draft of the [HMAC Request Signing standard](http://tools.ietf.org/html/draft-cavage-http-signatures-05). + +HMAC Signing is a good way to secure an API if message reliability is paramount, it goes without saying that all requests should go via TLS/SSL to ensure that MITM attacks can be minimized. There are many ways of managing HMAC, and because of the additional encryption processing overhead requests will be marginally slower than more standard access methods. + +An HMAC signature is essentially some additional data sent along with a request to identify the end-user using a hashed value, in our case we encode the 'date' header of a request, the algorithm would look like: + +``` +Base64Encode(HMAC-SHA1("date: Mon, 02 Jan 2006 15:04:05 MST", secret_key)) +``` + +The full request header for an HMAC request uses the standard `Authorization` header, and uses set, stripped comma-delimited fields to identify the user, from the draft proposal: + +``` +Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64Encode(HMAC-SHA1(signing string))" +``` + +Tyk supports the following HMAC algorithms: "hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512”, and reads value from algorithm header. You can limit the allowed algorithms by setting the `hmac.allowedAlgorithms` (Tyk Classic: `hmac_allowed_algorithms`) field in your API definition, like this: `"hmac_allowed_algorithms": ["hmac-sha256", "hmac-sha512"]`. + +The date format for an encoded string is: + +``` +Mon, 02 Jan 2006 15:04:05 MST +``` + +This is the standard for most browsers, but it is worth noting that requests will fail if they do not use the above format. + +## How Tyk validates the signature of incoming requests + +When an HMAC-signed request comes into Tyk, the key is extracted from the `Authorization` header, and retrieved from Redis. If a key exists then Tyk will generate its own signature based on the request's "date" header, if this generated signature matches the signature in the `Authorization` header the request is passed. + +### Supported headers + +Tyk API Gateway supports full header signing through the use of the `headers` HMAC signature field. This includes the request method and path using the`(request-target)` value. For body signature verification, HTTP Digest headers should be included in the request and in the header field value. + + + +All headers should be in lowercase. + + + +#### Date header not allowed for legacy .Net + +Older versions of some programming frameworks do not allow the Date header to be set, which can causes problems with implementing HMAC, therefore, if Tyk detects a `x-aux-date` header, it will use this to replace the Date header. + +### Clock Skew + +Tyk also implements the recommended clock-skew from the specification to prevent against replay attacks, a minimum lag of 300ms is allowed on either side of the date stamp, any more or less and the request will be rejected. This means that requesting machines need to be synchronised with NTP if possible. + +You can edit the length of the clock skew in the API Definition by setting the `hmac.allowedClockSkew` (Tyk Classic: `hmac_allowed_clock_skew`) value. This value will default to 0, which deactivates clock skew checks. + +## Setting up HMAC using the Dashboard + +To enable the use of HMAC Signing in your API from the Dashboard: + +1. Scroll to the **Authentication** options +2. Select **HMAC (Signed Authentication Key)** from the drop-down list +3. Configure your **HMAC Request Signing** settings. +4. Select **Strip Authorization Data** to strip any authorization data from your API requests. +5. Select the location of the signature in the request. + +Configuring HMAC request signing + +## Configuring your API to use HMAC Request Signing + +HMAC request signing is configured within the Tyk Vendor Extension by adding the `hmac` object within the `server.authentication` section and enabling authentication. + +You must indicate where Tyk should look for the request signature (`header`, `query` or `cookie`) and which `algorithm` will be used to encrypt the secret to create the signature. You can also optionally configure a limit for the `allowedClockSkew` between the timestamp in the signature and the current time as measured by Tyk. + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + hmac: + enabled: true + header: + enabled: true + name: Authorization + allowedAlgorithms: + - hmac-sha256 + allowedClockSkew: -1 +``` + +Note that URL query parameter keys and cookie names are case sensitive, whereas header names are case insensitive. + +You can optionally [strip the auth token](/api-management/client-authentication#managing-authorization-data) from the request prior to proxying to the upstream using the `authentication.stripAuthorizationData` field (Tyk Classic: `strip_auth_data`). + +### Using Tyk Classic + +As noted in the Tyk Classic API [documentation](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis), you can select HMAC Request Signing using the `enable_signature_checking` option. + +## Registering an HMAC user with Tyk + +When using HMAC request signing, you need to provide Tyk with sufficient information to verify the client's identity from the signature in the request. You do this by creating and registering an HMAC user [Session](/api-management/access-control/sessions-and-keys/understanding-sessions) with Tyk. When this is created, a matching HMAC secret is also generated, which must be used by the client when signing their requests. + +The way that this is implemented is through the creation of a key that grants access to the API (as you would for an API protected by [auth token](/api-management/authentication/bearer-token)) and indicating that the key is to be used for HMAC signed requests by setting `hmac_enabled` to `true`. Tyk will return the HMAC secret in the response confirming creation of the key. + +When calling the API, the client would never use the key itself as a token, instead they must sign requests using the provided secret. + +## Generating a signature + +This code snippet gives an example of how a client could construct and generate a Request Signature. + +```{.copyWrapper} +... + +refDate := "Mon, 02 Jan 2006 15:04:05 MST" + +// Prepare the request headers: +tim := time.Now().Format(refDate) +req.Header.Add("Date", tim) +req.Header.Add("X-Test-1", "hello") +req.Header.Add("X-Test-2", "world") + +// Prepare the signature to include those headers: +signatureString := "(request-target): " + "get /your/path/goes/here" +signatureString += "date: " + tim + "\n" +signatureString += "x-test-1: " + "hello" + "\n" +signatureString += "x-test-2: " + "world" + +// SHA1 Encode the signature +HmacSecret := "secret-key" +key := []byte(HmacSecret) +h := hmac.New(sha1.New, key) +h.Write([]byte(signatureString)) + +// Base64 and URL Encode the string +sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) +encodedString := url.QueryEscape(sigString) + +// Add the header +req.Header.Add("Authorization", + fmt.Sprintf("Signature keyId="9876",algorithm="hmac-sha1",headers="(request-target) date x-test-1 x-test-2",signature="%s"", encodedString)) + +... +``` diff --git a/basic-config-and-security/security/authentication-authorization/json-web-tokens.mdx b/basic-config-and-security/security/authentication-authorization/json-web-tokens.mdx new file mode 100644 index 0000000000..da66397d45 --- /dev/null +++ b/basic-config-and-security/security/authentication-authorization/json-web-tokens.mdx @@ -0,0 +1,155 @@ +--- +title: "JSON Web Token (JWT) Authentication" +description: "How to use JWT Authentication with Tyk" +keywords: "Authentication, JWT, JSON Web Tokens" +sidebarTitle: "Overview" +--- + +## Introduction + +JSON Web Token (JWT) is an open standard ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) that defines a compact and self-contained way for securely transmitting claims between parties as a JSON object. + +The information in the JSON object is digitally signed using either a secret (with the HMAC algorithm) or a public/private key pair (using RSA or ECDSA encryption) allowing the JWT to be used for client authentication. + +### Key Benefits + +- **Stateless Authentication**: Eliminates the need for server-side session storage, improving scalability. +- **Flexible Integration**: Works with multiple identity providers including Auth0, Okta, and custom JWT issuers. +- **Enhanced Security**: Supports multiple signature validation methods (RSA, ECDSA, HMAC) and claim verification. +- **Granular Access Control**: Leverage JWT claims for policy enforcement and scope-based permissions. +- **Performance Optimized**: Efficient token validation with minimal overhead and support for JWKS caching. + +## How JWT Authentication works with Tyk + +JWTs are commonly used in OAuth 2.0 and OpenID Connect flows to authenticate users and authorize access to APIs. The following diagram and steps outline the flow when using JWT Auth to secure access to your API through Tyk. + +JSON Web Tokens Flow + +1. **Authentication and Token Issuance** + + Alice (the *user* or *resource owner*) authenticates with an Identity Provider (IdP) via the client application (steps 1 and 2). + - The IdP issues a JWT containing specific **claims** (permissions delegated by the user). + - The client application receives this authorization code and exchanges it for an access token (step 3). + - The client acts as the **Token Bearer**, holding the token to access protected resources on Alice's behalf. + +2. **Request to Gateway** + + When the client sends a request to the API gateway, it includes the access token (JWT) in the request, usually in the `Authorization` header as a Bearer token (step 4). + +3. **Token Validation** + + Tyk validates the token's signature using the shared secret or public key(s) of the trusted issuer (IdP). This process typically involves: + - Locating the JWT in the request (header, cookie, or query parameter). + - Decoding the JWT header to extract the `kid` (Key ID). + - Fetching public keys from configured JWKS URIs (or using a local static key). + - Searching the retrieved keys for a match to the `kid`. + - Validating the signature using the matching JWK. + - Ensuring the token is valid and not expired. + - *If any validation step fails, the request is rejected immediately.* + + To know more about how Tyk validates JWT signatures and claims, see [JWT Signature Validation](/api-management/authentication/jwt-signature-validation) and [JWT Claim Validation](/api-management/authentication/jwt-claim-validation). + +4. **Internal Identity Creation** + + Once validated, Tyk creates an internal session for the request (step 5). This session is used to control access rights, consumption limits, and analytics. Tyk does not store user credentials; instead, it links the session to Alice using an identity [extracted from the JWT claims](/api-management/authentication/jwt-authorization#identifying-the-session-owner). + + To know more about how Tyk authorizes requests using JWT claims, see [JWT Authorization](/api-management/authentication/jwt-authorization). + +5. **Policy Enforcement** + + Tyk enforces authorization by inspecting specific claims to determine which Security Policies apply (step 6): + - Tyk checks the **Policy Claim** (identified by the value stored in `basePolicyClaims`). + - It maps this claim to a configured Tyk Security Policy. If no direct map exists, a `defaultPolicy` may be applied. + - The applied policy configures the specific Access Rights (ACLs), rate limits, and usage quotas for that specific session. + + To know more about how Tyk identifies the policies to be applied, see [Identifying the Tyk Policies to be Applied](/api-management/authentication/jwt-authorization#identifying-the-tyk-policies-to-be-applied). + +6. **Proxy to Upstream** + + If the token is valid and the policy allows the request, Tyk proxies the traffic to your upstream target service (step 7). + + +### JWT Workflow + +```mermaid +sequenceDiagram + participant User + participant IdP as Identity Provider (IdP) + participant Client as Client Application + participant Tyk as Tyk Gateway + + User->>IdP: Authorizes Client + IdP-->>Client: Issues JWT (with claims) + Client->>Tyk: Sends API request with JWT + Tyk->>IdP: (Optional) Fetch JWKS for validation + Tyk-->>Tyk: Validates JWT Signature + Tyk-->>Tyk: Extracts Claims + Tyk-->>Tyk: Applies Security Policies based on Claims + Tyk-->>Client: Forwards request or responds +``` + +## Configuration Options + +The OpenAPI Specification treats JWT authentication as a variant of [bearer authentication](https://swagger.io/docs/specification/v3_0/authentication/bearer-authentication/) in the `components.securitySchemes` object using the `type: http`, `scheme: bearer` and `bearerFormat: jwt`: + +```yaml +components: + securitySchemes: + myAuthScheme: + type: http + scheme: bearer + bearerFormat: jwt + +security: + - myAuthScheme: [] +``` + +With this configuration provided by the OpenAPI description, in the Tyk Vendor Extension we need to enable authentication, to select this security scheme and to indicate where Tyk should look for the credentials. Usually the credentials will be provided in the `Authorization` header, but Tyk is configurable, via the Tyk Vendor Extension, to support custom header keys and credential passing via query parameter or cooke. + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + header: + enabled: true + name: Authorization +``` + +**Note:** that URL query parameter keys and cookie names are case sensitive, whereas header names are case insensitive. + +You can optionally [strip the user credentials](/api-management/client-authentication#managing-authorization-data) from the request prior to proxying to the upstream using the `authentication.stripAuthorizationData` field (Tyk Classic: `strip_auth_data`). + +With the JWT method selected, you'll need to configure Tyk to handle the specific configuration of JSON Web Tokens that clients will be providing. All of the JWT specific configuration is performed within the `authentication.jwt` object in the [Tyk Vendor Extension](/api-management/gateway-config-tyk-oas#jwt). + +### Locating the JWT in the Request + +The OpenAPI Specification provides a `securitySchemes` mechanism that lets you define where the JWT should be located, for example in the request header. However, in practice, different clients may supply the token in different locations, such as a query parameter. + +While OAS does not support this natively, the Tyk Vendor Extension does this by allowing configuration of alternative locations in the JWT entry in `server.authentication.securitySchemes`. Building on the previous example, we can add optional query and cookie locations as follows: + +```yaml +x-tyk-api-gateway: + server: + authentication: + enabled: true + securitySchemes: + myAuthScheme: + enabled: true + header: + enabled: true + name: Authorization + query: + enabled: true + name: query-auth + cookie: + enabled: true + name: cookie-auth +``` + +### Using Tyk Classic APIs + +As noted in the Tyk Classic API [documentation](/api-management/gateway-config-tyk-classic#configuring-authentication-for-tyk-classic-apis), you can select JSON Web Token authentication using the `use_jwt` option. Tyk Classic APIs do not natively support multiple JWKS endpoints, though a [custom authentication plugin](/api-management/plugins/plugin-types#authentication-plugins) could be used to implement this functionality. diff --git a/basic-config-and-security/security/authentication-authorization/multiple-auth.mdx b/basic-config-and-security/security/authentication-authorization/multiple-auth.mdx new file mode 100644 index 0000000000..428509aa8e --- /dev/null +++ b/basic-config-and-security/security/authentication-authorization/multiple-auth.mdx @@ -0,0 +1,539 @@ +--- +title: "Combine Authentication Methods" +description: "How to combine multiple authentication methods in Tyk to enhance security and flexibility." +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Multi Authentication, Chained Authentication" +sidebarTitle: "Multi Auth" +--- + +## Introduction + +Tyk provides flexible multi-authentication capabilities, allowing you to combine various [authentication methods](/api-management/client-authentication#what-does-tyk-support) using different logical approaches: + +- AND logic: All configured authentication methods must succeed before granting access +- OR logic: Any one of the configured authentication methods can grant access _(Tyk OAS APIs only)_ + +This enables scenarios such as requiring both Bearer Token authentication and Basic Auth simultaneously, or allowing access via either JWT validation or API key authentication. + +```mermaid +graph LR + Client([Client]) -->|Request| Gateway[Tyk Gateway] + + subgraph "Multiple Authentication" + Gateway --> Auth1[Authentication Method 1
e.g., API Key] + Auth1 --> Auth2[Authentication Method 2
e.g., Basic Auth] + Auth2 --> Auth3[Authentication Method N
e.g., JWT] + Auth3 --> SessionCreation[Create Session
& Apply Policies] + SessionCreation --> AccessDecision{Access
Decision} + end + + AccessDecision -->|Granted| Upstream[(Upstream
API)] + AccessDecision -->|Denied| Reject[Reject Request] + + %% Styling + classDef client fill:#f9f9f9,stroke:#333,stroke-width:2px + classDef gateway fill:#d4edda,stroke:#28a745,stroke-width:2px + classDef auth fill:#e2f0fb,stroke:#0275d8,stroke-width:1px + classDef session fill:#d1ecf1,stroke:#17a2b8,stroke-width:1px + classDef decision fill:#cce5ff,stroke:#0275d8,stroke-width:2px + classDef upstream fill:#f8f9fa,stroke:#6c757d,stroke-width:2px + classDef reject fill:#f8d7da,stroke:#dc3545,stroke-width:2px + + class Client client + class Gateway gateway + class Auth1,Auth2,Auth3 auth + class SessionCreation session + class AccessDecision decision + class Upstream upstream + class Reject reject +``` + +## Use Cases + +- **Multi-tenant APIs**: Different tenants using different identity providers +- **Migration scenarios**: Supporting both legacy and modern auth during transitions +- **Partner integrations**: External partners use mTLS while internal users use JWT +- **Mobile + Web**: Different auth methods for different client types + +{/* ## Quick Start + + + +In this quick-start guide, we will configure a Tyk OAS API with multiple authentication methods. We will demonstrate how to set up an API that supports both Basic Auth and API Key authentication, allowing clients to authenticate using either method. + +### Prerequisites + +- **Working Tyk Environment:** You need access to a running Tyk instance that includes both the Tyk Gateway and Tyk Dashboard components. For Docker setup instructions, please refer to this [guide](/tyk-self-managed/install/docker). +- **Curl**: These tools will be used for testing. + +### Instructions + +#### Create an API + +#### Configuration + +#### Testing */} + +## Understanding Authentication Modes + +```mermaid +graph LR + Client([Client]) -->|Request| Gateway[Tyk Gateway] + + subgraph "Tyk Multiple Authentication" + Gateway --> AuthConfig{Authentication
Configuration} + + %% Legacy Mode + AuthConfig -->|Legacy Mode| LegacyAuth["AND Logic
(All must pass)"] + LegacyAuth --> Auth1[Auth Method 1] + Auth1 --> Auth2[Auth Method 2] + Auth2 --> SessionCreation["Create Session
(from BaseIdentityProvider)"] + + %% Compliant Mode + AuthConfig -->|Compliant Mode| CompliantAuth["OR Logic
(Any group can pass)"] + CompliantAuth --> Group1["Group 1
(AND Logic)"] + CompliantAuth --> Group2["Group 2
(AND Logic)"] + + Group1 --> G1Auth1["Auth Method A"] + G1Auth1 --> G1Auth2["Auth Method B"] + + Group2 --> G2Auth1["Auth Method C"] + G2Auth1 --> G2Auth2["Auth Method D"] + + G1Auth2 --> DynamicSession1["Create Session
(from last auth in group)"] + G2Auth2 --> DynamicSession2["Create Session
(from last auth in group)"] + + SessionCreation --> AccessDecision + DynamicSession1 --> AccessDecision + DynamicSession2 --> AccessDecision + + AccessDecision{Access
Decision} + end + + AccessDecision -->|Granted| Upstream[(Upstream
API)] + AccessDecision -->|Denied| Reject[Reject Request] + + %% Styling + classDef client fill:#f9f9f9,stroke:#333,stroke-width:2px + classDef gateway fill:#d4edda,stroke:#28a745,stroke-width:2px + classDef auth fill:#e2f0fb,stroke:#0275d8,stroke-width:1px + classDef group fill:#fff3cd,stroke:#ffc107,stroke-width:1px + classDef session fill:#d1ecf1,stroke:#17a2b8,stroke-width:1px + classDef decision fill:#cce5ff,stroke:#0275d8,stroke-width:2px + classDef upstream fill:#f8f9fa,stroke:#6c757d,stroke-width:2px + classDef reject fill:#f8d7da,stroke:#dc3545,stroke-width:2px + + class Client client + class Gateway gateway + class Auth1,Auth2,G1Auth1,G1Auth2,G2Auth1,G2Auth2,LegacyAuth,CompliantAuth auth + class Group1,Group2 group + class SessionCreation,DynamicSession1,DynamicSession2 session + class AuthConfig,AccessDecision decision + class Upstream upstream + class Reject reject +``` + +Tyk OAS offers two modes for configuring multiple authentication methods: + +- **Legacy Mode** (Default): Maintains backward compatibility with existing Tyk implementations using AND logic only +- **Compliant Mode**: Introduced in 5.10.0, provides enhanced flexibility by supporting both AND and OR logic between authentication methods + + + + + Tyk Classic APIs and pre-5.10 Tyk OAS APIs only support the legacy mode. + + + +### Legacy Mode + +The Legacy mode is the traditional implementation of multi-auth, supported by Tyk Classic APIs and Tyk OAS APIs prior to Tyk 5.10. + +In this mode, all configured authentication methods must be satisfied in the request (i.e., they are combined using AND logic). + +**How does the operation differ between Tyk Classic and Tyk OAS APIs?** + +- **Tyk Classic API**: All configured authentication methods must be satisfied in the request + +- **Tyk OAS API**: Only the **first** security requirement object in the OpenAPI description's `security` array is processed. All the authentication methods in the first object must be satisfied in the request, together with any proprietary auth methods `enabled` in the Tyk Vendor Extension. + + ``` + security: + - api_key: [] # this security requirement is processed: both methods must be satisfied + basic_auth: [] + - jwt_auth: [] # Ignored in Legacy mode + ``` + +#### Session Handling + +In Legacy mode, the `baseIdentityProvider` setting determines which authentication method provides the [session object](/api-management/access-control/sessions-and-keys/understanding-sessions). This setting must be configured to one of auth methods in the logical rule using the following mapping: + +- `auth_token` - for token-based authentication +- `jwt_claim` - for JWT authentication +- `basic_auth_user` - for Basic Authentication +- `hmac_key` - for HMAC authentication +- `custom_auth` - for custom authentication plugin + + +### Compliant Mode + +The Compliant mode is named as such because Tyk complies with the security requirements declared in the OpenAPI description, combining different authentication methods using AND and OR logic as required. + +#### OpenAPI Security Requirements + +In OpenAPI, security is defined using **Security Requirement Objects** in the `security` section: + +``` +security: + - api_key: [] + basic_auth: [] + - jwt_auth: [] + oauth2: [] +``` + +- Each security requirement object in the OAS `security` array represents an **alternative**, these are evaluated with **OR** logic. +- Within a single security requirement object, multiple security schemes can be declared and will combined using **AND** logic (i.e., all listed schemes must succeed together). +- A request is authorized if **any one** of the defined security requirement objects is successfully validated. +- The session object is determined dynamically based on which security requirement is satisfied +- This structure enables **multi-auth configurations**, supporting both **combined (AND)** and **alternative (OR)** authentication methods. + +#### How OR Logic Works + +When using Compliant mode with multiple security requirements: + +- Tyk attempts each authentication method in sequence +- If any method succeeds, the request is authorized +- If all methods fail, the request is rejected with the error from the last attempted method + +#### How AND Logic Works + +Within a single security requirement object that contains multiple schemes: + +- All schemes within that security requirement must be satisfied (AND logic) +- The request is only authorized if all schemes are valid +- If any scheme fails, the entire security requirement fails, and Tyk moves to the next one +- This allows for combining different authentication methods that must all be present + +#### Examples + +Here's an example `security` object from an OpenAPI description with both AND and OR logic: + +```json +{ + "security": [ + { + "scheme1": [] + }, + { + "scheme2": [], + "scheme3": [] + } + ] +} +``` +You will notice that the `security` object should contain references to security schemes that you've defined in the `components.securitySchemes` section of the OpenAPI description. +In this example, the request will be authorized if either: +- `scheme1` is provided (first security requirement) +- OR +- Both `scheme2` AND `scheme3` are provided (second security requirement with AND logic) + +#### Session Handling + +The [Session object](/api-management/access-control/sessions-and-keys/understanding-sessions) (determining rate limits, quotas, and access rights) comes from the successful authentication method. This allows different auth methods to have different associated policies and permissions. + +When using **Compliant** mode, the session object handling is more dynamic: + +1. Between different security requirement objects (OR logic): The first security requirement that successfully authenticates will provide the session object. + +2. Within a single security requirement object (AND logic): When multiple authentication methods are specified in the same requirement object (as in your example below), all methods must pass, and the **last** successfully processed authentication method will provide the session object. + +Auth methods are always validated in the following order (and skipped if not included in the security requirement): + +1. [Tyk OAuth 2.0](/api-management/authentication/oauth-2) +2. External OAuth (([deprecated](/api-management/client-authentication#integrate-with-external-authorization-server-deprecated)) +3. [Basic Auth](/api-management/authentication/basic-authentication) +4. [HMAC](/basic-config-and-security/security/authentication-authorization/hmac-signatures) +5. [JWT](/basic-config-and-security/security/authentication-authorization/json-web-tokens) +6. OpenID Connect ([deprecated](/api-management/client-authentication#integrate-with-openid-connect-deprecated)) +7. [Custom Plugin Auth](/api-management/authentication/custom-auth) +8. [Auth Token](/api-management/authentication/bearer-token) (API Key/Bearer Token) + +For example, if this security requirement is satisfied in the request, the session metadata will come from the Auth Token, despite it being declared first in the security requirement: + +``` +security: + - api_key: [] + basic_auth: [] +``` + + + + + +### Choosing the Right Mode + +**Use Legacy Mode when:** + +- Migrating from or using Tyk Classic APIs +- You need the session metadata to be taken from an auth method earlier in the middleware processing order + +**Use Compliant Mode when:** + +- You need alternative auth methods (OR logic) +- Supporting multiple client types or identity providers +- For APIs that need to serve diverse client bases with different security requirements +- Building new APIs with flexible authentication requirements + +## Configuration Options + +### Security Processing Mode + +The `securityProcessingMode` option in the Tyk Vendor Extension allows you to specify which mode to use when processing the `security` configuration in your API. This controls how Tyk will interpret the authentication settings in the OpenAPI description and the Vendor Extension. + +```yaml +x-tyk-api-gateway: + server: + authentication: + securityProcessingMode: compliant // or legacy +``` + +### Basic Example: API with Multiple Auth Methods + +Here's a simple example of an OpenAPI description that declares JWT and API Key security schemes: + +```yaml +# Example: API supporting either JWT OR API Key authentication +components: + securitySchemes: + api_key: + type: apiKey + name: X-API-Key + in: header + description: "API key for service-to-service authentication" + jwt_auth: + type: http + scheme: bearer + bearerFormat: JWT + description: "JWT token for user authentication" +security: + - api_key: [] # Option 1: API key only + - jwt_auth: [] # Option 2: JWT only +``` + +- If the `securityProcessingMode` in the Tyk Vendor Extension is set to `compliant`, Tyk will check incoming requests against each `security` option in turn, authenticating requests using Option 1 or Option 2. +- If the `securityProcessingMode` is set to `legacy` (or is omitted), Tyk will check requests only against the first `security` option (Option 1). + + +### Configuring Multiple Auth Methods in the API Designer + +You can configure chained authentication by following these steps: + +1. Enable **Authentication** in the **Servers** section + +2. Select the **Multiple Authentication Mechanisms** option from the drop-down list. + +3. Select the **Authentication Mode** that you wish to use: [Compliant](/basic-config-and-security/security/authentication-authorization/multiple-auth#compliant-mode) or [Legacy](/basic-config-and-security/security/authentication-authorization/multiple-auth#legacy-mode) + + + + + Select **Compliant mode** for full interpretation of the security requirements declared in the OpenAPI description, allowing for fully flexible authentication of your API clients: + + Select Compliant mode + + Use the API Editor view to configure the different security schemes and requirements to satisfy the client authentication needs of your API: + + Configure Compliant mode + + + + + Use Legacy mode for simple scenarios where you can select the **Authentication methods** that the client must satisfy in the request. + + You must identify the **Base identity provider** that will provide the session metadata: + + Select Legacy mode + + You can now configure each authentication method in the usual manner using the options in the API designer. + + Configure the Auth Methods for Legacy mode + + + + + + +## Advanced Configuration + +### Using Proprietary Auth Methods + +Compliant mode allows you to combine standard OpenAPI security schemes with Tyk's proprietary authentication methods by extending the OpenAPI `security` section into the Tyk Vendor Extension: + +```yaml +components: + securitySchemes: + api_key: + type: apiKey + name: Authorization + in: header + jwt_auth: + type: http + scheme: bearer + bearerFormat: JWT +security: + - jwt_auth: [] +x-tyk-api-gateway: + server: + authentication: + securityProcessingMode: compliant + security: + - - hmac + - api_key + - - custom_auth + securitySchemes: + hmac: + enabled: true + custom_auth: + enabled: true + config: + authType: coprocess +``` + +The extended security requirements in the vendor extension (`x-tyk-api-gateway.server.authentication.security`) are concatenated onto the requirements declared in the OpenAPI description. This configuration allows three authentication methods: JWT, API Key with HMAC, and Custom Auth. + +## Migration Considerations + +### Moving from Legacy to Compliant Mode + + + +If you change the security processing mode for an existing API from the Dashboard's API Designer, Tyk will add the `securityProcessingMode` field to your Vendor Extension, but will not make any other changes to the API's configuration. You may need to make adjustments to the OpenAPI description or Vendor Extension to ensure that the authentication rules are set correctly. + + + +When migrating from Legacy to Compliant mode: + +- Review your API's authentication configuration +- Ensure all required security schemes are properly defined, for example any use of Tyk proprietary auth methods (HMAC, custom authentication) will need to be reflected with creation of new security requirements within the Vendor Extension's `security` section and removal of requirements from the OpenAPI description's `security` section +- Test thoroughly, as authentication behavior will change +- Be aware that the session object may come from different sources depending on which auth method succeeds + +### Backward Compatibility + +*Legacy* mode ensures backward compatibility with existing Tyk implementations. If you're unsure which mode to use, start with *Legacy* mode and migrate to *Compliant* mode when ready. + +## Troubleshooting + + + +**Problem**: API returns 401 errors even with valid credentials. + +**Possible Causes & Solutions**: + +1. Security schemes not properly defined + + ```yaml + # ❌ Incorrect - missing security scheme definition + security: + - api_key: [] + # No corresponding securitySchemes definition + + # ✅ Correct - complete definition + components: + securitySchemes: + api_key: + type: apiKey + name: Authorization + in: header + security: + - api_key: [] + ``` + +2. Security schemes not enabled in Tyk extension + + ```yaml + x-tyk-api-gateway: + server: + authentication: + securityProcessingMode: compliant + securitySchemes: + api_key: + enabled: true # ← Must be explicitly enabled + ``` + +3. Mixed Legacy/Compliant configuration + + - Ensure you're not mixing `baseIdentityProvider` (Legacy) with complex security arrays (Compliant) + - Check that `securityProcessingMode` matches your intended configuration + + + +**Problem**: Requests are authenticated but get unexpected rate limits or access denials. + +**Root Cause**: In Compliant mode, the session object comes from whichever authentication method succeeds first. + +**Solutions**: + +1. Review security requirement order - Place most restrictive auth methods first: + + ```yaml + security: + - premium_jwt: [] # Premium users (higher limits) + - basic_api_key: [] # Basic users (lower limits) + ``` + +2. Ensure consistent policies across auth methods: + + - Verify that API keys and JWT tokens for the same user have similar access rights + - Check that rate limits align with your business logic + +3. Debug session source: + + ```bash + # Enable debug logging to see which auth method succeeded + "log_level": "debug" + ``` + + + +**Problem**: Slower response times with multiple authentication methods. + +**Expected Behavior**: Some performance impact is normal due to additional processing. + +**Optimization**: + +1. Order security requirements by likelihood: + + ```yaml + security: + - most_common_auth: [] # Try most common first + - fallback_auth: [] # Fallback for edge cases + ``` + +2. Monitor authentication attempts: + + ```bash + # Look for "OR wrapper" log entries showing auth attempts + grep "OR wrapper" /var/log/tyk/tyk.log + ``` + + + +Enable detailed logging in your Tyk Gateway to see which authentication methods are being attempted and which one succeeds: + +```json +{ + "global": { + "log_level": "debug" + } +} +``` + +Look for these log entries: +- `Processing multiple security requirements (OR conditions)` + - Confirms Compliant mode is active +- `OR wrapper` entries + - In Compliant mode, this shows which auth methods are being tried +- `BaseIdentityProvider set to` + - In Legacy mode, this shows which auth method succeeded + + diff --git a/basic-config-and-security/security/authentication-authorization/open-keyless.mdx b/basic-config-and-security/security/authentication-authorization/open-keyless.mdx new file mode 100644 index 0000000000..ee0158a7e4 --- /dev/null +++ b/basic-config-and-security/security/authentication-authorization/open-keyless.mdx @@ -0,0 +1,14 @@ +--- +title: "Open (No Authentication)" +description: "How to configure open or keyless authentication in Tyk." +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Open Authentication, Keyless Authentication" +sidebarTitle: "No Authentication" +--- + +Open or keyless authentication allows access to APIs without any authentication. This method is suitable for public APIs where access control is not required. + +Tyk OAS APIs are inherently "open" unless authentication is configured, however the older Tyk Classic API applies [Auth Token](/api-management/authentication/bearer-token) protection by default. + +You can disable authentication for a Tyk Classic API by setting the `use_keyless` flag in the API definition. + + diff --git a/basic-config-and-security/security/mutual-tls/client-mtls.mdx b/basic-config-and-security/security/mutual-tls/client-mtls.mdx new file mode 100644 index 0000000000..86b896fc7a --- /dev/null +++ b/basic-config-and-security/security/mutual-tls/client-mtls.mdx @@ -0,0 +1,313 @@ +--- +title: "Authentication using Mutual TLS" +description: "How to configure Mutual TLS (mTLS) for client authentication in Tyk." +keywords: "Authentication, Authorization, Tyk Authentication, Tyk Authorization, Mutual TLS, mTLS, Client mTLS" +sidebarTitle: "Mutual TLS" +--- + +## Introduction + +Mutual TLS (mTLS) is a robust security feature that ensures both the client and server authenticate each other using TLS certificates. This two-way authentication process provides enhanced security for API communications by cryptographically verifying the identity of both parties involved in the connection. + +In most cases when you try to access a secured HTTPS/TLS endpoint, you experience only the client-side check of the server certificate. The purpose of this check is to ensure that no fraud is involved and the data transfer between the client and server is encrypted. In fact, the TLS standard allows specifying the client certificate as well, so the server can accept connections only for clients with certificates registered with the server certificate authority, or provide additional security checks based on the information stored in the client certificate. This is what we call "Mutual TLS" - when both sides of the connection verify certificates. See the video below that gives you an introduction to mutual TLS and how it can be used to secure your APIs. + + + +## Why Use Mutual TLS? + +Mutual TLS is particularly valuable in environments where security is paramount, such as microservices architectures, financial services, healthcare, and any scenario requiring zero-trust security. It not only encrypts the data in transit but also ensures that the communicating parties are who they claim to be, mitigating the risks of unauthorized access and data breaches. + +* **Enhanced Security:** Provides two-way authentication, ensuring both the client and server are verified and trusted. +* **Data Integrity:** Protects the data exchanged between client and server by encrypting it, preventing tampering or interception. +* **Compliance:** Helps meet stringent security and compliance requirements, especially in regulated industries. + +## Client mTLS for Tyk Cloud + +Tyk Cloud users cannot currently use mTLS to secure the client to Gateway communication for Tyk-hosted gateways. + + +Tyk Hybrid users can, however, use mTLS with their self-hosted gateways. + + +## How Does Mutual TLS Work? + +Mutual TLS operates by requiring both the client and server to present and verify TLS certificates during the handshake process. Here’s how it works: + +**Client Authentication:** + +1. When a client attempts to connect to the server, the server requests the client’s TLS certificate. +2. The client provides its certificate, which the server verifies against a trusted Certificate Authority (CA). + +**Server Authentication:** + +1. Simultaneously, the server provides its own certificate to the client, which the client verifies against a trusted CA. + +This mutual verification ensures that both parties are legitimate, securing the connection from both ends. + +### Client authorization with mTLS +At the TLS level, authorization means only allowing access for clients who provide client certificates that are verified and trusted by the server. + +Tyk allows you to define a list of trusted certificates at the API level or Gateway (global) level. If you are updating API definition programmatically or via files, you need to set following the keys in your API +definition: +`use_mutual_tls_auth` to `true`, and `client_certificates` as an array of strings - certificate IDs. + +From the Tyk Dashboard, to do the same from the **API Designer Core settings** section you need to select **Mutual TLS** authentication mode from the **Authentication** section, and allow the certificates using the built-in widget, as below: + +mutual_tls_auth + +If all your APIs have a common set of certificates, you can define them in your Gateway configuration file via the `security.certificates.apis` key - string array of certificate IDs or paths. + +Select **Strip Authorization Data** to strip any authorization data from your API requests. + +Be aware that mutual TLS authorization has special treatment because it is not "authentication" and does not provide any identifying functionality, like keys, so you need to mix it with another authentication modes options like **Auth Key** or **Keyless**. On the dashboard, you need to choose **Use multiple auth mechanism** in the **Authentication mode** drop-down, where you should select **Mutual TLS** and another option which suits your use-case. + +### Fallback to HTTP Authorization +The TLS protocol has no access to the HTTP payload and works on the lower level; thus the only information we have at the TLS handshake level is the domain. In fact, even a domain is not included into a TLS handshake by default, but there is TLS extension called SNI (Server Name Indication) +which allows the client to send the domain name to the TLS handshake level. + +With this in mind, the only way to make API authorization work fully at the TLS level, each API protected by Mutual TLS should be deployed on its own domain. + +However, Tyk will gracefully fallback to a client certificate authorization at the HTTP level in cases when you want to have multiple mutual TLS protected APIs on the same domain, or you have clients that do not support the SNI extension. No additional configuration is needed. In case of such fallback, +instead of getting TLS error, a client will receive 403 HTTP error. + +### Authentication +Tyk can be configured to guess a user authentication key based on the provided client certificate. In other words, a user does not need to provide any key, except the certificate, and Tyk will be able to identify the user, apply policies, and do the monitoring - the same as with regular Keys. + +### Using with Authorization +Mutual TLS authentication does not require mutual TLS authorization to be turned on, and can be used separately. For example, you may allow some of the users to be authenticated by using a token in the header or similar, and some of the users via client certificates. + +If you want to use them both, just configure them separately. No additional knowledge is required. + +### Dynamic vs Static mTLS + +There are two ways to set up client mTLS in Tyk: static and dynamic. Each method is suited to different use cases, as outlined below: + +| Use Case | Static | Dynamic | +| ------------------------------------------------------------------ | :----: | :-----: | +| Let developers upload their own public certificates through the Developer Portal | ❌ | ✅ | +| Combine client mTLS with another authentication method | ✅ | ✅ | +| Allow certs at the API level (one or more APIs per cert) | ✅ | ❌ | +| Allow certs at an individual level (one or more APIs per cert) | ❌ | ✅ | + +## Dynamic mTLS + +Dynamic Client mTLS in Tyk allows you to authenticate users based solely on the provided client certificate, without the need for an additional authentication key. Tyk can identify the user, apply policies, and monitor usage just as with regular API keys. + +To set up Dynamic Client mTLS, we need to follow these steps: +* Protect the API: Configure the API in the API Designer by setting the authentication type to Auth Token and enabling Client Certificate. + +* Generate a Self-Signed Certificate: Use OpenSSL to generate a self-signed certificate and key if you don't have one. + +* Add a Key in the Dashboard: In the Tyk Dashboard, create a key for the API and upload only the public certificate. + +* Make an API Request: Use curl with your certificate and key to make an API request to the protected API, ensuring the request returns a 200 response. + +* Allow Developers to Upload Certificates: Create a policy and catalog entry for the API, allowing developers to request keys and upload their public certificates through the Developer Portal. Developers can then make API requests using their cert and private key. + + +### Protect the API + +In the API Designer, set the Authentication Type to Auth Token under Target Details > Authentication mode. Then select Enable Client Certificate. + +Enable Client Certificate + +### Generate a Self-Signed Key Pair + +If you don’t already have a certificate, generate a self-signed key pair using the following command: + +```bash +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes +``` + +### Add a Key through the Dashboard + +In the Tyk Dashboard, add a key for the API you set up in step #1. When uploading the certificate, ensure you only upload the public certificate. + + + + +The certificate you upload for this key must only be the public certificate. + + + + +### Make an API Request Using the Certificate + +Now you can make a cURL request to the API using the certificate and private key: + +```bash +curl -k --cert cert.pem --key key.pem https://localhost:8080/mtls-api/my-endpoint +``` + +A successful request should return a 200 response. + +### Allow Developers to Upload Certificates + +Instead of manually creating keys, you can allow developers to upload their own certificates via the Developer Portal. + +1. **Create a Policy:** Create a policy for the API you set up earlier. +2. **Create a Catalog Entry:** Create a catalog entry for this policy. +3. **Request a Key through the Portal:** As a developer, request a key for the API through the Portal. This will present a screen where the developer can upload their public certificate. + +portal_cert_request + +Add your public cert (cert.pem from above) into here and hit "Request Key". + +4. **Make an API Request Using the Uploaded Certificate:** After adding the public certificate, developers can make API requests using their cert + private key: + + ```bash + curl -k --cert cert.pem --key key.pem https://localhost:8080/mtls-api/my-endpoint + ``` + + A successful request should return a 200 response. + +## Static mTLS + +Static mTLS allows client certificates to be used at the API level. This method is straightforward and can be combined with another authentication method if needed. + +### Configure the API + +In the API authentication settings, choose mTLS as the authentication type and optionally select an additional authentication method. If you want to use only client certificates without another authentication method, select "keyless" as the other option. + +### Set the Base Identity + +The base identity can be anything, as the client certificate will be the primary authentication method. + + +### Setup Static mTLS in Tyk Operator using the Tyk Classic API Definition + +This setup requires mutual TLS (mTLS) for client authentication using specified client certificates. The example provided shows how to create an API definition with mTLS authentication for `httpbin-client-mtls`. + +1. **Generate Self-Signed Key Pair:** + +You can generate a self-signed key pair using the following OpenSSL command: + +```bash +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes +``` + +2. **Create Kubernetes Secret:** + +Create a secret in Kubernetes to store the client certificate: + +```bash +kubectl create secret tls my-test-tls --cert cert.pem --key key.pem +``` + +3. **Create API Definition:** + +Below is the YAML configuration for an API that uses mTLS authentication. Note that the `client_certificate_refs` field references the Kubernetes secret created in the previous step. + +```yaml {hl_lines=["19-21"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-client-mtls +spec: + name: Httpbin Client MTLS + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + use_mutual_tls_auth: true + client_certificate_refs: + - my-test-tls +``` + +### Setup Static mTLS in Tyk Operator using Tyk OAS API Definition + +Client certificates, In Tyk OAS API Definition, are managed using the `TykOasApiDefinition` CRD. You can reference Kubernetes secrets that store client certificates in your API definitions. + +**Example of Referencing Client Certificates in Tyk OAS** + +In this example, the `clientCertificate` section allows you to enable client certificate management and specify a list of Kubernetes secrets (`tls-cert`) that store allowed client certificates. + +```yaml {hl_lines=["48-50"],linenos=false} +# Secret is not created in this manifest. +# Please store client certificate in k8s TLS secret `tls-cert`. + +apiVersion: v1 +data: + test_oas.json: |- + { + "info": { + "title": "Petstore", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Petstore", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore.swagger.io/v2" + }, + "server": { + "listenPath": { + "value": "/petstore/", + "strip": true + } + } + } + } +kind: ConfigMap +metadata: + name: cm + namespace: default +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: petstore +spec: + tykOAS: + configmapRef: + name: cm + namespace: default + keyName: test_oas.json + clientCertificate: + enabled: true + allowlist: [tls-cert] +``` + + +## FAQ + +* **Why am I getting an error stating that certificates are not enabled for this API?** + + This issue can occur because client mTLS is an extension of Auth Token authentication mode. To enable this feature, ensure the API definition has `auth.use_certificate` set to `true`. + +* **Can I upload a full certificate chain when creating a key for dynamic client mTLS?** + + Yes, you can do this when manually creating a key as an Admin Dashboard user. However, through the Portal, you must upload only the public key (certificate). + +* **Can I use a root CA with client mTLS?** + + Yes, Tyk allows you to upload a root CA certificate for static mTLS authentication. This setup allows clients with certificates signed by the registered CA to be validated. + + **Key Points:** + + * The root CA certificate can be uploaded as a client certificate. + * Clients presenting certificates signed by this CA will be validated. + * Tyk traverses the certificate chain for validation. + + + + Root CA certificates are compatible only with Static mTLS and not with Dynamic mTLS. + + + + diff --git a/branches-config.json b/branches-config.json index 35b9d06a82..4285e4b98a 100644 --- a/branches-config.json +++ b/branches-config.json @@ -1,18 +1,42 @@ { "versions": [ { - "branch": "release-5.8", + "branch": "release-5.11", "isLatest": true, - "folder": "5.8", - "label": "v5.8 (Latest)" + "sourceFolder": "5.11-source", + "targetFolder": "5.11", + "label": "v5.11 (latest)" }, { "branch": "main", "isLatest": false, "isMain": true, - "folder": "nightly", + "sourceFolder": "nightly-source", + "targetFolder": "nightly", "label": "Nightly" }, + { + "branch": "release-5.10", + "isLatest": false, + "sourceFolder": "5.10-source", + "targetFolder": "5.10", + "label": "v5.10" + }, + { + "branch": "release-5.9", + "isLatest": false, + "sourceFolder": "5.9-source", + "targetFolder": "5.9", + "label": "v5.9" + }, + { + "branch": "release-5.8", + "isLatest": false, + "isMain": false, + "sourceFolder": "5.8-source", + "targetFolder": "5.8", + "label": "v5.8 (LTS)" + }, { "isExternal": true, "externalUrl": "https://tyk.io/docs/5.7", @@ -92,4 +116,4 @@ "buildConfig": { "docsDir": "." } -} \ No newline at end of file +} diff --git a/calculator.js b/calculator.js new file mode 100644 index 0000000000..2f6a9d113c --- /dev/null +++ b/calculator.js @@ -0,0 +1,3 @@ +/*! For license information please see main.b7b66d3b.js.LICENSE.txt */ +(() => { "use strict"; var __webpack_modules__ = { 111: (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.d(__webpack_exports__, { A: () => __WEBPACK_DEFAULT_EXPORT__ }); var formik__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(892), _components_Calculator_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(297), react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(579); const SUM = "sum", __WEBPACK_DEFAULT_EXPORT__ = _ref => { let { fields: fields } = _ref; const initialValues = {}; return Object.values(fields).map((e => Object.values(e).map((e => initialValues[e.name] = e.defaultValue || 0)))), (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__.jsx)(formik__WEBPACK_IMPORTED_MODULE_0__.l1, { initialValues: initialValues, onSubmit: (values, _ref2) => { let { setFieldValue: setFieldValue } = _ref2, sum = 0, x; Object.values(fields.calculated_fields).map((_ref3 => { let { name: name, value: value } = _ref3; x = eval(value), sum += x, name.startsWith(SUM), setFieldValue(name, x) })) }, component: e => (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_2__.jsx)(_components_Calculator_js__WEBPACK_IMPORTED_MODULE_1__.A, { fields: fields, onSubmit: !0, ...e }) }) } }, 297: (e, t, n) => { n.d(t, { A: () => u }); var r = n(892), a = n(579); const l = e => { if (e < 0 || 100 < e) return "Please enter a value between 0-100." }, o = e => { if (e < 0) return "Please enter a positive integer value." }, i = e => { let { fields: t, values: n, errors: i, fixed: u, disabled: s, handleChange: c } = e; return (0, a.jsx)(a.Fragment, { children: Object.entries(t).map((e => { let [t, { name: f, label: d, type: p, unit: m, description: h }] = e; return (0, a.jsxs)("div", { children: [(0, a.jsxs)("div", { style: { display: "flex", alignItems: "center" }, children: [(0, a.jsx)("label", { htmlFor: f, title: h, style: { marginRight: "auto" }, children: d }), (0, a.jsx)(r.D0, { id: f, name: f, type: p, value: u ? n[f].toFixed(4) : n[f], onChange: c, disabled: s, validate: "Percent" === m ? l : o, title: h, required: !0, style: { margin: "4px 10px", color: "black" } }), (0, a.jsx)("label", { style: { width: "80px" }, children: m })] }), i[f] ? (0, a.jsx)("div", { style: { color: "red" }, children: i[f] }) : null] }, t) })) }) }, u = e => { let { handleSubmit: t, handleChange: n, handleBlur: l, values: o, errors: u, fields: s } = e; return (0, a.jsxs)(r.lV, { onSubmit: t, children: [Object.keys(s).map((e => (0, a.jsxs)("div", { children: [(0, a.jsx)(i, { fields: s[e], values: o, errors: u, fixed: "calculated_fields" === e, disabled: "input_fields" !== e, handleChange: n }), (0, a.jsx)("hr", { style: { margin: "1rem 0" } })] }, e))), (0, a.jsx)("button", { type: "submit", disabled: 0 !== Object.keys(u).length, style: { width: "100%", backgroundColor: "#8438fa", borderColor: "#8438fa", color: "white" }, children: "Calculate" })] }) } }, 892: (e, t, n) => { n.d(t, { D0: () => gr, lV: () => _r, l1: () => mr }); var r = n(43), a = n(366), l = n.n(a), o = function (e) { return function (e) { return !!e && "object" === typeof e }(e) && !function (e) { var t = Object.prototype.toString.call(e); return "[object RegExp]" === t || "[object Date]" === t || function (e) { return e.$$typeof === i }(e) }(e) }; var i = "function" === typeof Symbol && Symbol.for ? Symbol.for("react.element") : 60103; function u(e, t) { return !1 !== t.clone && t.isMergeableObject(e) ? c((n = e, Array.isArray(n) ? [] : {}), e, t) : e; var n } function s(e, t, n) { return e.concat(t).map((function (e) { return u(e, n) })) } function c(e, t, n) { (n = n || {}).arrayMerge = n.arrayMerge || s, n.isMergeableObject = n.isMergeableObject || o; var r = Array.isArray(t); return r === Array.isArray(e) ? r ? n.arrayMerge(e, t, n) : function (e, t, n) { var r = {}; return n.isMergeableObject(e) && Object.keys(e).forEach((function (t) { r[t] = u(e[t], n) })), Object.keys(t).forEach((function (a) { n.isMergeableObject(t[a]) && e[a] ? r[a] = c(e[a], t[a], n) : r[a] = u(t[a], n) })), r }(e, t, n) : u(t, n) } c.all = function (e, t) { if (!Array.isArray(e)) throw new Error("first argument should be an array"); return e.reduce((function (e, n) { return c(e, n, t) }), {}) }; const f = c; const d = "object" == typeof global && global && global.Object === Object && global; var p = "object" == typeof self && self && self.Object === Object && self; const m = d || p || Function("return this")(); const h = m.Symbol; var y = Object.prototype, v = y.hasOwnProperty, b = y.toString, g = h ? h.toStringTag : void 0; const _ = function (e) { var t = v.call(e, g), n = e[g]; try { e[g] = void 0; var r = !0 } catch (l) { } var a = b.call(e); return r && (t ? e[g] = n : delete e[g]), a }; var S = Object.prototype.toString; const w = function (e) { return S.call(e) }; var k = h ? h.toStringTag : void 0; const E = function (e) { return null == e ? void 0 === e ? "[object Undefined]" : "[object Null]" : k && k in Object(e) ? _(e) : w(e) }; const x = function (e, t) { return function (n) { return e(t(n)) } }; const C = x(Object.getPrototypeOf, Object); const T = function (e) { return null != e && "object" == typeof e }; var P = Function.prototype, O = Object.prototype, z = P.toString, j = O.hasOwnProperty, R = z.call(Object); const A = function (e) { if (!T(e) || "[object Object]" != E(e)) return !1; var t = C(e); if (null === t) return !0; var n = j.call(t, "constructor") && t.constructor; return "function" == typeof n && n instanceof n && z.call(n) == R }; const N = function () { this.__data__ = [], this.size = 0 }; const M = function (e, t) { return e === t || e !== e && t !== t }; const L = function (e, t) { for (var n = e.length; n--;)if (M(e[n][0], t)) return n; return -1 }; var I = Array.prototype.splice; const F = function (e) { var t = this.__data__, n = L(t, e); return !(n < 0) && (n == t.length - 1 ? t.pop() : I.call(t, n, 1), --this.size, !0) }; const D = function (e) { var t = this.__data__, n = L(t, e); return n < 0 ? void 0 : t[n][1] }; const U = function (e) { return L(this.__data__, e) > -1 }; const V = function (e, t) { var n = this.__data__, r = L(n, e); return r < 0 ? (++this.size, n.push([e, t])) : n[r][1] = t, this }; function B(e) { var t = -1, n = null == e ? 0 : e.length; for (this.clear(); ++t < n;) { var r = e[t]; this.set(r[0], r[1]) } } B.prototype.clear = N, B.prototype.delete = F, B.prototype.get = D, B.prototype.has = U, B.prototype.set = V; const $ = B; const q = function () { this.__data__ = new $, this.size = 0 }; const W = function (e) { var t = this.__data__, n = t.delete(e); return this.size = t.size, n }; const H = function (e) { return this.__data__.get(e) }; const Q = function (e) { return this.__data__.has(e) }; const K = function (e) { var t = typeof e; return null != e && ("object" == t || "function" == t) }; const G = function (e) { if (!K(e)) return !1; var t = E(e); return "[object Function]" == t || "[object GeneratorFunction]" == t || "[object AsyncFunction]" == t || "[object Proxy]" == t }; const Y = m["__core-js_shared__"]; var X = function () { var e = /[^.]+$/.exec(Y && Y.keys && Y.keys.IE_PROTO || ""); return e ? "Symbol(src)_1." + e : "" }(); const Z = function (e) { return !!X && X in e }; var J = Function.prototype.toString; const ee = function (e) { if (null != e) { try { return J.call(e) } catch (t) { } try { return e + "" } catch (t) { } } return "" }; var te = /^\[object .+?Constructor\]$/, ne = Function.prototype, re = Object.prototype, ae = ne.toString, le = re.hasOwnProperty, oe = RegExp("^" + ae.call(le).replace(/[\\^$.*+?()[\]{}|]/g, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"); const ie = function (e) { return !(!K(e) || Z(e)) && (G(e) ? oe : te).test(ee(e)) }; const ue = function (e, t) { return null == e ? void 0 : e[t] }; const se = function (e, t) { var n = ue(e, t); return ie(n) ? n : void 0 }; const ce = se(m, "Map"); const fe = se(Object, "create"); const de = function () { this.__data__ = fe ? fe(null) : {}, this.size = 0 }; const pe = function (e) { var t = this.has(e) && delete this.__data__[e]; return this.size -= t ? 1 : 0, t }; var me = Object.prototype.hasOwnProperty; const he = function (e) { var t = this.__data__; if (fe) { var n = t[e]; return "__lodash_hash_undefined__" === n ? void 0 : n } return me.call(t, e) ? t[e] : void 0 }; var ye = Object.prototype.hasOwnProperty; const ve = function (e) { var t = this.__data__; return fe ? void 0 !== t[e] : ye.call(t, e) }; const be = function (e, t) { var n = this.__data__; return this.size += this.has(e) ? 0 : 1, n[e] = fe && void 0 === t ? "__lodash_hash_undefined__" : t, this }; function ge(e) { var t = -1, n = null == e ? 0 : e.length; for (this.clear(); ++t < n;) { var r = e[t]; this.set(r[0], r[1]) } } ge.prototype.clear = de, ge.prototype.delete = pe, ge.prototype.get = he, ge.prototype.has = ve, ge.prototype.set = be; const _e = ge; const Se = function () { this.size = 0, this.__data__ = { hash: new _e, map: new (ce || $), string: new _e } }; const we = function (e) { var t = typeof e; return "string" == t || "number" == t || "symbol" == t || "boolean" == t ? "__proto__" !== e : null === e }; const ke = function (e, t) { var n = e.__data__; return we(t) ? n["string" == typeof t ? "string" : "hash"] : n.map }; const Ee = function (e) { var t = ke(this, e).delete(e); return this.size -= t ? 1 : 0, t }; const xe = function (e) { return ke(this, e).get(e) }; const Ce = function (e) { return ke(this, e).has(e) }; const Te = function (e, t) { var n = ke(this, e), r = n.size; return n.set(e, t), this.size += n.size == r ? 0 : 1, this }; function Pe(e) { var t = -1, n = null == e ? 0 : e.length; for (this.clear(); ++t < n;) { var r = e[t]; this.set(r[0], r[1]) } } Pe.prototype.clear = Se, Pe.prototype.delete = Ee, Pe.prototype.get = xe, Pe.prototype.has = Ce, Pe.prototype.set = Te; const Oe = Pe; const ze = function (e, t) { var n = this.__data__; if (n instanceof $) { var r = n.__data__; if (!ce || r.length < 199) return r.push([e, t]), this.size = ++n.size, this; n = this.__data__ = new Oe(r) } return n.set(e, t), this.size = n.size, this }; function je(e) { var t = this.__data__ = new $(e); this.size = t.size } je.prototype.clear = q, je.prototype.delete = W, je.prototype.get = H, je.prototype.has = Q, je.prototype.set = ze; const Re = je; const Ae = function (e, t) { for (var n = -1, r = null == e ? 0 : e.length; ++n < r && !1 !== t(e[n], n, e);); return e }; const Ne = function () { try { var e = se(Object, "defineProperty"); return e({}, "", {}), e } catch (t) { } }(); const Me = function (e, t, n) { "__proto__" == t && Ne ? Ne(e, t, { configurable: !0, enumerable: !0, value: n, writable: !0 }) : e[t] = n }; var Le = Object.prototype.hasOwnProperty; const Ie = function (e, t, n) { var r = e[t]; Le.call(e, t) && M(r, n) && (void 0 !== n || t in e) || Me(e, t, n) }; const Fe = function (e, t, n, r) { var a = !n; n || (n = {}); for (var l = -1, o = t.length; ++l < o;) { var i = t[l], u = r ? r(n[i], e[i], i, n, e) : void 0; void 0 === u && (u = e[i]), a ? Me(n, i, u) : Ie(n, i, u) } return n }; const De = function (e, t) { for (var n = -1, r = Array(e); ++n < e;)r[n] = t(n); return r }; const Ue = function (e) { return T(e) && "[object Arguments]" == E(e) }; var Ve = Object.prototype, Be = Ve.hasOwnProperty, $e = Ve.propertyIsEnumerable; const qe = Ue(function () { return arguments }()) ? Ue : function (e) { return T(e) && Be.call(e, "callee") && !$e.call(e, "callee") }; const We = Array.isArray; const He = function () { return !1 }; var Qe = "object" == typeof exports && exports && !exports.nodeType && exports, Ke = Qe && "object" == typeof module && module && !module.nodeType && module, Ge = Ke && Ke.exports === Qe ? m.Buffer : void 0; const Ye = (Ge ? Ge.isBuffer : void 0) || He; var Xe = /^(?:0|[1-9]\d*)$/; const Ze = function (e, t) { var n = typeof e; return !!(t = null == t ? 9007199254740991 : t) && ("number" == n || "symbol" != n && Xe.test(e)) && e > -1 && e % 1 == 0 && e < t }; const Je = function (e) { return "number" == typeof e && e > -1 && e % 1 == 0 && e <= 9007199254740991 }; var et = {}; et["[object Float32Array]"] = et["[object Float64Array]"] = et["[object Int8Array]"] = et["[object Int16Array]"] = et["[object Int32Array]"] = et["[object Uint8Array]"] = et["[object Uint8ClampedArray]"] = et["[object Uint16Array]"] = et["[object Uint32Array]"] = !0, et["[object Arguments]"] = et["[object Array]"] = et["[object ArrayBuffer]"] = et["[object Boolean]"] = et["[object DataView]"] = et["[object Date]"] = et["[object Error]"] = et["[object Function]"] = et["[object Map]"] = et["[object Number]"] = et["[object Object]"] = et["[object RegExp]"] = et["[object Set]"] = et["[object String]"] = et["[object WeakMap]"] = !1; const tt = function (e) { return T(e) && Je(e.length) && !!et[E(e)] }; const nt = function (e) { return function (t) { return e(t) } }; var rt = "object" == typeof exports && exports && !exports.nodeType && exports, at = rt && "object" == typeof module && module && !module.nodeType && module, lt = at && at.exports === rt && d.process; const ot = function () { try { var e = at && at.require && at.require("util").types; return e || lt && lt.binding && lt.binding("util") } catch (t) { } }(); var it = ot && ot.isTypedArray; const ut = it ? nt(it) : tt; var st = Object.prototype.hasOwnProperty; const ct = function (e, t) { var n = We(e), r = !n && qe(e), a = !n && !r && Ye(e), l = !n && !r && !a && ut(e), o = n || r || a || l, i = o ? De(e.length, String) : [], u = i.length; for (var s in e) !t && !st.call(e, s) || o && ("length" == s || a && ("offset" == s || "parent" == s) || l && ("buffer" == s || "byteLength" == s || "byteOffset" == s) || Ze(s, u)) || i.push(s); return i }; var ft = Object.prototype; const dt = function (e) { var t = e && e.constructor; return e === ("function" == typeof t && t.prototype || ft) }; const pt = x(Object.keys, Object); var mt = Object.prototype.hasOwnProperty; const ht = function (e) { if (!dt(e)) return pt(e); var t = []; for (var n in Object(e)) mt.call(e, n) && "constructor" != n && t.push(n); return t }; const yt = function (e) { return null != e && Je(e.length) && !G(e) }; const vt = function (e) { return yt(e) ? ct(e) : ht(e) }; const bt = function (e, t) { return e && Fe(t, vt(t), e) }; const gt = function (e) { var t = []; if (null != e) for (var n in Object(e)) t.push(n); return t }; var _t = Object.prototype.hasOwnProperty; const St = function (e) { if (!K(e)) return gt(e); var t = dt(e), n = []; for (var r in e) ("constructor" != r || !t && _t.call(e, r)) && n.push(r); return n }; const wt = function (e) { return yt(e) ? ct(e, !0) : St(e) }; const kt = function (e, t) { return e && Fe(t, wt(t), e) }; var Et = "object" == typeof exports && exports && !exports.nodeType && exports, xt = Et && "object" == typeof module && module && !module.nodeType && module, Ct = xt && xt.exports === Et ? m.Buffer : void 0, Tt = Ct ? Ct.allocUnsafe : void 0; const Pt = function (e, t) { if (t) return e.slice(); var n = e.length, r = Tt ? Tt(n) : new e.constructor(n); return e.copy(r), r }; const Ot = function (e, t) { var n = -1, r = e.length; for (t || (t = Array(r)); ++n < r;)t[n] = e[n]; return t }; const zt = function (e, t) { for (var n = -1, r = null == e ? 0 : e.length, a = 0, l = []; ++n < r;) { var o = e[n]; t(o, n, e) && (l[a++] = o) } return l }; const jt = function () { return [] }; var Rt = Object.prototype.propertyIsEnumerable, At = Object.getOwnPropertySymbols; const Nt = At ? function (e) { return null == e ? [] : (e = Object(e), zt(At(e), (function (t) { return Rt.call(e, t) }))) } : jt; const Mt = function (e, t) { return Fe(e, Nt(e), t) }; const Lt = function (e, t) { for (var n = -1, r = t.length, a = e.length; ++n < r;)e[a + n] = t[n]; return e }; const It = Object.getOwnPropertySymbols ? function (e) { for (var t = []; e;)Lt(t, Nt(e)), e = C(e); return t } : jt; const Ft = function (e, t) { return Fe(e, It(e), t) }; const Dt = function (e, t, n) { var r = t(e); return We(e) ? r : Lt(r, n(e)) }; const Ut = function (e) { return Dt(e, vt, Nt) }; const Vt = function (e) { return Dt(e, wt, It) }; const Bt = se(m, "DataView"); const $t = se(m, "Promise"); const qt = se(m, "Set"); const Wt = se(m, "WeakMap"); var Ht = "[object Map]", Qt = "[object Promise]", Kt = "[object Set]", Gt = "[object WeakMap]", Yt = "[object DataView]", Xt = ee(Bt), Zt = ee(ce), Jt = ee($t), en = ee(qt), tn = ee(Wt), nn = E; (Bt && nn(new Bt(new ArrayBuffer(1))) != Yt || ce && nn(new ce) != Ht || $t && nn($t.resolve()) != Qt || qt && nn(new qt) != Kt || Wt && nn(new Wt) != Gt) && (nn = function (e) { var t = E(e), n = "[object Object]" == t ? e.constructor : void 0, r = n ? ee(n) : ""; if (r) switch (r) { case Xt: return Yt; case Zt: return Ht; case Jt: return Qt; case en: return Kt; case tn: return Gt }return t }); const rn = nn; var an = Object.prototype.hasOwnProperty; const ln = function (e) { var t = e.length, n = new e.constructor(t); return t && "string" == typeof e[0] && an.call(e, "index") && (n.index = e.index, n.input = e.input), n }; const on = m.Uint8Array; const un = function (e) { var t = new e.constructor(e.byteLength); return new on(t).set(new on(e)), t }; const sn = function (e, t) { var n = t ? un(e.buffer) : e.buffer; return new e.constructor(n, e.byteOffset, e.byteLength) }; var cn = /\w*$/; const fn = function (e) { var t = new e.constructor(e.source, cn.exec(e)); return t.lastIndex = e.lastIndex, t }; var dn = h ? h.prototype : void 0, pn = dn ? dn.valueOf : void 0; const mn = function (e) { return pn ? Object(pn.call(e)) : {} }; const hn = function (e, t) { var n = t ? un(e.buffer) : e.buffer; return new e.constructor(n, e.byteOffset, e.length) }; const yn = function (e, t, n) { var r = e.constructor; switch (t) { case "[object ArrayBuffer]": return un(e); case "[object Boolean]": case "[object Date]": return new r(+e); case "[object DataView]": return sn(e, n); case "[object Float32Array]": case "[object Float64Array]": case "[object Int8Array]": case "[object Int16Array]": case "[object Int32Array]": case "[object Uint8Array]": case "[object Uint8ClampedArray]": case "[object Uint16Array]": case "[object Uint32Array]": return hn(e, n); case "[object Map]": case "[object Set]": return new r; case "[object Number]": case "[object String]": return new r(e); case "[object RegExp]": return fn(e); case "[object Symbol]": return mn(e) } }; var vn = Object.create; const bn = function () { function e() { } return function (t) { if (!K(t)) return {}; if (vn) return vn(t); e.prototype = t; var n = new e; return e.prototype = void 0, n } }(); const gn = function (e) { return "function" != typeof e.constructor || dt(e) ? {} : bn(C(e)) }; const _n = function (e) { return T(e) && "[object Map]" == rn(e) }; var Sn = ot && ot.isMap; const wn = Sn ? nt(Sn) : _n; const kn = function (e) { return T(e) && "[object Set]" == rn(e) }; var En = ot && ot.isSet; const xn = En ? nt(En) : kn; var Cn = "[object Arguments]", Tn = "[object Function]", Pn = "[object Object]", On = {}; On[Cn] = On["[object Array]"] = On["[object ArrayBuffer]"] = On["[object DataView]"] = On["[object Boolean]"] = On["[object Date]"] = On["[object Float32Array]"] = On["[object Float64Array]"] = On["[object Int8Array]"] = On["[object Int16Array]"] = On["[object Int32Array]"] = On["[object Map]"] = On["[object Number]"] = On[Pn] = On["[object RegExp]"] = On["[object Set]"] = On["[object String]"] = On["[object Symbol]"] = On["[object Uint8Array]"] = On["[object Uint8ClampedArray]"] = On["[object Uint16Array]"] = On["[object Uint32Array]"] = !0, On["[object Error]"] = On[Tn] = On["[object WeakMap]"] = !1; const zn = function e(t, n, r, a, l, o) { var i, u = 1 & n, s = 2 & n, c = 4 & n; if (r && (i = l ? r(t, a, l, o) : r(t)), void 0 !== i) return i; if (!K(t)) return t; var f = We(t); if (f) { if (i = ln(t), !u) return Ot(t, i) } else { var d = rn(t), p = d == Tn || "[object GeneratorFunction]" == d; if (Ye(t)) return Pt(t, u); if (d == Pn || d == Cn || p && !l) { if (i = s || p ? {} : gn(t), !u) return s ? Ft(t, kt(i, t)) : Mt(t, bt(i, t)) } else { if (!On[d]) return l ? t : {}; i = yn(t, d, u) } } o || (o = new Re); var m = o.get(t); if (m) return m; o.set(t, i), xn(t) ? t.forEach((function (a) { i.add(e(a, n, r, a, t, o)) })) : wn(t) && t.forEach((function (a, l) { i.set(l, e(a, n, r, l, t, o)) })); var h = f ? void 0 : (c ? s ? Vt : Ut : s ? wt : vt)(t); return Ae(h || t, (function (a, l) { h && (a = t[l = a]), Ie(i, l, e(a, n, r, l, t, o)) })), i }; const jn = function (e) { return zn(e, 4) }; const Rn = function (e, t) { for (var n = -1, r = null == e ? 0 : e.length, a = Array(r); ++n < r;)a[n] = t(e[n], n, e); return a }; const An = function (e) { return "symbol" == typeof e || T(e) && "[object Symbol]" == E(e) }; function Nn(e, t) { if ("function" != typeof e || null != t && "function" != typeof t) throw new TypeError("Expected a function"); var n = function () { var r = arguments, a = t ? t.apply(this, r) : r[0], l = n.cache; if (l.has(a)) return l.get(a); var o = e.apply(this, r); return n.cache = l.set(a, o) || l, o }; return n.cache = new (Nn.Cache || Oe), n } Nn.Cache = Oe; const Mn = Nn; var Ln = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g, In = /\\(\\)?/g; const Fn = function (e) { var t = Mn(e, (function (e) { return 500 === n.size && n.clear(), e })), n = t.cache; return t }((function (e) { var t = []; return 46 === e.charCodeAt(0) && t.push(""), e.replace(Ln, (function (e, n, r, a) { t.push(r ? a.replace(In, "$1") : n || e) })), t })); const Dn = function (e) { if ("string" == typeof e || An(e)) return e; var t = e + ""; return "0" == t && 1 / e == -1 / 0 ? "-0" : t }; var Un = h ? h.prototype : void 0, Vn = Un ? Un.toString : void 0; const Bn = function e(t) { if ("string" == typeof t) return t; if (We(t)) return Rn(t, e) + ""; if (An(t)) return Vn ? Vn.call(t) : ""; var n = t + ""; return "0" == n && 1 / t == -1 / 0 ? "-0" : n }; const $n = function (e) { return null == e ? "" : Bn(e) }; const qn = function (e) { return We(e) ? Rn(e, Dn) : An(e) ? [e] : Ot(Fn($n(e))) }; const Wn = function (e, t) { }; n(219); const Hn = function (e) { return zn(e, 5) }; function Qn() { return Qn = Object.assign || function (e) { for (var t = 1; t < arguments.length; t++) { var n = arguments[t]; for (var r in n) Object.prototype.hasOwnProperty.call(n, r) && (e[r] = n[r]) } return e }, Qn.apply(this, arguments) } function Kn(e, t) { e.prototype = Object.create(t.prototype), e.prototype.constructor = e, e.__proto__ = t } function Gn(e, t) { if (null == e) return {}; var n, r, a = {}, l = Object.keys(e); for (r = 0; r < l.length; r++)n = l[r], t.indexOf(n) >= 0 || (a[n] = e[n]); return a } function Yn(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e } var Xn = function (e) { return Array.isArray(e) && 0 === e.length }, Zn = function (e) { return "function" === typeof e }, Jn = function (e) { return null !== e && "object" === typeof e }, er = function (e) { return String(Math.floor(Number(e))) === e }, tr = function (e) { return "[object String]" === Object.prototype.toString.call(e) }, nr = function (e) { return 0 === r.Children.count(e) }, rr = function (e) { return Jn(e) && Zn(e.then) }; function ar(e, t, n, r) { void 0 === r && (r = 0); for (var a = qn(t); e && r < a.length;)e = e[a[r++]]; return void 0 === e ? n : e } function lr(e, t, n) { for (var r = jn(e), a = r, l = 0, o = qn(t); l < o.length - 1; l++) { var i = o[l], u = ar(e, o.slice(0, l + 1)); if (u && (Jn(u) || Array.isArray(u))) a = a[i] = jn(u); else { var s = o[l + 1]; a = a[i] = er(s) && Number(s) >= 0 ? [] : {} } } return (0 === l ? e : a)[o[l]] === n ? e : (void 0 === n ? delete a[o[l]] : a[o[l]] = n, 0 === l && void 0 === n && delete r[o[l]], r) } function or(e, t, n, r) { void 0 === n && (n = new WeakMap), void 0 === r && (r = {}); for (var a = 0, l = Object.keys(e); a < l.length; a++) { var o = l[a], i = e[o]; Jn(i) ? n.get(i) || (n.set(i, !0), r[o] = Array.isArray(i) ? [] : {}, or(i, t, n, r[o])) : r[o] = t } return r } var ir = (0, r.createContext)(void 0); ir.displayName = "FormikContext"; var ur = ir.Provider; ir.Consumer; function sr() { var e = (0, r.useContext)(ir); return e || Wn(!1), e } function cr(e, t) { switch (t.type) { case "SET_VALUES": return Qn({}, e, { values: t.payload }); case "SET_TOUCHED": return Qn({}, e, { touched: t.payload }); case "SET_ERRORS": return l()(e.errors, t.payload) ? e : Qn({}, e, { errors: t.payload }); case "SET_STATUS": return Qn({}, e, { status: t.payload }); case "SET_ISSUBMITTING": return Qn({}, e, { isSubmitting: t.payload }); case "SET_ISVALIDATING": return Qn({}, e, { isValidating: t.payload }); case "SET_FIELD_VALUE": return Qn({}, e, { values: lr(e.values, t.payload.field, t.payload.value) }); case "SET_FIELD_TOUCHED": return Qn({}, e, { touched: lr(e.touched, t.payload.field, t.payload.value) }); case "SET_FIELD_ERROR": return Qn({}, e, { errors: lr(e.errors, t.payload.field, t.payload.value) }); case "RESET_FORM": return Qn({}, e, t.payload); case "SET_FORMIK_STATE": return t.payload(e); case "SUBMIT_ATTEMPT": return Qn({}, e, { touched: or(e.values, !0), isSubmitting: !0, submitCount: e.submitCount + 1 }); case "SUBMIT_FAILURE": case "SUBMIT_SUCCESS": return Qn({}, e, { isSubmitting: !1 }); default: return e } } var fr = {}, dr = {}; function pr(e) { var t = e.validateOnChange, n = void 0 === t || t, a = e.validateOnBlur, o = void 0 === a || a, i = e.validateOnMount, u = void 0 !== i && i, s = e.isInitialValid, c = e.enableReinitialize, d = void 0 !== c && c, p = e.onSubmit, m = Gn(e, ["validateOnChange", "validateOnBlur", "validateOnMount", "isInitialValid", "enableReinitialize", "onSubmit"]), h = Qn({ validateOnChange: n, validateOnBlur: o, validateOnMount: u, onSubmit: p }, m), y = (0, r.useRef)(h.initialValues), v = (0, r.useRef)(h.initialErrors || fr), b = (0, r.useRef)(h.initialTouched || dr), g = (0, r.useRef)(h.initialStatus), _ = (0, r.useRef)(!1), S = (0, r.useRef)({}); (0, r.useEffect)((function () { return _.current = !0, function () { _.current = !1 } }), []); var w = (0, r.useReducer)(cr, { values: h.initialValues, errors: h.initialErrors || fr, touched: h.initialTouched || dr, status: h.initialStatus, isSubmitting: !1, isValidating: !1, submitCount: 0 }), k = w[0], E = w[1], x = (0, r.useCallback)((function (e, t) { return new Promise((function (n, r) { var a = h.validate(e, t); null == a ? n(fr) : rr(a) ? a.then((function (e) { n(e || fr) }), (function (e) { r(e) })) : n(a) })) }), [h.validate]), C = (0, r.useCallback)((function (e, t) { var n = h.validationSchema, r = Zn(n) ? n(t) : n, a = t && r.validateAt ? r.validateAt(t, e) : function (e, t, n, r) { void 0 === n && (n = !1); void 0 === r && (r = {}); var a = hr(e); return t[n ? "validateSync" : "validate"](a, { abortEarly: !1, context: r }) }(e, r); return new Promise((function (e, t) { a.then((function () { e(fr) }), (function (n) { "ValidationError" === n.name ? e(function (e) { var t = {}; if (e.inner) { if (0 === e.inner.length) return lr(t, e.path, e.message); var n = e.inner, r = Array.isArray(n), a = 0; for (n = r ? n : n[Symbol.iterator](); ;) { var l; if (r) { if (a >= n.length) break; l = n[a++] } else { if ((a = n.next()).done) break; l = a.value } var o = l; ar(t, o.path) || (t = lr(t, o.path, o.message)) } } return t }(n)) : t(n) })) })) }), [h.validationSchema]), T = (0, r.useCallback)((function (e, t) { return new Promise((function (n) { return n(S.current[e].validate(t)) })) }), []), P = (0, r.useCallback)((function (e) { var t = Object.keys(S.current).filter((function (e) { return Zn(S.current[e].validate) })), n = t.length > 0 ? t.map((function (t) { return T(t, ar(e, t)) })) : [Promise.resolve("DO_NOT_DELETE_YOU_WILL_BE_FIRED")]; return Promise.all(n).then((function (e) { return e.reduce((function (e, n, r) { return "DO_NOT_DELETE_YOU_WILL_BE_FIRED" === n || n && (e = lr(e, t[r], n)), e }), {}) })) }), [T]), O = (0, r.useCallback)((function (e) { return Promise.all([P(e), h.validationSchema ? C(e) : {}, h.validate ? x(e) : {}]).then((function (e) { var t = e[0], n = e[1], r = e[2]; return f.all([t, n, r], { arrayMerge: yr }) })) }), [h.validate, h.validationSchema, P, x, C]), z = br((function (e) { return void 0 === e && (e = k.values), E({ type: "SET_ISVALIDATING", payload: !0 }), O(e).then((function (e) { return _.current && (E({ type: "SET_ISVALIDATING", payload: !1 }), E({ type: "SET_ERRORS", payload: e })), e })) })); (0, r.useEffect)((function () { u && !0 === _.current && l()(y.current, h.initialValues) && z(y.current) }), [u, z]); var j = (0, r.useCallback)((function (e) { var t = e && e.values ? e.values : y.current, n = e && e.errors ? e.errors : v.current ? v.current : h.initialErrors || {}, r = e && e.touched ? e.touched : b.current ? b.current : h.initialTouched || {}, a = e && e.status ? e.status : g.current ? g.current : h.initialStatus; y.current = t, v.current = n, b.current = r, g.current = a; var l = function () { E({ type: "RESET_FORM", payload: { isSubmitting: !!e && !!e.isSubmitting, errors: n, touched: r, status: a, values: t, isValidating: !!e && !!e.isValidating, submitCount: e && e.submitCount && "number" === typeof e.submitCount ? e.submitCount : 0 } }) }; if (h.onReset) { var o = h.onReset(k.values, Y); rr(o) ? o.then(l) : l() } else l() }), [h.initialErrors, h.initialStatus, h.initialTouched]); (0, r.useEffect)((function () { !0 !== _.current || l()(y.current, h.initialValues) || (d && (y.current = h.initialValues, j()), u && z(y.current)) }), [d, h.initialValues, j, u, z]), (0, r.useEffect)((function () { d && !0 === _.current && !l()(v.current, h.initialErrors) && (v.current = h.initialErrors || fr, E({ type: "SET_ERRORS", payload: h.initialErrors || fr })) }), [d, h.initialErrors]), (0, r.useEffect)((function () { d && !0 === _.current && !l()(b.current, h.initialTouched) && (b.current = h.initialTouched || dr, E({ type: "SET_TOUCHED", payload: h.initialTouched || dr })) }), [d, h.initialTouched]), (0, r.useEffect)((function () { d && !0 === _.current && !l()(g.current, h.initialStatus) && (g.current = h.initialStatus, E({ type: "SET_STATUS", payload: h.initialStatus })) }), [d, h.initialStatus, h.initialTouched]); var R = br((function (e) { if (S.current[e] && Zn(S.current[e].validate)) { var t = ar(k.values, e), n = S.current[e].validate(t); return rr(n) ? (E({ type: "SET_ISVALIDATING", payload: !0 }), n.then((function (e) { return e })).then((function (t) { E({ type: "SET_FIELD_ERROR", payload: { field: e, value: t } }), E({ type: "SET_ISVALIDATING", payload: !1 }) }))) : (E({ type: "SET_FIELD_ERROR", payload: { field: e, value: n } }), Promise.resolve(n)) } return h.validationSchema ? (E({ type: "SET_ISVALIDATING", payload: !0 }), C(k.values, e).then((function (e) { return e })).then((function (t) { E({ type: "SET_FIELD_ERROR", payload: { field: e, value: t[e] } }), E({ type: "SET_ISVALIDATING", payload: !1 }) }))) : Promise.resolve() })), A = (0, r.useCallback)((function (e, t) { var n = t.validate; S.current[e] = { validate: n } }), []), N = (0, r.useCallback)((function (e) { delete S.current[e] }), []), M = br((function (e, t) { return E({ type: "SET_TOUCHED", payload: e }), (void 0 === t ? o : t) ? z(k.values) : Promise.resolve() })), L = (0, r.useCallback)((function (e) { E({ type: "SET_ERRORS", payload: e }) }), []), I = br((function (e, t) { var r = Zn(e) ? e(k.values) : e; return E({ type: "SET_VALUES", payload: r }), (void 0 === t ? n : t) ? z(r) : Promise.resolve() })), F = (0, r.useCallback)((function (e, t) { E({ type: "SET_FIELD_ERROR", payload: { field: e, value: t } }) }), []), D = br((function (e, t, r) { return E({ type: "SET_FIELD_VALUE", payload: { field: e, value: t } }), (void 0 === r ? n : r) ? z(lr(k.values, e, t)) : Promise.resolve() })), U = (0, r.useCallback)((function (e, t) { var n, r = t, a = e; if (!tr(e)) { e.persist && e.persist(); var l = e.target ? e.target : e.currentTarget, o = l.type, i = l.name, u = l.id, s = l.value, c = l.checked, f = (l.outerHTML, l.options), d = l.multiple; r = t || (i || u), a = /number|range/.test(o) ? (n = parseFloat(s), isNaN(n) ? "" : n) : /checkbox/.test(o) ? function (e, t, n) { if ("boolean" === typeof e) return Boolean(t); var r = [], a = !1, l = -1; if (Array.isArray(e)) r = e, a = (l = e.indexOf(n)) >= 0; else if (!n || "true" == n || "false" == n) return Boolean(t); if (t && n && !a) return r.concat(n); if (!a) return r; return r.slice(0, l).concat(r.slice(l + 1)) }(ar(k.values, r), c, s) : f && d ? function (e) { return Array.from(e).filter((function (e) { return e.selected })).map((function (e) { return e.value })) }(f) : s } r && D(r, a) }), [D, k.values]), V = br((function (e) { if (tr(e)) return function (t) { return U(t, e) }; U(e) })), B = br((function (e, t, n) { return void 0 === t && (t = !0), E({ type: "SET_FIELD_TOUCHED", payload: { field: e, value: t } }), (void 0 === n ? o : n) ? z(k.values) : Promise.resolve() })), $ = (0, r.useCallback)((function (e, t) { e.persist && e.persist(); var n = e.target, r = n.name, a = n.id, l = (n.outerHTML, t || (r || a)); B(l, !0) }), [B]), q = br((function (e) { if (tr(e)) return function (t) { return $(t, e) }; $(e) })), W = (0, r.useCallback)((function (e) { Zn(e) ? E({ type: "SET_FORMIK_STATE", payload: e }) : E({ type: "SET_FORMIK_STATE", payload: function () { return e } }) }), []), H = (0, r.useCallback)((function (e) { E({ type: "SET_STATUS", payload: e }) }), []), Q = (0, r.useCallback)((function (e) { E({ type: "SET_ISSUBMITTING", payload: e }) }), []), K = br((function () { return E({ type: "SUBMIT_ATTEMPT" }), z().then((function (e) { var t = e instanceof Error; if (!t && 0 === Object.keys(e).length) { var n; try { if (void 0 === (n = X())) return } catch (r) { throw r } return Promise.resolve(n).then((function (e) { return _.current && E({ type: "SUBMIT_SUCCESS" }), e })).catch((function (e) { if (_.current) throw E({ type: "SUBMIT_FAILURE" }), e })) } if (_.current && (E({ type: "SUBMIT_FAILURE" }), t)) throw e })) })), G = br((function (e) { e && e.preventDefault && Zn(e.preventDefault) && e.preventDefault(), e && e.stopPropagation && Zn(e.stopPropagation) && e.stopPropagation(), K().catch((function (e) { console.warn("Warning: An unhandled error was caught from submitForm()", e) })) })), Y = { resetForm: j, validateForm: z, validateField: R, setErrors: L, setFieldError: F, setFieldTouched: B, setFieldValue: D, setStatus: H, setSubmitting: Q, setTouched: M, setValues: I, setFormikState: W, submitForm: K }, X = br((function () { return p(k.values, Y) })), Z = br((function (e) { e && e.preventDefault && Zn(e.preventDefault) && e.preventDefault(), e && e.stopPropagation && Zn(e.stopPropagation) && e.stopPropagation(), j() })), J = (0, r.useCallback)((function (e) { return { value: ar(k.values, e), error: ar(k.errors, e), touched: !!ar(k.touched, e), initialValue: ar(y.current, e), initialTouched: !!ar(b.current, e), initialError: ar(v.current, e) } }), [k.errors, k.touched, k.values]), ee = (0, r.useCallback)((function (e) { return { setValue: function (t, n) { return D(e, t, n) }, setTouched: function (t, n) { return B(e, t, n) }, setError: function (t) { return F(e, t) } } }), [D, B, F]), te = (0, r.useCallback)((function (e) { var t = Jn(e), n = t ? e.name : e, r = ar(k.values, n), a = { name: n, value: r, onChange: V, onBlur: q }; if (t) { var l = e.type, o = e.value, i = e.as, u = e.multiple; "checkbox" === l ? void 0 === o ? a.checked = !!r : (a.checked = !(!Array.isArray(r) || !~r.indexOf(o)), a.value = o) : "radio" === l ? (a.checked = r === o, a.value = o) : "select" === i && u && (a.value = a.value || [], a.multiple = !0) } return a }), [q, V, k.values]), ne = (0, r.useMemo)((function () { return !l()(y.current, k.values) }), [y.current, k.values]), re = (0, r.useMemo)((function () { return "undefined" !== typeof s ? ne ? k.errors && 0 === Object.keys(k.errors).length : !1 !== s && Zn(s) ? s(h) : s : k.errors && 0 === Object.keys(k.errors).length }), [s, ne, k.errors, h]); return Qn({}, k, { initialValues: y.current, initialErrors: v.current, initialTouched: b.current, initialStatus: g.current, handleBlur: q, handleChange: V, handleReset: Z, handleSubmit: G, resetForm: j, setErrors: L, setFormikState: W, setFieldTouched: B, setFieldValue: D, setFieldError: F, setStatus: H, setSubmitting: Q, setTouched: M, setValues: I, submitForm: K, validateForm: z, validateField: R, isValid: re, dirty: ne, unregisterField: N, registerField: A, getFieldProps: te, getFieldMeta: J, getFieldHelpers: ee, validateOnBlur: o, validateOnChange: n, validateOnMount: u }) } function mr(e) { var t = pr(e), n = e.component, a = e.children, l = e.render, o = e.innerRef; return (0, r.useImperativeHandle)(o, (function () { return t })), (0, r.createElement)(ur, { value: t }, n ? (0, r.createElement)(n, t) : l ? l(t) : a ? Zn(a) ? a(t) : nr(a) ? null : r.Children.only(a) : null) } function hr(e) { var t = Array.isArray(e) ? [] : {}; for (var n in e) if (Object.prototype.hasOwnProperty.call(e, n)) { var r = String(n); !0 === Array.isArray(e[r]) ? t[r] = e[r].map((function (e) { return !0 === Array.isArray(e) || A(e) ? hr(e) : "" !== e ? e : void 0 })) : A(e[r]) ? t[r] = hr(e[r]) : t[r] = "" !== e[r] ? e[r] : void 0 } return t } function yr(e, t, n) { var r = e.slice(); return t.forEach((function (t, a) { if ("undefined" === typeof r[a]) { var l = !1 !== n.clone && n.isMergeableObject(t); r[a] = l ? f(Array.isArray(t) ? [] : {}, t, n) : t } else n.isMergeableObject(t) ? r[a] = f(e[a], t, n) : -1 === e.indexOf(t) && r.push(t) })), r } var vr = "undefined" !== typeof window && "undefined" !== typeof window.document && "undefined" !== typeof window.document.createElement ? r.useLayoutEffect : r.useEffect; function br(e) { var t = (0, r.useRef)(e); return vr((function () { t.current = e })), (0, r.useCallback)((function () { for (var e = arguments.length, n = new Array(e), r = 0; r < e; r++)n[r] = arguments[r]; return t.current.apply(void 0, n) }), []) } function gr(e) { var t = e.validate, n = e.name, a = e.render, l = e.children, o = e.as, i = e.component, u = Gn(e, ["validate", "name", "render", "children", "as", "component"]), s = Gn(sr(), ["validate", "validationSchema"]); var c = s.registerField, f = s.unregisterField; (0, r.useEffect)((function () { return c(n, { validate: t }), function () { f(n) } }), [c, f, n, t]); var d = s.getFieldProps(Qn({ name: n }, u)), p = s.getFieldMeta(n), m = { field: d, form: s }; if (a) return a(Qn({}, m, { meta: p })); if (Zn(l)) return l(Qn({}, m, { meta: p })); if (i) { if ("string" === typeof i) { var h = u.innerRef, y = Gn(u, ["innerRef"]); return (0, r.createElement)(i, Qn({ ref: h }, d, y), l) } return (0, r.createElement)(i, Qn({ field: d, form: s }, u), l) } var v = o || "input"; if ("string" === typeof v) { var b = u.innerRef, g = Gn(u, ["innerRef"]); return (0, r.createElement)(v, Qn({ ref: b }, d, g), l) } return (0, r.createElement)(v, Qn({}, d, u), l) } var _r = (0, r.forwardRef)((function (e, t) { var n = e.action, a = Gn(e, ["action"]), l = null != n ? n : "#", o = sr(), i = o.handleReset, u = o.handleSubmit; return (0, r.createElement)("form", Object.assign({ onSubmit: u, ref: t, onReset: i, action: l }, a)) })); _r.displayName = "Form"; var Sr = function (e, t, n) { var r = wr(e); return r.splice(t, 0, n), r }, wr = function (e) { if (e) { if (Array.isArray(e)) return [].concat(e); var t = Object.keys(e).map((function (e) { return parseInt(e) })).reduce((function (e, t) { return t > e ? t : e }), 0); return Array.from(Qn({}, e, { length: t + 1 })) } return [] }, kr = function (e) { function t(t) { var n; return (n = e.call(this, t) || this).updateArrayField = function (e, t, r) { var a = n.props, l = a.name; (0, a.formik.setFormikState)((function (n) { var a = "function" === typeof r ? r : e, o = "function" === typeof t ? t : e, i = lr(n.values, l, e(ar(n.values, l))), u = r ? a(ar(n.errors, l)) : void 0, s = t ? o(ar(n.touched, l)) : void 0; return Xn(u) && (u = void 0), Xn(s) && (s = void 0), Qn({}, n, { values: i, errors: r ? lr(n.errors, l, u) : n.errors, touched: t ? lr(n.touched, l, s) : n.touched }) })) }, n.push = function (e) { return n.updateArrayField((function (t) { return [].concat(wr(t), [Hn(e)]) }), !1, !1) }, n.handlePush = function (e) { return function () { return n.push(e) } }, n.swap = function (e, t) { return n.updateArrayField((function (n) { return function (e, t, n) { var r = wr(e), a = r[t]; return r[t] = r[n], r[n] = a, r }(n, e, t) }), !0, !0) }, n.handleSwap = function (e, t) { return function () { return n.swap(e, t) } }, n.move = function (e, t) { return n.updateArrayField((function (n) { return function (e, t, n) { var r = wr(e), a = r[t]; return r.splice(t, 1), r.splice(n, 0, a), r }(n, e, t) }), !0, !0) }, n.handleMove = function (e, t) { return function () { return n.move(e, t) } }, n.insert = function (e, t) { return n.updateArrayField((function (n) { return Sr(n, e, t) }), (function (t) { return Sr(t, e, null) }), (function (t) { return Sr(t, e, null) })) }, n.handleInsert = function (e, t) { return function () { return n.insert(e, t) } }, n.replace = function (e, t) { return n.updateArrayField((function (n) { return function (e, t, n) { var r = wr(e); return r[t] = n, r }(n, e, t) }), !1, !1) }, n.handleReplace = function (e, t) { return function () { return n.replace(e, t) } }, n.unshift = function (e) { var t = -1; return n.updateArrayField((function (n) { var r = n ? [e].concat(n) : [e]; return t < 0 && (t = r.length), r }), (function (e) { var n = e ? [null].concat(e) : [null]; return t < 0 && (t = n.length), n }), (function (e) { var n = e ? [null].concat(e) : [null]; return t < 0 && (t = n.length), n })), t }, n.handleUnshift = function (e) { return function () { return n.unshift(e) } }, n.handleRemove = function (e) { return function () { return n.remove(e) } }, n.handlePop = function () { return function () { return n.pop() } }, n.remove = n.remove.bind(Yn(n)), n.pop = n.pop.bind(Yn(n)), n } Kn(t, e); var n = t.prototype; return n.componentDidUpdate = function (e) { this.props.validateOnChange && this.props.formik.validateOnChange && !l()(ar(e.formik.values, e.name), ar(this.props.formik.values, this.props.name)) && this.props.formik.validateForm(this.props.formik.values) }, n.remove = function (e) { var t; return this.updateArrayField((function (n) { var r = n ? wr(n) : []; return t || (t = r[e]), Zn(r.splice) && r.splice(e, 1), r }), !0, !0), t }, n.pop = function () { var e; return this.updateArrayField((function (t) { var n = t; return e || (e = n && n.pop && n.pop()), n }), !0, !0), e }, n.render = function () { var e = { push: this.push, pop: this.pop, swap: this.swap, move: this.move, insert: this.insert, replace: this.replace, unshift: this.unshift, remove: this.remove, handlePush: this.handlePush, handlePop: this.handlePop, handleSwap: this.handleSwap, handleMove: this.handleMove, handleInsert: this.handleInsert, handleReplace: this.handleReplace, handleUnshift: this.handleUnshift, handleRemove: this.handleRemove }, t = this.props, n = t.component, a = t.render, l = t.children, o = t.name, i = Qn({}, e, { form: Gn(t.formik, ["validate", "validationSchema"]), name: o }); return n ? (0, r.createElement)(n, i) : a ? a(i) : l ? "function" === typeof l ? l(i) : nr(l) ? null : r.Children.only(l) : null }, t }(r.Component); kr.defaultProps = { validateOnChange: !0 } }, 219: (e, t, n) => { var r = n(86), a = { childContextTypes: !0, contextType: !0, contextTypes: !0, defaultProps: !0, displayName: !0, getDefaultProps: !0, getDerivedStateFromError: !0, getDerivedStateFromProps: !0, mixins: !0, propTypes: !0, type: !0 }, l = { name: !0, length: !0, prototype: !0, caller: !0, callee: !0, arguments: !0, arity: !0 }, o = { $$typeof: !0, compare: !0, defaultProps: !0, displayName: !0, propTypes: !0, type: !0 }, i = {}; function u(e) { return r.isMemo(e) ? o : i[e.$$typeof] || a } i[r.ForwardRef] = { $$typeof: !0, render: !0, defaultProps: !0, displayName: !0, propTypes: !0 }, i[r.Memo] = o; var s = Object.defineProperty, c = Object.getOwnPropertyNames, f = Object.getOwnPropertySymbols, d = Object.getOwnPropertyDescriptor, p = Object.getPrototypeOf, m = Object.prototype; e.exports = function e(t, n, r) { if ("string" !== typeof n) { if (m) { var a = p(n); a && a !== m && e(t, a, r) } var o = c(n); f && (o = o.concat(f(n))); for (var i = u(t), h = u(n), y = 0; y < o.length; ++y) { var v = o[y]; if (!l[v] && (!r || !r[v]) && (!h || !h[v]) && (!i || !i[v])) { var b = d(n, v); try { s(t, v, b) } catch (g) { } } } } return t } }, 730: (e, t, n) => { var r = n(43), a = n(853); function l(e) { for (var t = "https://reactjs.org/docs/error-decoder.html?invariant=" + e, n = 1; n < arguments.length; n++)t += "&args[]=" + encodeURIComponent(arguments[n]); return "Minified React error #" + e + "; visit " + t + " for the full message or use the non-minified dev environment for full errors and additional helpful warnings." } var o = new Set, i = {}; function u(e, t) { s(e, t), s(e + "Capture", t) } function s(e, t) { for (i[e] = t, e = 0; e < t.length; e++)o.add(t[e]) } var c = !("undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement), f = Object.prototype.hasOwnProperty, d = /^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, p = {}, m = {}; function h(e, t, n, r, a, l, o) { this.acceptsBooleans = 2 === t || 3 === t || 4 === t, this.attributeName = r, this.attributeNamespace = a, this.mustUseProperty = n, this.propertyName = e, this.type = t, this.sanitizeURL = l, this.removeEmptyString = o } var y = {}; "children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach((function (e) { y[e] = new h(e, 0, !1, e, null, !1, !1) })), [["acceptCharset", "accept-charset"], ["className", "class"], ["htmlFor", "for"], ["httpEquiv", "http-equiv"]].forEach((function (e) { var t = e[0]; y[t] = new h(t, 1, !1, e[1], null, !1, !1) })), ["contentEditable", "draggable", "spellCheck", "value"].forEach((function (e) { y[e] = new h(e, 2, !1, e.toLowerCase(), null, !1, !1) })), ["autoReverse", "externalResourcesRequired", "focusable", "preserveAlpha"].forEach((function (e) { y[e] = new h(e, 2, !1, e, null, !1, !1) })), "allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach((function (e) { y[e] = new h(e, 3, !1, e.toLowerCase(), null, !1, !1) })), ["checked", "multiple", "muted", "selected"].forEach((function (e) { y[e] = new h(e, 3, !0, e, null, !1, !1) })), ["capture", "download"].forEach((function (e) { y[e] = new h(e, 4, !1, e, null, !1, !1) })), ["cols", "rows", "size", "span"].forEach((function (e) { y[e] = new h(e, 6, !1, e, null, !1, !1) })), ["rowSpan", "start"].forEach((function (e) { y[e] = new h(e, 5, !1, e.toLowerCase(), null, !1, !1) })); var v = /[\-:]([a-z])/g; function b(e) { return e[1].toUpperCase() } function g(e, t, n, r) { var a = y.hasOwnProperty(t) ? y[t] : null; (null !== a ? 0 !== a.type : r || !(2 < t.length) || "o" !== t[0] && "O" !== t[0] || "n" !== t[1] && "N" !== t[1]) && (function (e, t, n, r) { if (null === t || "undefined" === typeof t || function (e, t, n, r) { if (null !== n && 0 === n.type) return !1; switch (typeof t) { case "function": case "symbol": return !0; case "boolean": return !r && (null !== n ? !n.acceptsBooleans : "data-" !== (e = e.toLowerCase().slice(0, 5)) && "aria-" !== e); default: return !1 } }(e, t, n, r)) return !0; if (r) return !1; if (null !== n) switch (n.type) { case 3: return !t; case 4: return !1 === t; case 5: return isNaN(t); case 6: return isNaN(t) || 1 > t }return !1 }(t, n, a, r) && (n = null), r || null === a ? function (e) { return !!f.call(m, e) || !f.call(p, e) && (d.test(e) ? m[e] = !0 : (p[e] = !0, !1)) }(t) && (null === n ? e.removeAttribute(t) : e.setAttribute(t, "" + n)) : a.mustUseProperty ? e[a.propertyName] = null === n ? 3 !== a.type && "" : n : (t = a.attributeName, r = a.attributeNamespace, null === n ? e.removeAttribute(t) : (n = 3 === (a = a.type) || 4 === a && !0 === n ? "" : "" + n, r ? e.setAttributeNS(r, t, n) : e.setAttribute(t, n)))) } "accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function (e) { var t = e.replace(v, b); y[t] = new h(t, 1, !1, e, null, !1, !1) })), "xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function (e) { var t = e.replace(v, b); y[t] = new h(t, 1, !1, e, "http://www.w3.org/1999/xlink", !1, !1) })), ["xml:base", "xml:lang", "xml:space"].forEach((function (e) { var t = e.replace(v, b); y[t] = new h(t, 1, !1, e, "http://www.w3.org/XML/1998/namespace", !1, !1) })), ["tabIndex", "crossOrigin"].forEach((function (e) { y[e] = new h(e, 1, !1, e.toLowerCase(), null, !1, !1) })), y.xlinkHref = new h("xlinkHref", 1, !1, "xlink:href", "http://www.w3.org/1999/xlink", !0, !1), ["src", "href", "action", "formAction"].forEach((function (e) { y[e] = new h(e, 1, !1, e.toLowerCase(), null, !0, !0) })); var _ = r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, S = Symbol.for("react.element"), w = Symbol.for("react.portal"), k = Symbol.for("react.fragment"), E = Symbol.for("react.strict_mode"), x = Symbol.for("react.profiler"), C = Symbol.for("react.provider"), T = Symbol.for("react.context"), P = Symbol.for("react.forward_ref"), O = Symbol.for("react.suspense"), z = Symbol.for("react.suspense_list"), j = Symbol.for("react.memo"), R = Symbol.for("react.lazy"); Symbol.for("react.scope"), Symbol.for("react.debug_trace_mode"); var A = Symbol.for("react.offscreen"); Symbol.for("react.legacy_hidden"), Symbol.for("react.cache"), Symbol.for("react.tracing_marker"); var N = Symbol.iterator; function M(e) { return null === e || "object" !== typeof e ? null : "function" === typeof (e = N && e[N] || e["@@iterator"]) ? e : null } var L, I = Object.assign; function F(e) { if (void 0 === L) try { throw Error() } catch (n) { var t = n.stack.trim().match(/\n( *(at )?)/); L = t && t[1] || "" } return "\n" + L + e } var D = !1; function U(e, t) { if (!e || D) return ""; D = !0; var n = Error.prepareStackTrace; Error.prepareStackTrace = void 0; try { if (t) if (t = function () { throw Error() }, Object.defineProperty(t.prototype, "props", { set: function () { throw Error() } }), "object" === typeof Reflect && Reflect.construct) { try { Reflect.construct(t, []) } catch (s) { var r = s } Reflect.construct(e, [], t) } else { try { t.call() } catch (s) { r = s } e.call(t.prototype) } else { try { throw Error() } catch (s) { r = s } e() } } catch (s) { if (s && r && "string" === typeof s.stack) { for (var a = s.stack.split("\n"), l = r.stack.split("\n"), o = a.length - 1, i = l.length - 1; 1 <= o && 0 <= i && a[o] !== l[i];)i--; for (; 1 <= o && 0 <= i; o--, i--)if (a[o] !== l[i]) { if (1 !== o || 1 !== i) do { if (o--, 0 > --i || a[o] !== l[i]) { var u = "\n" + a[o].replace(" at new ", " at "); return e.displayName && u.includes("") && (u = u.replace("", e.displayName)), u } } while (1 <= o && 0 <= i); break } } } finally { D = !1, Error.prepareStackTrace = n } return (e = e ? e.displayName || e.name : "") ? F(e) : "" } function V(e) { switch (e.tag) { case 5: return F(e.type); case 16: return F("Lazy"); case 13: return F("Suspense"); case 19: return F("SuspenseList"); case 0: case 2: case 15: return e = U(e.type, !1); case 11: return e = U(e.type.render, !1); case 1: return e = U(e.type, !0); default: return "" } } function B(e) { if (null == e) return null; if ("function" === typeof e) return e.displayName || e.name || null; if ("string" === typeof e) return e; switch (e) { case k: return "Fragment"; case w: return "Portal"; case x: return "Profiler"; case E: return "StrictMode"; case O: return "Suspense"; case z: return "SuspenseList" }if ("object" === typeof e) switch (e.$$typeof) { case T: return (e.displayName || "Context") + ".Consumer"; case C: return (e._context.displayName || "Context") + ".Provider"; case P: var t = e.render; return (e = e.displayName) || (e = "" !== (e = t.displayName || t.name || "") ? "ForwardRef(" + e + ")" : "ForwardRef"), e; case j: return null !== (t = e.displayName || null) ? t : B(e.type) || "Memo"; case R: t = e._payload, e = e._init; try { return B(e(t)) } catch (n) { } }return null } function $(e) { var t = e.type; switch (e.tag) { case 24: return "Cache"; case 9: return (t.displayName || "Context") + ".Consumer"; case 10: return (t._context.displayName || "Context") + ".Provider"; case 18: return "DehydratedFragment"; case 11: return e = (e = t.render).displayName || e.name || "", t.displayName || ("" !== e ? "ForwardRef(" + e + ")" : "ForwardRef"); case 7: return "Fragment"; case 5: return t; case 4: return "Portal"; case 3: return "Root"; case 6: return "Text"; case 16: return B(t); case 8: return t === E ? "StrictMode" : "Mode"; case 22: return "Offscreen"; case 12: return "Profiler"; case 21: return "Scope"; case 13: return "Suspense"; case 19: return "SuspenseList"; case 25: return "TracingMarker"; case 1: case 0: case 17: case 2: case 14: case 15: if ("function" === typeof t) return t.displayName || t.name || null; if ("string" === typeof t) return t }return null } function q(e) { switch (typeof e) { case "boolean": case "number": case "string": case "undefined": case "object": return e; default: return "" } } function W(e) { var t = e.type; return (e = e.nodeName) && "input" === e.toLowerCase() && ("checkbox" === t || "radio" === t) } function H(e) { e._valueTracker || (e._valueTracker = function (e) { var t = W(e) ? "checked" : "value", n = Object.getOwnPropertyDescriptor(e.constructor.prototype, t), r = "" + e[t]; if (!e.hasOwnProperty(t) && "undefined" !== typeof n && "function" === typeof n.get && "function" === typeof n.set) { var a = n.get, l = n.set; return Object.defineProperty(e, t, { configurable: !0, get: function () { return a.call(this) }, set: function (e) { r = "" + e, l.call(this, e) } }), Object.defineProperty(e, t, { enumerable: n.enumerable }), { getValue: function () { return r }, setValue: function (e) { r = "" + e }, stopTracking: function () { e._valueTracker = null, delete e[t] } } } }(e)) } function Q(e) { if (!e) return !1; var t = e._valueTracker; if (!t) return !0; var n = t.getValue(), r = ""; return e && (r = W(e) ? e.checked ? "true" : "false" : e.value), (e = r) !== n && (t.setValue(e), !0) } function K(e) { if ("undefined" === typeof (e = e || ("undefined" !== typeof document ? document : void 0))) return null; try { return e.activeElement || e.body } catch (t) { return e.body } } function G(e, t) { var n = t.checked; return I({}, t, { defaultChecked: void 0, defaultValue: void 0, value: void 0, checked: null != n ? n : e._wrapperState.initialChecked }) } function Y(e, t) { var n = null == t.defaultValue ? "" : t.defaultValue, r = null != t.checked ? t.checked : t.defaultChecked; n = q(null != t.value ? t.value : n), e._wrapperState = { initialChecked: r, initialValue: n, controlled: "checkbox" === t.type || "radio" === t.type ? null != t.checked : null != t.value } } function X(e, t) { null != (t = t.checked) && g(e, "checked", t, !1) } function Z(e, t) { X(e, t); var n = q(t.value), r = t.type; if (null != n) "number" === r ? (0 === n && "" === e.value || e.value != n) && (e.value = "" + n) : e.value !== "" + n && (e.value = "" + n); else if ("submit" === r || "reset" === r) return void e.removeAttribute("value"); t.hasOwnProperty("value") ? ee(e, t.type, n) : t.hasOwnProperty("defaultValue") && ee(e, t.type, q(t.defaultValue)), null == t.checked && null != t.defaultChecked && (e.defaultChecked = !!t.defaultChecked) } function J(e, t, n) { if (t.hasOwnProperty("value") || t.hasOwnProperty("defaultValue")) { var r = t.type; if (!("submit" !== r && "reset" !== r || void 0 !== t.value && null !== t.value)) return; t = "" + e._wrapperState.initialValue, n || t === e.value || (e.value = t), e.defaultValue = t } "" !== (n = e.name) && (e.name = ""), e.defaultChecked = !!e._wrapperState.initialChecked, "" !== n && (e.name = n) } function ee(e, t, n) { "number" === t && K(e.ownerDocument) === e || (null == n ? e.defaultValue = "" + e._wrapperState.initialValue : e.defaultValue !== "" + n && (e.defaultValue = "" + n)) } var te = Array.isArray; function ne(e, t, n, r) { if (e = e.options, t) { t = {}; for (var a = 0; a < n.length; a++)t["$" + n[a]] = !0; for (n = 0; n < e.length; n++)a = t.hasOwnProperty("$" + e[n].value), e[n].selected !== a && (e[n].selected = a), a && r && (e[n].defaultSelected = !0) } else { for (n = "" + q(n), t = null, a = 0; a < e.length; a++) { if (e[a].value === n) return e[a].selected = !0, void (r && (e[a].defaultSelected = !0)); null !== t || e[a].disabled || (t = e[a]) } null !== t && (t.selected = !0) } } function re(e, t) { if (null != t.dangerouslySetInnerHTML) throw Error(l(91)); return I({}, t, { value: void 0, defaultValue: void 0, children: "" + e._wrapperState.initialValue }) } function ae(e, t) { var n = t.value; if (null == n) { if (n = t.children, t = t.defaultValue, null != n) { if (null != t) throw Error(l(92)); if (te(n)) { if (1 < n.length) throw Error(l(93)); n = n[0] } t = n } null == t && (t = ""), n = t } e._wrapperState = { initialValue: q(n) } } function le(e, t) { var n = q(t.value), r = q(t.defaultValue); null != n && ((n = "" + n) !== e.value && (e.value = n), null == t.defaultValue && e.defaultValue !== n && (e.defaultValue = n)), null != r && (e.defaultValue = "" + r) } function oe(e) { var t = e.textContent; t === e._wrapperState.initialValue && "" !== t && null !== t && (e.value = t) } function ie(e) { switch (e) { case "svg": return "http://www.w3.org/2000/svg"; case "math": return "http://www.w3.org/1998/Math/MathML"; default: return "http://www.w3.org/1999/xhtml" } } function ue(e, t) { return null == e || "http://www.w3.org/1999/xhtml" === e ? ie(t) : "http://www.w3.org/2000/svg" === e && "foreignObject" === t ? "http://www.w3.org/1999/xhtml" : e } var se, ce, fe = (ce = function (e, t) { if ("http://www.w3.org/2000/svg" !== e.namespaceURI || "innerHTML" in e) e.innerHTML = t; else { for ((se = se || document.createElement("div")).innerHTML = "" + t.valueOf().toString() + "", t = se.firstChild; e.firstChild;)e.removeChild(e.firstChild); for (; t.firstChild;)e.appendChild(t.firstChild) } }, "undefined" !== typeof MSApp && MSApp.execUnsafeLocalFunction ? function (e, t, n, r) { MSApp.execUnsafeLocalFunction((function () { return ce(e, t) })) } : ce); function de(e, t) { if (t) { var n = e.firstChild; if (n && n === e.lastChild && 3 === n.nodeType) return void (n.nodeValue = t) } e.textContent = t } var pe = { animationIterationCount: !0, aspectRatio: !0, borderImageOutset: !0, borderImageSlice: !0, borderImageWidth: !0, boxFlex: !0, boxFlexGroup: !0, boxOrdinalGroup: !0, columnCount: !0, columns: !0, flex: !0, flexGrow: !0, flexPositive: !0, flexShrink: !0, flexNegative: !0, flexOrder: !0, gridArea: !0, gridRow: !0, gridRowEnd: !0, gridRowSpan: !0, gridRowStart: !0, gridColumn: !0, gridColumnEnd: !0, gridColumnSpan: !0, gridColumnStart: !0, fontWeight: !0, lineClamp: !0, lineHeight: !0, opacity: !0, order: !0, orphans: !0, tabSize: !0, widows: !0, zIndex: !0, zoom: !0, fillOpacity: !0, floodOpacity: !0, stopOpacity: !0, strokeDasharray: !0, strokeDashoffset: !0, strokeMiterlimit: !0, strokeOpacity: !0, strokeWidth: !0 }, me = ["Webkit", "ms", "Moz", "O"]; function he(e, t, n) { return null == t || "boolean" === typeof t || "" === t ? "" : n || "number" !== typeof t || 0 === t || pe.hasOwnProperty(e) && pe[e] ? ("" + t).trim() : t + "px" } function ye(e, t) { for (var n in e = e.style, t) if (t.hasOwnProperty(n)) { var r = 0 === n.indexOf("--"), a = he(n, t[n], r); "float" === n && (n = "cssFloat"), r ? e.setProperty(n, a) : e[n] = a } } Object.keys(pe).forEach((function (e) { me.forEach((function (t) { t = t + e.charAt(0).toUpperCase() + e.substring(1), pe[t] = pe[e] })) })); var ve = I({ menuitem: !0 }, { area: !0, base: !0, br: !0, col: !0, embed: !0, hr: !0, img: !0, input: !0, keygen: !0, link: !0, meta: !0, param: !0, source: !0, track: !0, wbr: !0 }); function be(e, t) { if (t) { if (ve[e] && (null != t.children || null != t.dangerouslySetInnerHTML)) throw Error(l(137, e)); if (null != t.dangerouslySetInnerHTML) { if (null != t.children) throw Error(l(60)); if ("object" !== typeof t.dangerouslySetInnerHTML || !("__html" in t.dangerouslySetInnerHTML)) throw Error(l(61)) } if (null != t.style && "object" !== typeof t.style) throw Error(l(62)) } } function ge(e, t) { if (-1 === e.indexOf("-")) return "string" === typeof t.is; switch (e) { case "annotation-xml": case "color-profile": case "font-face": case "font-face-src": case "font-face-uri": case "font-face-format": case "font-face-name": case "missing-glyph": return !1; default: return !0 } } var _e = null; function Se(e) { return (e = e.target || e.srcElement || window).correspondingUseElement && (e = e.correspondingUseElement), 3 === e.nodeType ? e.parentNode : e } var we = null, ke = null, Ee = null; function xe(e) { if (e = ga(e)) { if ("function" !== typeof we) throw Error(l(280)); var t = e.stateNode; t && (t = Sa(t), we(e.stateNode, e.type, t)) } } function Ce(e) { ke ? Ee ? Ee.push(e) : Ee = [e] : ke = e } function Te() { if (ke) { var e = ke, t = Ee; if (Ee = ke = null, xe(e), t) for (e = 0; e < t.length; e++)xe(t[e]) } } function Pe(e, t) { return e(t) } function Oe() { } var ze = !1; function je(e, t, n) { if (ze) return e(t, n); ze = !0; try { return Pe(e, t, n) } finally { ze = !1, (null !== ke || null !== Ee) && (Oe(), Te()) } } function Re(e, t) { var n = e.stateNode; if (null === n) return null; var r = Sa(n); if (null === r) return null; n = r[t]; e: switch (t) { case "onClick": case "onClickCapture": case "onDoubleClick": case "onDoubleClickCapture": case "onMouseDown": case "onMouseDownCapture": case "onMouseMove": case "onMouseMoveCapture": case "onMouseUp": case "onMouseUpCapture": case "onMouseEnter": (r = !r.disabled) || (r = !("button" === (e = e.type) || "input" === e || "select" === e || "textarea" === e)), e = !r; break e; default: e = !1 }if (e) return null; if (n && "function" !== typeof n) throw Error(l(231, t, typeof n)); return n } var Ae = !1; if (c) try { var Ne = {}; Object.defineProperty(Ne, "passive", { get: function () { Ae = !0 } }), window.addEventListener("test", Ne, Ne), window.removeEventListener("test", Ne, Ne) } catch (ce) { Ae = !1 } function Me(e, t, n, r, a, l, o, i, u) { var s = Array.prototype.slice.call(arguments, 3); try { t.apply(n, s) } catch (c) { this.onError(c) } } var Le = !1, Ie = null, Fe = !1, De = null, Ue = { onError: function (e) { Le = !0, Ie = e } }; function Ve(e, t, n, r, a, l, o, i, u) { Le = !1, Ie = null, Me.apply(Ue, arguments) } function Be(e) { var t = e, n = e; if (e.alternate) for (; t.return;)t = t.return; else { e = t; do { 0 !== (4098 & (t = e).flags) && (n = t.return), e = t.return } while (e) } return 3 === t.tag ? n : null } function $e(e) { if (13 === e.tag) { var t = e.memoizedState; if (null === t && (null !== (e = e.alternate) && (t = e.memoizedState)), null !== t) return t.dehydrated } return null } function qe(e) { if (Be(e) !== e) throw Error(l(188)) } function We(e) { return null !== (e = function (e) { var t = e.alternate; if (!t) { if (null === (t = Be(e))) throw Error(l(188)); return t !== e ? null : e } for (var n = e, r = t; ;) { var a = n.return; if (null === a) break; var o = a.alternate; if (null === o) { if (null !== (r = a.return)) { n = r; continue } break } if (a.child === o.child) { for (o = a.child; o;) { if (o === n) return qe(a), e; if (o === r) return qe(a), t; o = o.sibling } throw Error(l(188)) } if (n.return !== r.return) n = a, r = o; else { for (var i = !1, u = a.child; u;) { if (u === n) { i = !0, n = a, r = o; break } if (u === r) { i = !0, r = a, n = o; break } u = u.sibling } if (!i) { for (u = o.child; u;) { if (u === n) { i = !0, n = o, r = a; break } if (u === r) { i = !0, r = o, n = a; break } u = u.sibling } if (!i) throw Error(l(189)) } } if (n.alternate !== r) throw Error(l(190)) } if (3 !== n.tag) throw Error(l(188)); return n.stateNode.current === n ? e : t }(e)) ? He(e) : null } function He(e) { if (5 === e.tag || 6 === e.tag) return e; for (e = e.child; null !== e;) { var t = He(e); if (null !== t) return t; e = e.sibling } return null } var Qe = a.unstable_scheduleCallback, Ke = a.unstable_cancelCallback, Ge = a.unstable_shouldYield, Ye = a.unstable_requestPaint, Xe = a.unstable_now, Ze = a.unstable_getCurrentPriorityLevel, Je = a.unstable_ImmediatePriority, et = a.unstable_UserBlockingPriority, tt = a.unstable_NormalPriority, nt = a.unstable_LowPriority, rt = a.unstable_IdlePriority, at = null, lt = null; var ot = Math.clz32 ? Math.clz32 : function (e) { return e >>>= 0, 0 === e ? 32 : 31 - (it(e) / ut | 0) | 0 }, it = Math.log, ut = Math.LN2; var st = 64, ct = 4194304; function ft(e) { switch (e & -e) { case 1: return 1; case 2: return 2; case 4: return 4; case 8: return 8; case 16: return 16; case 32: return 32; case 64: case 128: case 256: case 512: case 1024: case 2048: case 4096: case 8192: case 16384: case 32768: case 65536: case 131072: case 262144: case 524288: case 1048576: case 2097152: return 4194240 & e; case 4194304: case 8388608: case 16777216: case 33554432: case 67108864: return 130023424 & e; case 134217728: return 134217728; case 268435456: return 268435456; case 536870912: return 536870912; case 1073741824: return 1073741824; default: return e } } function dt(e, t) { var n = e.pendingLanes; if (0 === n) return 0; var r = 0, a = e.suspendedLanes, l = e.pingedLanes, o = 268435455 & n; if (0 !== o) { var i = o & ~a; 0 !== i ? r = ft(i) : 0 !== (l &= o) && (r = ft(l)) } else 0 !== (o = n & ~a) ? r = ft(o) : 0 !== l && (r = ft(l)); if (0 === r) return 0; if (0 !== t && t !== r && 0 === (t & a) && ((a = r & -r) >= (l = t & -t) || 16 === a && 0 !== (4194240 & l))) return t; if (0 !== (4 & r) && (r |= 16 & n), 0 !== (t = e.entangledLanes)) for (e = e.entanglements, t &= r; 0 < t;)a = 1 << (n = 31 - ot(t)), r |= e[n], t &= ~a; return r } function pt(e, t) { switch (e) { case 1: case 2: case 4: return t + 250; case 8: case 16: case 32: case 64: case 128: case 256: case 512: case 1024: case 2048: case 4096: case 8192: case 16384: case 32768: case 65536: case 131072: case 262144: case 524288: case 1048576: case 2097152: return t + 5e3; default: return -1 } } function mt(e) { return 0 !== (e = -1073741825 & e.pendingLanes) ? e : 1073741824 & e ? 1073741824 : 0 } function ht() { var e = st; return 0 === (4194240 & (st <<= 1)) && (st = 64), e } function yt(e) { for (var t = [], n = 0; 31 > n; n++)t.push(e); return t } function vt(e, t, n) { e.pendingLanes |= t, 536870912 !== t && (e.suspendedLanes = 0, e.pingedLanes = 0), (e = e.eventTimes)[t = 31 - ot(t)] = n } function bt(e, t) { var n = e.entangledLanes |= t; for (e = e.entanglements; n;) { var r = 31 - ot(n), a = 1 << r; a & t | e[r] & t && (e[r] |= t), n &= ~a } } var gt = 0; function _t(e) { return 1 < (e &= -e) ? 4 < e ? 0 !== (268435455 & e) ? 16 : 536870912 : 4 : 1 } var St, wt, kt, Et, xt, Ct = !1, Tt = [], Pt = null, Ot = null, zt = null, jt = new Map, Rt = new Map, At = [], Nt = "mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" "); function Mt(e, t) { switch (e) { case "focusin": case "focusout": Pt = null; break; case "dragenter": case "dragleave": Ot = null; break; case "mouseover": case "mouseout": zt = null; break; case "pointerover": case "pointerout": jt.delete(t.pointerId); break; case "gotpointercapture": case "lostpointercapture": Rt.delete(t.pointerId) } } function Lt(e, t, n, r, a, l) { return null === e || e.nativeEvent !== l ? (e = { blockedOn: t, domEventName: n, eventSystemFlags: r, nativeEvent: l, targetContainers: [a] }, null !== t && (null !== (t = ga(t)) && wt(t)), e) : (e.eventSystemFlags |= r, t = e.targetContainers, null !== a && -1 === t.indexOf(a) && t.push(a), e) } function It(e) { var t = ba(e.target); if (null !== t) { var n = Be(t); if (null !== n) if (13 === (t = n.tag)) { if (null !== (t = $e(n))) return e.blockedOn = t, void xt(e.priority, (function () { kt(n) })) } else if (3 === t && n.stateNode.current.memoizedState.isDehydrated) return void (e.blockedOn = 3 === n.tag ? n.stateNode.containerInfo : null) } e.blockedOn = null } function Ft(e) { if (null !== e.blockedOn) return !1; for (var t = e.targetContainers; 0 < t.length;) { var n = Gt(e.domEventName, e.eventSystemFlags, t[0], e.nativeEvent); if (null !== n) return null !== (t = ga(n)) && wt(t), e.blockedOn = n, !1; var r = new (n = e.nativeEvent).constructor(n.type, n); _e = r, n.target.dispatchEvent(r), _e = null, t.shift() } return !0 } function Dt(e, t, n) { Ft(e) && n.delete(t) } function Ut() { Ct = !1, null !== Pt && Ft(Pt) && (Pt = null), null !== Ot && Ft(Ot) && (Ot = null), null !== zt && Ft(zt) && (zt = null), jt.forEach(Dt), Rt.forEach(Dt) } function Vt(e, t) { e.blockedOn === t && (e.blockedOn = null, Ct || (Ct = !0, a.unstable_scheduleCallback(a.unstable_NormalPriority, Ut))) } function Bt(e) { function t(t) { return Vt(t, e) } if (0 < Tt.length) { Vt(Tt[0], e); for (var n = 1; n < Tt.length; n++) { var r = Tt[n]; r.blockedOn === e && (r.blockedOn = null) } } for (null !== Pt && Vt(Pt, e), null !== Ot && Vt(Ot, e), null !== zt && Vt(zt, e), jt.forEach(t), Rt.forEach(t), n = 0; n < At.length; n++)(r = At[n]).blockedOn === e && (r.blockedOn = null); for (; 0 < At.length && null === (n = At[0]).blockedOn;)It(n), null === n.blockedOn && At.shift() } var $t = _.ReactCurrentBatchConfig, qt = !0; function Wt(e, t, n, r) { var a = gt, l = $t.transition; $t.transition = null; try { gt = 1, Qt(e, t, n, r) } finally { gt = a, $t.transition = l } } function Ht(e, t, n, r) { var a = gt, l = $t.transition; $t.transition = null; try { gt = 4, Qt(e, t, n, r) } finally { gt = a, $t.transition = l } } function Qt(e, t, n, r) { if (qt) { var a = Gt(e, t, n, r); if (null === a) qr(e, t, r, Kt, n), Mt(e, r); else if (function (e, t, n, r, a) { switch (t) { case "focusin": return Pt = Lt(Pt, e, t, n, r, a), !0; case "dragenter": return Ot = Lt(Ot, e, t, n, r, a), !0; case "mouseover": return zt = Lt(zt, e, t, n, r, a), !0; case "pointerover": var l = a.pointerId; return jt.set(l, Lt(jt.get(l) || null, e, t, n, r, a)), !0; case "gotpointercapture": return l = a.pointerId, Rt.set(l, Lt(Rt.get(l) || null, e, t, n, r, a)), !0 }return !1 }(a, e, t, n, r)) r.stopPropagation(); else if (Mt(e, r), 4 & t && -1 < Nt.indexOf(e)) { for (; null !== a;) { var l = ga(a); if (null !== l && St(l), null === (l = Gt(e, t, n, r)) && qr(e, t, r, Kt, n), l === a) break; a = l } null !== a && r.stopPropagation() } else qr(e, t, r, null, n) } } var Kt = null; function Gt(e, t, n, r) { if (Kt = null, null !== (e = ba(e = Se(r)))) if (null === (t = Be(e))) e = null; else if (13 === (n = t.tag)) { if (null !== (e = $e(t))) return e; e = null } else if (3 === n) { if (t.stateNode.current.memoizedState.isDehydrated) return 3 === t.tag ? t.stateNode.containerInfo : null; e = null } else t !== e && (e = null); return Kt = e, null } function Yt(e) { switch (e) { case "cancel": case "click": case "close": case "contextmenu": case "copy": case "cut": case "auxclick": case "dblclick": case "dragend": case "dragstart": case "drop": case "focusin": case "focusout": case "input": case "invalid": case "keydown": case "keypress": case "keyup": case "mousedown": case "mouseup": case "paste": case "pause": case "play": case "pointercancel": case "pointerdown": case "pointerup": case "ratechange": case "reset": case "resize": case "seeked": case "submit": case "touchcancel": case "touchend": case "touchstart": case "volumechange": case "change": case "selectionchange": case "textInput": case "compositionstart": case "compositionend": case "compositionupdate": case "beforeblur": case "afterblur": case "beforeinput": case "blur": case "fullscreenchange": case "focus": case "hashchange": case "popstate": case "select": case "selectstart": return 1; case "drag": case "dragenter": case "dragexit": case "dragleave": case "dragover": case "mousemove": case "mouseout": case "mouseover": case "pointermove": case "pointerout": case "pointerover": case "scroll": case "toggle": case "touchmove": case "wheel": case "mouseenter": case "mouseleave": case "pointerenter": case "pointerleave": return 4; case "message": switch (Ze()) { case Je: return 1; case et: return 4; case tt: case nt: return 16; case rt: return 536870912; default: return 16 }default: return 16 } } var Xt = null, Zt = null, Jt = null; function en() { if (Jt) return Jt; var e, t, n = Zt, r = n.length, a = "value" in Xt ? Xt.value : Xt.textContent, l = a.length; for (e = 0; e < r && n[e] === a[e]; e++); var o = r - e; for (t = 1; t <= o && n[r - t] === a[l - t]; t++); return Jt = a.slice(e, 1 < t ? 1 - t : void 0) } function tn(e) { var t = e.keyCode; return "charCode" in e ? 0 === (e = e.charCode) && 13 === t && (e = 13) : e = t, 10 === e && (e = 13), 32 <= e || 13 === e ? e : 0 } function nn() { return !0 } function rn() { return !1 } function an(e) { function t(t, n, r, a, l) { for (var o in this._reactName = t, this._targetInst = r, this.type = n, this.nativeEvent = a, this.target = l, this.currentTarget = null, e) e.hasOwnProperty(o) && (t = e[o], this[o] = t ? t(a) : a[o]); return this.isDefaultPrevented = (null != a.defaultPrevented ? a.defaultPrevented : !1 === a.returnValue) ? nn : rn, this.isPropagationStopped = rn, this } return I(t.prototype, { preventDefault: function () { this.defaultPrevented = !0; var e = this.nativeEvent; e && (e.preventDefault ? e.preventDefault() : "unknown" !== typeof e.returnValue && (e.returnValue = !1), this.isDefaultPrevented = nn) }, stopPropagation: function () { var e = this.nativeEvent; e && (e.stopPropagation ? e.stopPropagation() : "unknown" !== typeof e.cancelBubble && (e.cancelBubble = !0), this.isPropagationStopped = nn) }, persist: function () { }, isPersistent: nn }), t } var ln, on, un, sn = { eventPhase: 0, bubbles: 0, cancelable: 0, timeStamp: function (e) { return e.timeStamp || Date.now() }, defaultPrevented: 0, isTrusted: 0 }, cn = an(sn), fn = I({}, sn, { view: 0, detail: 0 }), dn = an(fn), pn = I({}, fn, { screenX: 0, screenY: 0, clientX: 0, clientY: 0, pageX: 0, pageY: 0, ctrlKey: 0, shiftKey: 0, altKey: 0, metaKey: 0, getModifierState: xn, button: 0, buttons: 0, relatedTarget: function (e) { return void 0 === e.relatedTarget ? e.fromElement === e.srcElement ? e.toElement : e.fromElement : e.relatedTarget }, movementX: function (e) { return "movementX" in e ? e.movementX : (e !== un && (un && "mousemove" === e.type ? (ln = e.screenX - un.screenX, on = e.screenY - un.screenY) : on = ln = 0, un = e), ln) }, movementY: function (e) { return "movementY" in e ? e.movementY : on } }), mn = an(pn), hn = an(I({}, pn, { dataTransfer: 0 })), yn = an(I({}, fn, { relatedTarget: 0 })), vn = an(I({}, sn, { animationName: 0, elapsedTime: 0, pseudoElement: 0 })), bn = I({}, sn, { clipboardData: function (e) { return "clipboardData" in e ? e.clipboardData : window.clipboardData } }), gn = an(bn), _n = an(I({}, sn, { data: 0 })), Sn = { Esc: "Escape", Spacebar: " ", Left: "ArrowLeft", Up: "ArrowUp", Right: "ArrowRight", Down: "ArrowDown", Del: "Delete", Win: "OS", Menu: "ContextMenu", Apps: "ContextMenu", Scroll: "ScrollLock", MozPrintableKey: "Unidentified" }, wn = { 8: "Backspace", 9: "Tab", 12: "Clear", 13: "Enter", 16: "Shift", 17: "Control", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Escape", 32: " ", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "ArrowLeft", 38: "ArrowUp", 39: "ArrowRight", 40: "ArrowDown", 45: "Insert", 46: "Delete", 112: "F1", 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "NumLock", 145: "ScrollLock", 224: "Meta" }, kn = { Alt: "altKey", Control: "ctrlKey", Meta: "metaKey", Shift: "shiftKey" }; function En(e) { var t = this.nativeEvent; return t.getModifierState ? t.getModifierState(e) : !!(e = kn[e]) && !!t[e] } function xn() { return En } var Cn = I({}, fn, { key: function (e) { if (e.key) { var t = Sn[e.key] || e.key; if ("Unidentified" !== t) return t } return "keypress" === e.type ? 13 === (e = tn(e)) ? "Enter" : String.fromCharCode(e) : "keydown" === e.type || "keyup" === e.type ? wn[e.keyCode] || "Unidentified" : "" }, code: 0, location: 0, ctrlKey: 0, shiftKey: 0, altKey: 0, metaKey: 0, repeat: 0, locale: 0, getModifierState: xn, charCode: function (e) { return "keypress" === e.type ? tn(e) : 0 }, keyCode: function (e) { return "keydown" === e.type || "keyup" === e.type ? e.keyCode : 0 }, which: function (e) { return "keypress" === e.type ? tn(e) : "keydown" === e.type || "keyup" === e.type ? e.keyCode : 0 } }), Tn = an(Cn), Pn = an(I({}, pn, { pointerId: 0, width: 0, height: 0, pressure: 0, tangentialPressure: 0, tiltX: 0, tiltY: 0, twist: 0, pointerType: 0, isPrimary: 0 })), On = an(I({}, fn, { touches: 0, targetTouches: 0, changedTouches: 0, altKey: 0, metaKey: 0, ctrlKey: 0, shiftKey: 0, getModifierState: xn })), zn = an(I({}, sn, { propertyName: 0, elapsedTime: 0, pseudoElement: 0 })), jn = I({}, pn, { deltaX: function (e) { return "deltaX" in e ? e.deltaX : "wheelDeltaX" in e ? -e.wheelDeltaX : 0 }, deltaY: function (e) { return "deltaY" in e ? e.deltaY : "wheelDeltaY" in e ? -e.wheelDeltaY : "wheelDelta" in e ? -e.wheelDelta : 0 }, deltaZ: 0, deltaMode: 0 }), Rn = an(jn), An = [9, 13, 27, 32], Nn = c && "CompositionEvent" in window, Mn = null; c && "documentMode" in document && (Mn = document.documentMode); var Ln = c && "TextEvent" in window && !Mn, In = c && (!Nn || Mn && 8 < Mn && 11 >= Mn), Fn = String.fromCharCode(32), Dn = !1; function Un(e, t) { switch (e) { case "keyup": return -1 !== An.indexOf(t.keyCode); case "keydown": return 229 !== t.keyCode; case "keypress": case "mousedown": case "focusout": return !0; default: return !1 } } function Vn(e) { return "object" === typeof (e = e.detail) && "data" in e ? e.data : null } var Bn = !1; var $n = { color: !0, date: !0, datetime: !0, "datetime-local": !0, email: !0, month: !0, number: !0, password: !0, range: !0, search: !0, tel: !0, text: !0, time: !0, url: !0, week: !0 }; function qn(e) { var t = e && e.nodeName && e.nodeName.toLowerCase(); return "input" === t ? !!$n[e.type] : "textarea" === t } function Wn(e, t, n, r) { Ce(r), 0 < (t = Hr(t, "onChange")).length && (n = new cn("onChange", "change", null, n, r), e.push({ event: n, listeners: t })) } var Hn = null, Qn = null; function Kn(e) { Fr(e, 0) } function Gn(e) { if (Q(_a(e))) return e } function Yn(e, t) { if ("change" === e) return t } var Xn = !1; if (c) { var Zn; if (c) { var Jn = "oninput" in document; if (!Jn) { var er = document.createElement("div"); er.setAttribute("oninput", "return;"), Jn = "function" === typeof er.oninput } Zn = Jn } else Zn = !1; Xn = Zn && (!document.documentMode || 9 < document.documentMode) } function tr() { Hn && (Hn.detachEvent("onpropertychange", nr), Qn = Hn = null) } function nr(e) { if ("value" === e.propertyName && Gn(Qn)) { var t = []; Wn(t, Qn, e, Se(e)), je(Kn, t) } } function rr(e, t, n) { "focusin" === e ? (tr(), Qn = n, (Hn = t).attachEvent("onpropertychange", nr)) : "focusout" === e && tr() } function ar(e) { if ("selectionchange" === e || "keyup" === e || "keydown" === e) return Gn(Qn) } function lr(e, t) { if ("click" === e) return Gn(t) } function or(e, t) { if ("input" === e || "change" === e) return Gn(t) } var ir = "function" === typeof Object.is ? Object.is : function (e, t) { return e === t && (0 !== e || 1 / e === 1 / t) || e !== e && t !== t }; function ur(e, t) { if (ir(e, t)) return !0; if ("object" !== typeof e || null === e || "object" !== typeof t || null === t) return !1; var n = Object.keys(e), r = Object.keys(t); if (n.length !== r.length) return !1; for (r = 0; r < n.length; r++) { var a = n[r]; if (!f.call(t, a) || !ir(e[a], t[a])) return !1 } return !0 } function sr(e) { for (; e && e.firstChild;)e = e.firstChild; return e } function cr(e, t) { var n, r = sr(e); for (e = 0; r;) { if (3 === r.nodeType) { if (n = e + r.textContent.length, e <= t && n >= t) return { node: r, offset: t - e }; e = n } e: { for (; r;) { if (r.nextSibling) { r = r.nextSibling; break e } r = r.parentNode } r = void 0 } r = sr(r) } } function fr(e, t) { return !(!e || !t) && (e === t || (!e || 3 !== e.nodeType) && (t && 3 === t.nodeType ? fr(e, t.parentNode) : "contains" in e ? e.contains(t) : !!e.compareDocumentPosition && !!(16 & e.compareDocumentPosition(t)))) } function dr() { for (var e = window, t = K(); t instanceof e.HTMLIFrameElement;) { try { var n = "string" === typeof t.contentWindow.location.href } catch (r) { n = !1 } if (!n) break; t = K((e = t.contentWindow).document) } return t } function pr(e) { var t = e && e.nodeName && e.nodeName.toLowerCase(); return t && ("input" === t && ("text" === e.type || "search" === e.type || "tel" === e.type || "url" === e.type || "password" === e.type) || "textarea" === t || "true" === e.contentEditable) } function mr(e) { var t = dr(), n = e.focusedElem, r = e.selectionRange; if (t !== n && n && n.ownerDocument && fr(n.ownerDocument.documentElement, n)) { if (null !== r && pr(n)) if (t = r.start, void 0 === (e = r.end) && (e = t), "selectionStart" in n) n.selectionStart = t, n.selectionEnd = Math.min(e, n.value.length); else if ((e = (t = n.ownerDocument || document) && t.defaultView || window).getSelection) { e = e.getSelection(); var a = n.textContent.length, l = Math.min(r.start, a); r = void 0 === r.end ? l : Math.min(r.end, a), !e.extend && l > r && (a = r, r = l, l = a), a = cr(n, l); var o = cr(n, r); a && o && (1 !== e.rangeCount || e.anchorNode !== a.node || e.anchorOffset !== a.offset || e.focusNode !== o.node || e.focusOffset !== o.offset) && ((t = t.createRange()).setStart(a.node, a.offset), e.removeAllRanges(), l > r ? (e.addRange(t), e.extend(o.node, o.offset)) : (t.setEnd(o.node, o.offset), e.addRange(t))) } for (t = [], e = n; e = e.parentNode;)1 === e.nodeType && t.push({ element: e, left: e.scrollLeft, top: e.scrollTop }); for ("function" === typeof n.focus && n.focus(), n = 0; n < t.length; n++)(e = t[n]).element.scrollLeft = e.left, e.element.scrollTop = e.top } } var hr = c && "documentMode" in document && 11 >= document.documentMode, yr = null, vr = null, br = null, gr = !1; function _r(e, t, n) { var r = n.window === n ? n.document : 9 === n.nodeType ? n : n.ownerDocument; gr || null == yr || yr !== K(r) || ("selectionStart" in (r = yr) && pr(r) ? r = { start: r.selectionStart, end: r.selectionEnd } : r = { anchorNode: (r = (r.ownerDocument && r.ownerDocument.defaultView || window).getSelection()).anchorNode, anchorOffset: r.anchorOffset, focusNode: r.focusNode, focusOffset: r.focusOffset }, br && ur(br, r) || (br = r, 0 < (r = Hr(vr, "onSelect")).length && (t = new cn("onSelect", "select", null, t, n), e.push({ event: t, listeners: r }), t.target = yr))) } function Sr(e, t) { var n = {}; return n[e.toLowerCase()] = t.toLowerCase(), n["Webkit" + e] = "webkit" + t, n["Moz" + e] = "moz" + t, n } var wr = { animationend: Sr("Animation", "AnimationEnd"), animationiteration: Sr("Animation", "AnimationIteration"), animationstart: Sr("Animation", "AnimationStart"), transitionend: Sr("Transition", "TransitionEnd") }, kr = {}, Er = {}; function xr(e) { if (kr[e]) return kr[e]; if (!wr[e]) return e; var t, n = wr[e]; for (t in n) if (n.hasOwnProperty(t) && t in Er) return kr[e] = n[t]; return e } c && (Er = document.createElement("div").style, "AnimationEvent" in window || (delete wr.animationend.animation, delete wr.animationiteration.animation, delete wr.animationstart.animation), "TransitionEvent" in window || delete wr.transitionend.transition); var Cr = xr("animationend"), Tr = xr("animationiteration"), Pr = xr("animationstart"), Or = xr("transitionend"), zr = new Map, jr = "abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" "); function Rr(e, t) { zr.set(e, t), u(t, [e]) } for (var Ar = 0; Ar < jr.length; Ar++) { var Nr = jr[Ar]; Rr(Nr.toLowerCase(), "on" + (Nr[0].toUpperCase() + Nr.slice(1))) } Rr(Cr, "onAnimationEnd"), Rr(Tr, "onAnimationIteration"), Rr(Pr, "onAnimationStart"), Rr("dblclick", "onDoubleClick"), Rr("focusin", "onFocus"), Rr("focusout", "onBlur"), Rr(Or, "onTransitionEnd"), s("onMouseEnter", ["mouseout", "mouseover"]), s("onMouseLeave", ["mouseout", "mouseover"]), s("onPointerEnter", ["pointerout", "pointerover"]), s("onPointerLeave", ["pointerout", "pointerover"]), u("onChange", "change click focusin focusout input keydown keyup selectionchange".split(" ")), u("onSelect", "focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")), u("onBeforeInput", ["compositionend", "keypress", "textInput", "paste"]), u("onCompositionEnd", "compositionend focusout keydown keypress keyup mousedown".split(" ")), u("onCompositionStart", "compositionstart focusout keydown keypress keyup mousedown".split(" ")), u("onCompositionUpdate", "compositionupdate focusout keydown keypress keyup mousedown".split(" ")); var Mr = "abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "), Lr = new Set("cancel close invalid load scroll toggle".split(" ").concat(Mr)); function Ir(e, t, n) { var r = e.type || "unknown-event"; e.currentTarget = n, function (e, t, n, r, a, o, i, u, s) { if (Ve.apply(this, arguments), Le) { if (!Le) throw Error(l(198)); var c = Ie; Le = !1, Ie = null, Fe || (Fe = !0, De = c) } }(r, t, void 0, e), e.currentTarget = null } function Fr(e, t) { t = 0 !== (4 & t); for (var n = 0; n < e.length; n++) { var r = e[n], a = r.event; r = r.listeners; e: { var l = void 0; if (t) for (var o = r.length - 1; 0 <= o; o--) { var i = r[o], u = i.instance, s = i.currentTarget; if (i = i.listener, u !== l && a.isPropagationStopped()) break e; Ir(a, i, s), l = u } else for (o = 0; o < r.length; o++) { if (u = (i = r[o]).instance, s = i.currentTarget, i = i.listener, u !== l && a.isPropagationStopped()) break e; Ir(a, i, s), l = u } } } if (Fe) throw e = De, Fe = !1, De = null, e } function Dr(e, t) { var n = t[ha]; void 0 === n && (n = t[ha] = new Set); var r = e + "__bubble"; n.has(r) || ($r(t, e, 2, !1), n.add(r)) } function Ur(e, t, n) { var r = 0; t && (r |= 4), $r(n, e, r, t) } var Vr = "_reactListening" + Math.random().toString(36).slice(2); function Br(e) { if (!e[Vr]) { e[Vr] = !0, o.forEach((function (t) { "selectionchange" !== t && (Lr.has(t) || Ur(t, !1, e), Ur(t, !0, e)) })); var t = 9 === e.nodeType ? e : e.ownerDocument; null === t || t[Vr] || (t[Vr] = !0, Ur("selectionchange", !1, t)) } } function $r(e, t, n, r) { switch (Yt(t)) { case 1: var a = Wt; break; case 4: a = Ht; break; default: a = Qt }n = a.bind(null, t, n, e), a = void 0, !Ae || "touchstart" !== t && "touchmove" !== t && "wheel" !== t || (a = !0), r ? void 0 !== a ? e.addEventListener(t, n, { capture: !0, passive: a }) : e.addEventListener(t, n, !0) : void 0 !== a ? e.addEventListener(t, n, { passive: a }) : e.addEventListener(t, n, !1) } function qr(e, t, n, r, a) { var l = r; if (0 === (1 & t) && 0 === (2 & t) && null !== r) e: for (; ;) { if (null === r) return; var o = r.tag; if (3 === o || 4 === o) { var i = r.stateNode.containerInfo; if (i === a || 8 === i.nodeType && i.parentNode === a) break; if (4 === o) for (o = r.return; null !== o;) { var u = o.tag; if ((3 === u || 4 === u) && ((u = o.stateNode.containerInfo) === a || 8 === u.nodeType && u.parentNode === a)) return; o = o.return } for (; null !== i;) { if (null === (o = ba(i))) return; if (5 === (u = o.tag) || 6 === u) { r = l = o; continue e } i = i.parentNode } } r = r.return } je((function () { var r = l, a = Se(n), o = []; e: { var i = zr.get(e); if (void 0 !== i) { var u = cn, s = e; switch (e) { case "keypress": if (0 === tn(n)) break e; case "keydown": case "keyup": u = Tn; break; case "focusin": s = "focus", u = yn; break; case "focusout": s = "blur", u = yn; break; case "beforeblur": case "afterblur": u = yn; break; case "click": if (2 === n.button) break e; case "auxclick": case "dblclick": case "mousedown": case "mousemove": case "mouseup": case "mouseout": case "mouseover": case "contextmenu": u = mn; break; case "drag": case "dragend": case "dragenter": case "dragexit": case "dragleave": case "dragover": case "dragstart": case "drop": u = hn; break; case "touchcancel": case "touchend": case "touchmove": case "touchstart": u = On; break; case Cr: case Tr: case Pr: u = vn; break; case Or: u = zn; break; case "scroll": u = dn; break; case "wheel": u = Rn; break; case "copy": case "cut": case "paste": u = gn; break; case "gotpointercapture": case "lostpointercapture": case "pointercancel": case "pointerdown": case "pointermove": case "pointerout": case "pointerover": case "pointerup": u = Pn }var c = 0 !== (4 & t), f = !c && "scroll" === e, d = c ? null !== i ? i + "Capture" : null : i; c = []; for (var p, m = r; null !== m;) { var h = (p = m).stateNode; if (5 === p.tag && null !== h && (p = h, null !== d && (null != (h = Re(m, d)) && c.push(Wr(m, h, p)))), f) break; m = m.return } 0 < c.length && (i = new u(i, s, null, n, a), o.push({ event: i, listeners: c })) } } if (0 === (7 & t)) { if (u = "mouseout" === e || "pointerout" === e, (!(i = "mouseover" === e || "pointerover" === e) || n === _e || !(s = n.relatedTarget || n.fromElement) || !ba(s) && !s[ma]) && (u || i) && (i = a.window === a ? a : (i = a.ownerDocument) ? i.defaultView || i.parentWindow : window, u ? (u = r, null !== (s = (s = n.relatedTarget || n.toElement) ? ba(s) : null) && (s !== (f = Be(s)) || 5 !== s.tag && 6 !== s.tag) && (s = null)) : (u = null, s = r), u !== s)) { if (c = mn, h = "onMouseLeave", d = "onMouseEnter", m = "mouse", "pointerout" !== e && "pointerover" !== e || (c = Pn, h = "onPointerLeave", d = "onPointerEnter", m = "pointer"), f = null == u ? i : _a(u), p = null == s ? i : _a(s), (i = new c(h, m + "leave", u, n, a)).target = f, i.relatedTarget = p, h = null, ba(a) === r && ((c = new c(d, m + "enter", s, n, a)).target = p, c.relatedTarget = f, h = c), f = h, u && s) e: { for (d = s, m = 0, p = c = u; p; p = Qr(p))m++; for (p = 0, h = d; h; h = Qr(h))p++; for (; 0 < m - p;)c = Qr(c), m--; for (; 0 < p - m;)d = Qr(d), p--; for (; m--;) { if (c === d || null !== d && c === d.alternate) break e; c = Qr(c), d = Qr(d) } c = null } else c = null; null !== u && Kr(o, i, u, c, !1), null !== s && null !== f && Kr(o, f, s, c, !0) } if ("select" === (u = (i = r ? _a(r) : window).nodeName && i.nodeName.toLowerCase()) || "input" === u && "file" === i.type) var y = Yn; else if (qn(i)) if (Xn) y = or; else { y = ar; var v = rr } else (u = i.nodeName) && "input" === u.toLowerCase() && ("checkbox" === i.type || "radio" === i.type) && (y = lr); switch (y && (y = y(e, r)) ? Wn(o, y, n, a) : (v && v(e, i, r), "focusout" === e && (v = i._wrapperState) && v.controlled && "number" === i.type && ee(i, "number", i.value)), v = r ? _a(r) : window, e) { case "focusin": (qn(v) || "true" === v.contentEditable) && (yr = v, vr = r, br = null); break; case "focusout": br = vr = yr = null; break; case "mousedown": gr = !0; break; case "contextmenu": case "mouseup": case "dragend": gr = !1, _r(o, n, a); break; case "selectionchange": if (hr) break; case "keydown": case "keyup": _r(o, n, a) }var b; if (Nn) e: { switch (e) { case "compositionstart": var g = "onCompositionStart"; break e; case "compositionend": g = "onCompositionEnd"; break e; case "compositionupdate": g = "onCompositionUpdate"; break e }g = void 0 } else Bn ? Un(e, n) && (g = "onCompositionEnd") : "keydown" === e && 229 === n.keyCode && (g = "onCompositionStart"); g && (In && "ko" !== n.locale && (Bn || "onCompositionStart" !== g ? "onCompositionEnd" === g && Bn && (b = en()) : (Zt = "value" in (Xt = a) ? Xt.value : Xt.textContent, Bn = !0)), 0 < (v = Hr(r, g)).length && (g = new _n(g, e, null, n, a), o.push({ event: g, listeners: v }), b ? g.data = b : null !== (b = Vn(n)) && (g.data = b))), (b = Ln ? function (e, t) { switch (e) { case "compositionend": return Vn(t); case "keypress": return 32 !== t.which ? null : (Dn = !0, Fn); case "textInput": return (e = t.data) === Fn && Dn ? null : e; default: return null } }(e, n) : function (e, t) { if (Bn) return "compositionend" === e || !Nn && Un(e, t) ? (e = en(), Jt = Zt = Xt = null, Bn = !1, e) : null; switch (e) { case "paste": default: return null; case "keypress": if (!(t.ctrlKey || t.altKey || t.metaKey) || t.ctrlKey && t.altKey) { if (t.char && 1 < t.char.length) return t.char; if (t.which) return String.fromCharCode(t.which) } return null; case "compositionend": return In && "ko" !== t.locale ? null : t.data } }(e, n)) && (0 < (r = Hr(r, "onBeforeInput")).length && (a = new _n("onBeforeInput", "beforeinput", null, n, a), o.push({ event: a, listeners: r }), a.data = b)) } Fr(o, t) })) } function Wr(e, t, n) { return { instance: e, listener: t, currentTarget: n } } function Hr(e, t) { for (var n = t + "Capture", r = []; null !== e;) { var a = e, l = a.stateNode; 5 === a.tag && null !== l && (a = l, null != (l = Re(e, n)) && r.unshift(Wr(e, l, a)), null != (l = Re(e, t)) && r.push(Wr(e, l, a))), e = e.return } return r } function Qr(e) { if (null === e) return null; do { e = e.return } while (e && 5 !== e.tag); return e || null } function Kr(e, t, n, r, a) { for (var l = t._reactName, o = []; null !== n && n !== r;) { var i = n, u = i.alternate, s = i.stateNode; if (null !== u && u === r) break; 5 === i.tag && null !== s && (i = s, a ? null != (u = Re(n, l)) && o.unshift(Wr(n, u, i)) : a || null != (u = Re(n, l)) && o.push(Wr(n, u, i))), n = n.return } 0 !== o.length && e.push({ event: t, listeners: o }) } var Gr = /\r\n?/g, Yr = /\u0000|\uFFFD/g; function Xr(e) { return ("string" === typeof e ? e : "" + e).replace(Gr, "\n").replace(Yr, "") } function Zr(e, t, n) { if (t = Xr(t), Xr(e) !== t && n) throw Error(l(425)) } function Jr() { } var ea = null, ta = null; function na(e, t) { return "textarea" === e || "noscript" === e || "string" === typeof t.children || "number" === typeof t.children || "object" === typeof t.dangerouslySetInnerHTML && null !== t.dangerouslySetInnerHTML && null != t.dangerouslySetInnerHTML.__html } var ra = "function" === typeof setTimeout ? setTimeout : void 0, aa = "function" === typeof clearTimeout ? clearTimeout : void 0, la = "function" === typeof Promise ? Promise : void 0, oa = "function" === typeof queueMicrotask ? queueMicrotask : "undefined" !== typeof la ? function (e) { return la.resolve(null).then(e).catch(ia) } : ra; function ia(e) { setTimeout((function () { throw e })) } function ua(e, t) { var n = t, r = 0; do { var a = n.nextSibling; if (e.removeChild(n), a && 8 === a.nodeType) if ("/$" === (n = a.data)) { if (0 === r) return e.removeChild(a), void Bt(t); r-- } else "$" !== n && "$?" !== n && "$!" !== n || r++; n = a } while (n); Bt(t) } function sa(e) { for (; null != e; e = e.nextSibling) { var t = e.nodeType; if (1 === t || 3 === t) break; if (8 === t) { if ("$" === (t = e.data) || "$!" === t || "$?" === t) break; if ("/$" === t) return null } } return e } function ca(e) { e = e.previousSibling; for (var t = 0; e;) { if (8 === e.nodeType) { var n = e.data; if ("$" === n || "$!" === n || "$?" === n) { if (0 === t) return e; t-- } else "/$" === n && t++ } e = e.previousSibling } return null } var fa = Math.random().toString(36).slice(2), da = "__reactFiber$" + fa, pa = "__reactProps$" + fa, ma = "__reactContainer$" + fa, ha = "__reactEvents$" + fa, ya = "__reactListeners$" + fa, va = "__reactHandles$" + fa; function ba(e) { var t = e[da]; if (t) return t; for (var n = e.parentNode; n;) { if (t = n[ma] || n[da]) { if (n = t.alternate, null !== t.child || null !== n && null !== n.child) for (e = ca(e); null !== e;) { if (n = e[da]) return n; e = ca(e) } return t } n = (e = n).parentNode } return null } function ga(e) { return !(e = e[da] || e[ma]) || 5 !== e.tag && 6 !== e.tag && 13 !== e.tag && 3 !== e.tag ? null : e } function _a(e) { if (5 === e.tag || 6 === e.tag) return e.stateNode; throw Error(l(33)) } function Sa(e) { return e[pa] || null } var wa = [], ka = -1; function Ea(e) { return { current: e } } function xa(e) { 0 > ka || (e.current = wa[ka], wa[ka] = null, ka--) } function Ca(e, t) { ka++, wa[ka] = e.current, e.current = t } var Ta = {}, Pa = Ea(Ta), Oa = Ea(!1), za = Ta; function ja(e, t) { var n = e.type.contextTypes; if (!n) return Ta; var r = e.stateNode; if (r && r.__reactInternalMemoizedUnmaskedChildContext === t) return r.__reactInternalMemoizedMaskedChildContext; var a, l = {}; for (a in n) l[a] = t[a]; return r && ((e = e.stateNode).__reactInternalMemoizedUnmaskedChildContext = t, e.__reactInternalMemoizedMaskedChildContext = l), l } function Ra(e) { return null !== (e = e.childContextTypes) && void 0 !== e } function Aa() { xa(Oa), xa(Pa) } function Na(e, t, n) { if (Pa.current !== Ta) throw Error(l(168)); Ca(Pa, t), Ca(Oa, n) } function Ma(e, t, n) { var r = e.stateNode; if (t = t.childContextTypes, "function" !== typeof r.getChildContext) return n; for (var a in r = r.getChildContext()) if (!(a in t)) throw Error(l(108, $(e) || "Unknown", a)); return I({}, n, r) } function La(e) { return e = (e = e.stateNode) && e.__reactInternalMemoizedMergedChildContext || Ta, za = Pa.current, Ca(Pa, e), Ca(Oa, Oa.current), !0 } function Ia(e, t, n) { var r = e.stateNode; if (!r) throw Error(l(169)); n ? (e = Ma(e, t, za), r.__reactInternalMemoizedMergedChildContext = e, xa(Oa), xa(Pa), Ca(Pa, e)) : xa(Oa), Ca(Oa, n) } var Fa = null, Da = !1, Ua = !1; function Va(e) { null === Fa ? Fa = [e] : Fa.push(e) } function Ba() { if (!Ua && null !== Fa) { Ua = !0; var e = 0, t = gt; try { var n = Fa; for (gt = 1; e < n.length; e++) { var r = n[e]; do { r = r(!0) } while (null !== r) } Fa = null, Da = !1 } catch (a) { throw null !== Fa && (Fa = Fa.slice(e + 1)), Qe(Je, Ba), a } finally { gt = t, Ua = !1 } } return null } var $a = [], qa = 0, Wa = null, Ha = 0, Qa = [], Ka = 0, Ga = null, Ya = 1, Xa = ""; function Za(e, t) { $a[qa++] = Ha, $a[qa++] = Wa, Wa = e, Ha = t } function Ja(e, t, n) { Qa[Ka++] = Ya, Qa[Ka++] = Xa, Qa[Ka++] = Ga, Ga = e; var r = Ya; e = Xa; var a = 32 - ot(r) - 1; r &= ~(1 << a), n += 1; var l = 32 - ot(t) + a; if (30 < l) { var o = a - a % 5; l = (r & (1 << o) - 1).toString(32), r >>= o, a -= o, Ya = 1 << 32 - ot(t) + a | n << a | r, Xa = l + e } else Ya = 1 << l | n << a | r, Xa = e } function el(e) { null !== e.return && (Za(e, 1), Ja(e, 1, 0)) } function tl(e) { for (; e === Wa;)Wa = $a[--qa], $a[qa] = null, Ha = $a[--qa], $a[qa] = null; for (; e === Ga;)Ga = Qa[--Ka], Qa[Ka] = null, Xa = Qa[--Ka], Qa[Ka] = null, Ya = Qa[--Ka], Qa[Ka] = null } var nl = null, rl = null, al = !1, ll = null; function ol(e, t) { var n = Rs(5, null, null, 0); n.elementType = "DELETED", n.stateNode = t, n.return = e, null === (t = e.deletions) ? (e.deletions = [n], e.flags |= 16) : t.push(n) } function il(e, t) { switch (e.tag) { case 5: var n = e.type; return null !== (t = 1 !== t.nodeType || n.toLowerCase() !== t.nodeName.toLowerCase() ? null : t) && (e.stateNode = t, nl = e, rl = sa(t.firstChild), !0); case 6: return null !== (t = "" === e.pendingProps || 3 !== t.nodeType ? null : t) && (e.stateNode = t, nl = e, rl = null, !0); case 13: return null !== (t = 8 !== t.nodeType ? null : t) && (n = null !== Ga ? { id: Ya, overflow: Xa } : null, e.memoizedState = { dehydrated: t, treeContext: n, retryLane: 1073741824 }, (n = Rs(18, null, null, 0)).stateNode = t, n.return = e, e.child = n, nl = e, rl = null, !0); default: return !1 } } function ul(e) { return 0 !== (1 & e.mode) && 0 === (128 & e.flags) } function sl(e) { if (al) { var t = rl; if (t) { var n = t; if (!il(e, t)) { if (ul(e)) throw Error(l(418)); t = sa(n.nextSibling); var r = nl; t && il(e, t) ? ol(r, n) : (e.flags = -4097 & e.flags | 2, al = !1, nl = e) } } else { if (ul(e)) throw Error(l(418)); e.flags = -4097 & e.flags | 2, al = !1, nl = e } } } function cl(e) { for (e = e.return; null !== e && 5 !== e.tag && 3 !== e.tag && 13 !== e.tag;)e = e.return; nl = e } function fl(e) { if (e !== nl) return !1; if (!al) return cl(e), al = !0, !1; var t; if ((t = 3 !== e.tag) && !(t = 5 !== e.tag) && (t = "head" !== (t = e.type) && "body" !== t && !na(e.type, e.memoizedProps)), t && (t = rl)) { if (ul(e)) throw dl(), Error(l(418)); for (; t;)ol(e, t), t = sa(t.nextSibling) } if (cl(e), 13 === e.tag) { if (!(e = null !== (e = e.memoizedState) ? e.dehydrated : null)) throw Error(l(317)); e: { for (e = e.nextSibling, t = 0; e;) { if (8 === e.nodeType) { var n = e.data; if ("/$" === n) { if (0 === t) { rl = sa(e.nextSibling); break e } t-- } else "$" !== n && "$!" !== n && "$?" !== n || t++ } e = e.nextSibling } rl = null } } else rl = nl ? sa(e.stateNode.nextSibling) : null; return !0 } function dl() { for (var e = rl; e;)e = sa(e.nextSibling) } function pl() { rl = nl = null, al = !1 } function ml(e) { null === ll ? ll = [e] : ll.push(e) } var hl = _.ReactCurrentBatchConfig; function yl(e, t) { if (e && e.defaultProps) { for (var n in t = I({}, t), e = e.defaultProps) void 0 === t[n] && (t[n] = e[n]); return t } return t } var vl = Ea(null), bl = null, gl = null, _l = null; function Sl() { _l = gl = bl = null } function wl(e) { var t = vl.current; xa(vl), e._currentValue = t } function kl(e, t, n) { for (; null !== e;) { var r = e.alternate; if ((e.childLanes & t) !== t ? (e.childLanes |= t, null !== r && (r.childLanes |= t)) : null !== r && (r.childLanes & t) !== t && (r.childLanes |= t), e === n) break; e = e.return } } function El(e, t) { bl = e, _l = gl = null, null !== (e = e.dependencies) && null !== e.firstContext && (0 !== (e.lanes & t) && (_i = !0), e.firstContext = null) } function xl(e) { var t = e._currentValue; if (_l !== e) if (e = { context: e, memoizedValue: t, next: null }, null === gl) { if (null === bl) throw Error(l(308)); gl = e, bl.dependencies = { lanes: 0, firstContext: e } } else gl = gl.next = e; return t } var Cl = null; function Tl(e) { null === Cl ? Cl = [e] : Cl.push(e) } function Pl(e, t, n, r) { var a = t.interleaved; return null === a ? (n.next = n, Tl(t)) : (n.next = a.next, a.next = n), t.interleaved = n, Ol(e, r) } function Ol(e, t) { e.lanes |= t; var n = e.alternate; for (null !== n && (n.lanes |= t), n = e, e = e.return; null !== e;)e.childLanes |= t, null !== (n = e.alternate) && (n.childLanes |= t), n = e, e = e.return; return 3 === n.tag ? n.stateNode : null } var zl = !1; function jl(e) { e.updateQueue = { baseState: e.memoizedState, firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: null, interleaved: null, lanes: 0 }, effects: null } } function Rl(e, t) { e = e.updateQueue, t.updateQueue === e && (t.updateQueue = { baseState: e.baseState, firstBaseUpdate: e.firstBaseUpdate, lastBaseUpdate: e.lastBaseUpdate, shared: e.shared, effects: e.effects }) } function Al(e, t) { return { eventTime: e, lane: t, tag: 0, payload: null, callback: null, next: null } } function Nl(e, t, n) { var r = e.updateQueue; if (null === r) return null; if (r = r.shared, 0 !== (2 & Ou)) { var a = r.pending; return null === a ? t.next = t : (t.next = a.next, a.next = t), r.pending = t, Ol(e, n) } return null === (a = r.interleaved) ? (t.next = t, Tl(r)) : (t.next = a.next, a.next = t), r.interleaved = t, Ol(e, n) } function Ml(e, t, n) { if (null !== (t = t.updateQueue) && (t = t.shared, 0 !== (4194240 & n))) { var r = t.lanes; n |= r &= e.pendingLanes, t.lanes = n, bt(e, n) } } function Ll(e, t) { var n = e.updateQueue, r = e.alternate; if (null !== r && n === (r = r.updateQueue)) { var a = null, l = null; if (null !== (n = n.firstBaseUpdate)) { do { var o = { eventTime: n.eventTime, lane: n.lane, tag: n.tag, payload: n.payload, callback: n.callback, next: null }; null === l ? a = l = o : l = l.next = o, n = n.next } while (null !== n); null === l ? a = l = t : l = l.next = t } else a = l = t; return n = { baseState: r.baseState, firstBaseUpdate: a, lastBaseUpdate: l, shared: r.shared, effects: r.effects }, void (e.updateQueue = n) } null === (e = n.lastBaseUpdate) ? n.firstBaseUpdate = t : e.next = t, n.lastBaseUpdate = t } function Il(e, t, n, r) { var a = e.updateQueue; zl = !1; var l = a.firstBaseUpdate, o = a.lastBaseUpdate, i = a.shared.pending; if (null !== i) { a.shared.pending = null; var u = i, s = u.next; u.next = null, null === o ? l = s : o.next = s, o = u; var c = e.alternate; null !== c && ((i = (c = c.updateQueue).lastBaseUpdate) !== o && (null === i ? c.firstBaseUpdate = s : i.next = s, c.lastBaseUpdate = u)) } if (null !== l) { var f = a.baseState; for (o = 0, c = s = u = null, i = l; ;) { var d = i.lane, p = i.eventTime; if ((r & d) === d) { null !== c && (c = c.next = { eventTime: p, lane: 0, tag: i.tag, payload: i.payload, callback: i.callback, next: null }); e: { var m = e, h = i; switch (d = t, p = n, h.tag) { case 1: if ("function" === typeof (m = h.payload)) { f = m.call(p, f, d); break e } f = m; break e; case 3: m.flags = -65537 & m.flags | 128; case 0: if (null === (d = "function" === typeof (m = h.payload) ? m.call(p, f, d) : m) || void 0 === d) break e; f = I({}, f, d); break e; case 2: zl = !0 } } null !== i.callback && 0 !== i.lane && (e.flags |= 64, null === (d = a.effects) ? a.effects = [i] : d.push(i)) } else p = { eventTime: p, lane: d, tag: i.tag, payload: i.payload, callback: i.callback, next: null }, null === c ? (s = c = p, u = f) : c = c.next = p, o |= d; if (null === (i = i.next)) { if (null === (i = a.shared.pending)) break; i = (d = i).next, d.next = null, a.lastBaseUpdate = d, a.shared.pending = null } } if (null === c && (u = f), a.baseState = u, a.firstBaseUpdate = s, a.lastBaseUpdate = c, null !== (t = a.shared.interleaved)) { a = t; do { o |= a.lane, a = a.next } while (a !== t) } else null === l && (a.shared.lanes = 0); Iu |= o, e.lanes = o, e.memoizedState = f } } function Fl(e, t, n) { if (e = t.effects, t.effects = null, null !== e) for (t = 0; t < e.length; t++) { var r = e[t], a = r.callback; if (null !== a) { if (r.callback = null, r = n, "function" !== typeof a) throw Error(l(191, a)); a.call(r) } } } var Dl = (new r.Component).refs; function Ul(e, t, n, r) { n = null === (n = n(r, t = e.memoizedState)) || void 0 === n ? t : I({}, t, n), e.memoizedState = n, 0 === e.lanes && (e.updateQueue.baseState = n) } var Vl = { isMounted: function (e) { return !!(e = e._reactInternals) && Be(e) === e }, enqueueSetState: function (e, t, n) { e = e._reactInternals; var r = ts(), a = ns(e), l = Al(r, a); l.payload = t, void 0 !== n && null !== n && (l.callback = n), null !== (t = Nl(e, l, a)) && (rs(t, e, a, r), Ml(t, e, a)) }, enqueueReplaceState: function (e, t, n) { e = e._reactInternals; var r = ts(), a = ns(e), l = Al(r, a); l.tag = 1, l.payload = t, void 0 !== n && null !== n && (l.callback = n), null !== (t = Nl(e, l, a)) && (rs(t, e, a, r), Ml(t, e, a)) }, enqueueForceUpdate: function (e, t) { e = e._reactInternals; var n = ts(), r = ns(e), a = Al(n, r); a.tag = 2, void 0 !== t && null !== t && (a.callback = t), null !== (t = Nl(e, a, r)) && (rs(t, e, r, n), Ml(t, e, r)) } }; function Bl(e, t, n, r, a, l, o) { return "function" === typeof (e = e.stateNode).shouldComponentUpdate ? e.shouldComponentUpdate(r, l, o) : !t.prototype || !t.prototype.isPureReactComponent || (!ur(n, r) || !ur(a, l)) } function $l(e, t, n) { var r = !1, a = Ta, l = t.contextType; return "object" === typeof l && null !== l ? l = xl(l) : (a = Ra(t) ? za : Pa.current, l = (r = null !== (r = t.contextTypes) && void 0 !== r) ? ja(e, a) : Ta), t = new t(n, l), e.memoizedState = null !== t.state && void 0 !== t.state ? t.state : null, t.updater = Vl, e.stateNode = t, t._reactInternals = e, r && ((e = e.stateNode).__reactInternalMemoizedUnmaskedChildContext = a, e.__reactInternalMemoizedMaskedChildContext = l), t } function ql(e, t, n, r) { e = t.state, "function" === typeof t.componentWillReceiveProps && t.componentWillReceiveProps(n, r), "function" === typeof t.UNSAFE_componentWillReceiveProps && t.UNSAFE_componentWillReceiveProps(n, r), t.state !== e && Vl.enqueueReplaceState(t, t.state, null) } function Wl(e, t, n, r) { var a = e.stateNode; a.props = n, a.state = e.memoizedState, a.refs = Dl, jl(e); var l = t.contextType; "object" === typeof l && null !== l ? a.context = xl(l) : (l = Ra(t) ? za : Pa.current, a.context = ja(e, l)), a.state = e.memoizedState, "function" === typeof (l = t.getDerivedStateFromProps) && (Ul(e, t, l, n), a.state = e.memoizedState), "function" === typeof t.getDerivedStateFromProps || "function" === typeof a.getSnapshotBeforeUpdate || "function" !== typeof a.UNSAFE_componentWillMount && "function" !== typeof a.componentWillMount || (t = a.state, "function" === typeof a.componentWillMount && a.componentWillMount(), "function" === typeof a.UNSAFE_componentWillMount && a.UNSAFE_componentWillMount(), t !== a.state && Vl.enqueueReplaceState(a, a.state, null), Il(e, n, a, r), a.state = e.memoizedState), "function" === typeof a.componentDidMount && (e.flags |= 4194308) } function Hl(e, t, n) { if (null !== (e = n.ref) && "function" !== typeof e && "object" !== typeof e) { if (n._owner) { if (n = n._owner) { if (1 !== n.tag) throw Error(l(309)); var r = n.stateNode } if (!r) throw Error(l(147, e)); var a = r, o = "" + e; return null !== t && null !== t.ref && "function" === typeof t.ref && t.ref._stringRef === o ? t.ref : (t = function (e) { var t = a.refs; t === Dl && (t = a.refs = {}), null === e ? delete t[o] : t[o] = e }, t._stringRef = o, t) } if ("string" !== typeof e) throw Error(l(284)); if (!n._owner) throw Error(l(290, e)) } return e } function Ql(e, t) { throw e = Object.prototype.toString.call(t), Error(l(31, "[object Object]" === e ? "object with keys {" + Object.keys(t).join(", ") + "}" : e)) } function Kl(e) { return (0, e._init)(e._payload) } function Gl(e) { function t(t, n) { if (e) { var r = t.deletions; null === r ? (t.deletions = [n], t.flags |= 16) : r.push(n) } } function n(n, r) { if (!e) return null; for (; null !== r;)t(n, r), r = r.sibling; return null } function r(e, t) { for (e = new Map; null !== t;)null !== t.key ? e.set(t.key, t) : e.set(t.index, t), t = t.sibling; return e } function a(e, t) { return (e = Ns(e, t)).index = 0, e.sibling = null, e } function o(t, n, r) { return t.index = r, e ? null !== (r = t.alternate) ? (r = r.index) < n ? (t.flags |= 2, n) : r : (t.flags |= 2, n) : (t.flags |= 1048576, n) } function i(t) { return e && null === t.alternate && (t.flags |= 2), t } function u(e, t, n, r) { return null === t || 6 !== t.tag ? ((t = Fs(n, e.mode, r)).return = e, t) : ((t = a(t, n)).return = e, t) } function s(e, t, n, r) { var l = n.type; return l === k ? f(e, t, n.props.children, r, n.key) : null !== t && (t.elementType === l || "object" === typeof l && null !== l && l.$$typeof === R && Kl(l) === t.type) ? ((r = a(t, n.props)).ref = Hl(e, t, n), r.return = e, r) : ((r = Ms(n.type, n.key, n.props, null, e.mode, r)).ref = Hl(e, t, n), r.return = e, r) } function c(e, t, n, r) { return null === t || 4 !== t.tag || t.stateNode.containerInfo !== n.containerInfo || t.stateNode.implementation !== n.implementation ? ((t = Ds(n, e.mode, r)).return = e, t) : ((t = a(t, n.children || [])).return = e, t) } function f(e, t, n, r, l) { return null === t || 7 !== t.tag ? ((t = Ls(n, e.mode, r, l)).return = e, t) : ((t = a(t, n)).return = e, t) } function d(e, t, n) { if ("string" === typeof t && "" !== t || "number" === typeof t) return (t = Fs("" + t, e.mode, n)).return = e, t; if ("object" === typeof t && null !== t) { switch (t.$$typeof) { case S: return (n = Ms(t.type, t.key, t.props, null, e.mode, n)).ref = Hl(e, null, t), n.return = e, n; case w: return (t = Ds(t, e.mode, n)).return = e, t; case R: return d(e, (0, t._init)(t._payload), n) }if (te(t) || M(t)) return (t = Ls(t, e.mode, n, null)).return = e, t; Ql(e, t) } return null } function p(e, t, n, r) { var a = null !== t ? t.key : null; if ("string" === typeof n && "" !== n || "number" === typeof n) return null !== a ? null : u(e, t, "" + n, r); if ("object" === typeof n && null !== n) { switch (n.$$typeof) { case S: return n.key === a ? s(e, t, n, r) : null; case w: return n.key === a ? c(e, t, n, r) : null; case R: return p(e, t, (a = n._init)(n._payload), r) }if (te(n) || M(n)) return null !== a ? null : f(e, t, n, r, null); Ql(e, n) } return null } function m(e, t, n, r, a) { if ("string" === typeof r && "" !== r || "number" === typeof r) return u(t, e = e.get(n) || null, "" + r, a); if ("object" === typeof r && null !== r) { switch (r.$$typeof) { case S: return s(t, e = e.get(null === r.key ? n : r.key) || null, r, a); case w: return c(t, e = e.get(null === r.key ? n : r.key) || null, r, a); case R: return m(e, t, n, (0, r._init)(r._payload), a) }if (te(r) || M(r)) return f(t, e = e.get(n) || null, r, a, null); Ql(t, r) } return null } function h(a, l, i, u) { for (var s = null, c = null, f = l, h = l = 0, y = null; null !== f && h < i.length; h++) { f.index > h ? (y = f, f = null) : y = f.sibling; var v = p(a, f, i[h], u); if (null === v) { null === f && (f = y); break } e && f && null === v.alternate && t(a, f), l = o(v, l, h), null === c ? s = v : c.sibling = v, c = v, f = y } if (h === i.length) return n(a, f), al && Za(a, h), s; if (null === f) { for (; h < i.length; h++)null !== (f = d(a, i[h], u)) && (l = o(f, l, h), null === c ? s = f : c.sibling = f, c = f); return al && Za(a, h), s } for (f = r(a, f); h < i.length; h++)null !== (y = m(f, a, h, i[h], u)) && (e && null !== y.alternate && f.delete(null === y.key ? h : y.key), l = o(y, l, h), null === c ? s = y : c.sibling = y, c = y); return e && f.forEach((function (e) { return t(a, e) })), al && Za(a, h), s } function y(a, i, u, s) { var c = M(u); if ("function" !== typeof c) throw Error(l(150)); if (null == (u = c.call(u))) throw Error(l(151)); for (var f = c = null, h = i, y = i = 0, v = null, b = u.next(); null !== h && !b.done; y++, b = u.next()) { h.index > y ? (v = h, h = null) : v = h.sibling; var g = p(a, h, b.value, s); if (null === g) { null === h && (h = v); break } e && h && null === g.alternate && t(a, h), i = o(g, i, y), null === f ? c = g : f.sibling = g, f = g, h = v } if (b.done) return n(a, h), al && Za(a, y), c; if (null === h) { for (; !b.done; y++, b = u.next())null !== (b = d(a, b.value, s)) && (i = o(b, i, y), null === f ? c = b : f.sibling = b, f = b); return al && Za(a, y), c } for (h = r(a, h); !b.done; y++, b = u.next())null !== (b = m(h, a, y, b.value, s)) && (e && null !== b.alternate && h.delete(null === b.key ? y : b.key), i = o(b, i, y), null === f ? c = b : f.sibling = b, f = b); return e && h.forEach((function (e) { return t(a, e) })), al && Za(a, y), c } return function e(r, l, o, u) { if ("object" === typeof o && null !== o && o.type === k && null === o.key && (o = o.props.children), "object" === typeof o && null !== o) { switch (o.$$typeof) { case S: e: { for (var s = o.key, c = l; null !== c;) { if (c.key === s) { if ((s = o.type) === k) { if (7 === c.tag) { n(r, c.sibling), (l = a(c, o.props.children)).return = r, r = l; break e } } else if (c.elementType === s || "object" === typeof s && null !== s && s.$$typeof === R && Kl(s) === c.type) { n(r, c.sibling), (l = a(c, o.props)).ref = Hl(r, c, o), l.return = r, r = l; break e } n(r, c); break } t(r, c), c = c.sibling } o.type === k ? ((l = Ls(o.props.children, r.mode, u, o.key)).return = r, r = l) : ((u = Ms(o.type, o.key, o.props, null, r.mode, u)).ref = Hl(r, l, o), u.return = r, r = u) } return i(r); case w: e: { for (c = o.key; null !== l;) { if (l.key === c) { if (4 === l.tag && l.stateNode.containerInfo === o.containerInfo && l.stateNode.implementation === o.implementation) { n(r, l.sibling), (l = a(l, o.children || [])).return = r, r = l; break e } n(r, l); break } t(r, l), l = l.sibling } (l = Ds(o, r.mode, u)).return = r, r = l } return i(r); case R: return e(r, l, (c = o._init)(o._payload), u) }if (te(o)) return h(r, l, o, u); if (M(o)) return y(r, l, o, u); Ql(r, o) } return "string" === typeof o && "" !== o || "number" === typeof o ? (o = "" + o, null !== l && 6 === l.tag ? (n(r, l.sibling), (l = a(l, o)).return = r, r = l) : (n(r, l), (l = Fs(o, r.mode, u)).return = r, r = l), i(r)) : n(r, l) } } var Yl = Gl(!0), Xl = Gl(!1), Zl = {}, Jl = Ea(Zl), eo = Ea(Zl), to = Ea(Zl); function no(e) { if (e === Zl) throw Error(l(174)); return e } function ro(e, t) { switch (Ca(to, t), Ca(eo, e), Ca(Jl, Zl), e = t.nodeType) { case 9: case 11: t = (t = t.documentElement) ? t.namespaceURI : ue(null, ""); break; default: t = ue(t = (e = 8 === e ? t.parentNode : t).namespaceURI || null, e = e.tagName) }xa(Jl), Ca(Jl, t) } function ao() { xa(Jl), xa(eo), xa(to) } function lo(e) { no(to.current); var t = no(Jl.current), n = ue(t, e.type); t !== n && (Ca(eo, e), Ca(Jl, n)) } function oo(e) { eo.current === e && (xa(Jl), xa(eo)) } var io = Ea(0); function uo(e) { for (var t = e; null !== t;) { if (13 === t.tag) { var n = t.memoizedState; if (null !== n && (null === (n = n.dehydrated) || "$?" === n.data || "$!" === n.data)) return t } else if (19 === t.tag && void 0 !== t.memoizedProps.revealOrder) { if (0 !== (128 & t.flags)) return t } else if (null !== t.child) { t.child.return = t, t = t.child; continue } if (t === e) break; for (; null === t.sibling;) { if (null === t.return || t.return === e) return null; t = t.return } t.sibling.return = t.return, t = t.sibling } return null } var so = []; function co() { for (var e = 0; e < so.length; e++)so[e]._workInProgressVersionPrimary = null; so.length = 0 } var fo = _.ReactCurrentDispatcher, po = _.ReactCurrentBatchConfig, mo = 0, ho = null, yo = null, vo = null, bo = !1, go = !1, _o = 0, So = 0; function wo() { throw Error(l(321)) } function ko(e, t) { if (null === t) return !1; for (var n = 0; n < t.length && n < e.length; n++)if (!ir(e[n], t[n])) return !1; return !0 } function Eo(e, t, n, r, a, o) { if (mo = o, ho = t, t.memoizedState = null, t.updateQueue = null, t.lanes = 0, fo.current = null === e || null === e.memoizedState ? ii : ui, e = n(r, a), go) { o = 0; do { if (go = !1, _o = 0, 25 <= o) throw Error(l(301)); o += 1, vo = yo = null, t.updateQueue = null, fo.current = si, e = n(r, a) } while (go) } if (fo.current = oi, t = null !== yo && null !== yo.next, mo = 0, vo = yo = ho = null, bo = !1, t) throw Error(l(300)); return e } function xo() { var e = 0 !== _o; return _o = 0, e } function Co() { var e = { memoizedState: null, baseState: null, baseQueue: null, queue: null, next: null }; return null === vo ? ho.memoizedState = vo = e : vo = vo.next = e, vo } function To() { if (null === yo) { var e = ho.alternate; e = null !== e ? e.memoizedState : null } else e = yo.next; var t = null === vo ? ho.memoizedState : vo.next; if (null !== t) vo = t, yo = e; else { if (null === e) throw Error(l(310)); e = { memoizedState: (yo = e).memoizedState, baseState: yo.baseState, baseQueue: yo.baseQueue, queue: yo.queue, next: null }, null === vo ? ho.memoizedState = vo = e : vo = vo.next = e } return vo } function Po(e, t) { return "function" === typeof t ? t(e) : t } function Oo(e) { var t = To(), n = t.queue; if (null === n) throw Error(l(311)); n.lastRenderedReducer = e; var r = yo, a = r.baseQueue, o = n.pending; if (null !== o) { if (null !== a) { var i = a.next; a.next = o.next, o.next = i } r.baseQueue = a = o, n.pending = null } if (null !== a) { o = a.next, r = r.baseState; var u = i = null, s = null, c = o; do { var f = c.lane; if ((mo & f) === f) null !== s && (s = s.next = { lane: 0, action: c.action, hasEagerState: c.hasEagerState, eagerState: c.eagerState, next: null }), r = c.hasEagerState ? c.eagerState : e(r, c.action); else { var d = { lane: f, action: c.action, hasEagerState: c.hasEagerState, eagerState: c.eagerState, next: null }; null === s ? (u = s = d, i = r) : s = s.next = d, ho.lanes |= f, Iu |= f } c = c.next } while (null !== c && c !== o); null === s ? i = r : s.next = u, ir(r, t.memoizedState) || (_i = !0), t.memoizedState = r, t.baseState = i, t.baseQueue = s, n.lastRenderedState = r } if (null !== (e = n.interleaved)) { a = e; do { o = a.lane, ho.lanes |= o, Iu |= o, a = a.next } while (a !== e) } else null === a && (n.lanes = 0); return [t.memoizedState, n.dispatch] } function zo(e) { var t = To(), n = t.queue; if (null === n) throw Error(l(311)); n.lastRenderedReducer = e; var r = n.dispatch, a = n.pending, o = t.memoizedState; if (null !== a) { n.pending = null; var i = a = a.next; do { o = e(o, i.action), i = i.next } while (i !== a); ir(o, t.memoizedState) || (_i = !0), t.memoizedState = o, null === t.baseQueue && (t.baseState = o), n.lastRenderedState = o } return [o, r] } function jo() { } function Ro(e, t) { var n = ho, r = To(), a = t(), o = !ir(r.memoizedState, a); if (o && (r.memoizedState = a, _i = !0), r = r.queue, qo(Mo.bind(null, n, r, e), [e]), r.getSnapshot !== t || o || null !== vo && 1 & vo.memoizedState.tag) { if (n.flags |= 2048, Do(9, No.bind(null, n, r, a, t), void 0, null), null === zu) throw Error(l(349)); 0 !== (30 & mo) || Ao(n, t, a) } return a } function Ao(e, t, n) { e.flags |= 16384, e = { getSnapshot: t, value: n }, null === (t = ho.updateQueue) ? (t = { lastEffect: null, stores: null }, ho.updateQueue = t, t.stores = [e]) : null === (n = t.stores) ? t.stores = [e] : n.push(e) } function No(e, t, n, r) { t.value = n, t.getSnapshot = r, Lo(t) && Io(e) } function Mo(e, t, n) { return n((function () { Lo(t) && Io(e) })) } function Lo(e) { var t = e.getSnapshot; e = e.value; try { var n = t(); return !ir(e, n) } catch (r) { return !0 } } function Io(e) { var t = Ol(e, 1); null !== t && rs(t, e, 1, -1) } function Fo(e) { var t = Co(); return "function" === typeof e && (e = e()), t.memoizedState = t.baseState = e, e = { pending: null, interleaved: null, lanes: 0, dispatch: null, lastRenderedReducer: Po, lastRenderedState: e }, t.queue = e, e = e.dispatch = ni.bind(null, ho, e), [t.memoizedState, e] } function Do(e, t, n, r) { return e = { tag: e, create: t, destroy: n, deps: r, next: null }, null === (t = ho.updateQueue) ? (t = { lastEffect: null, stores: null }, ho.updateQueue = t, t.lastEffect = e.next = e) : null === (n = t.lastEffect) ? t.lastEffect = e.next = e : (r = n.next, n.next = e, e.next = r, t.lastEffect = e), e } function Uo() { return To().memoizedState } function Vo(e, t, n, r) { var a = Co(); ho.flags |= e, a.memoizedState = Do(1 | t, n, void 0, void 0 === r ? null : r) } function Bo(e, t, n, r) { var a = To(); r = void 0 === r ? null : r; var l = void 0; if (null !== yo) { var o = yo.memoizedState; if (l = o.destroy, null !== r && ko(r, o.deps)) return void (a.memoizedState = Do(t, n, l, r)) } ho.flags |= e, a.memoizedState = Do(1 | t, n, l, r) } function $o(e, t) { return Vo(8390656, 8, e, t) } function qo(e, t) { return Bo(2048, 8, e, t) } function Wo(e, t) { return Bo(4, 2, e, t) } function Ho(e, t) { return Bo(4, 4, e, t) } function Qo(e, t) { return "function" === typeof t ? (e = e(), t(e), function () { t(null) }) : null !== t && void 0 !== t ? (e = e(), t.current = e, function () { t.current = null }) : void 0 } function Ko(e, t, n) { return n = null !== n && void 0 !== n ? n.concat([e]) : null, Bo(4, 4, Qo.bind(null, t, e), n) } function Go() { } function Yo(e, t) { var n = To(); t = void 0 === t ? null : t; var r = n.memoizedState; return null !== r && null !== t && ko(t, r[1]) ? r[0] : (n.memoizedState = [e, t], e) } function Xo(e, t) { var n = To(); t = void 0 === t ? null : t; var r = n.memoizedState; return null !== r && null !== t && ko(t, r[1]) ? r[0] : (e = e(), n.memoizedState = [e, t], e) } function Zo(e, t, n) { return 0 === (21 & mo) ? (e.baseState && (e.baseState = !1, _i = !0), e.memoizedState = n) : (ir(n, t) || (n = ht(), ho.lanes |= n, Iu |= n, e.baseState = !0), t) } function Jo(e, t) { var n = gt; gt = 0 !== n && 4 > n ? n : 4, e(!0); var r = po.transition; po.transition = {}; try { e(!1), t() } finally { gt = n, po.transition = r } } function ei() { return To().memoizedState } function ti(e, t, n) { var r = ns(e); if (n = { lane: r, action: n, hasEagerState: !1, eagerState: null, next: null }, ri(e)) ai(t, n); else if (null !== (n = Pl(e, t, n, r))) { rs(n, e, r, ts()), li(n, t, r) } } function ni(e, t, n) { var r = ns(e), a = { lane: r, action: n, hasEagerState: !1, eagerState: null, next: null }; if (ri(e)) ai(t, a); else { var l = e.alternate; if (0 === e.lanes && (null === l || 0 === l.lanes) && null !== (l = t.lastRenderedReducer)) try { var o = t.lastRenderedState, i = l(o, n); if (a.hasEagerState = !0, a.eagerState = i, ir(i, o)) { var u = t.interleaved; return null === u ? (a.next = a, Tl(t)) : (a.next = u.next, u.next = a), void (t.interleaved = a) } } catch (s) { } null !== (n = Pl(e, t, a, r)) && (rs(n, e, r, a = ts()), li(n, t, r)) } } function ri(e) { var t = e.alternate; return e === ho || null !== t && t === ho } function ai(e, t) { go = bo = !0; var n = e.pending; null === n ? t.next = t : (t.next = n.next, n.next = t), e.pending = t } function li(e, t, n) { if (0 !== (4194240 & n)) { var r = t.lanes; n |= r &= e.pendingLanes, t.lanes = n, bt(e, n) } } var oi = { readContext: xl, useCallback: wo, useContext: wo, useEffect: wo, useImperativeHandle: wo, useInsertionEffect: wo, useLayoutEffect: wo, useMemo: wo, useReducer: wo, useRef: wo, useState: wo, useDebugValue: wo, useDeferredValue: wo, useTransition: wo, useMutableSource: wo, useSyncExternalStore: wo, useId: wo, unstable_isNewReconciler: !1 }, ii = { readContext: xl, useCallback: function (e, t) { return Co().memoizedState = [e, void 0 === t ? null : t], e }, useContext: xl, useEffect: $o, useImperativeHandle: function (e, t, n) { return n = null !== n && void 0 !== n ? n.concat([e]) : null, Vo(4194308, 4, Qo.bind(null, t, e), n) }, useLayoutEffect: function (e, t) { return Vo(4194308, 4, e, t) }, useInsertionEffect: function (e, t) { return Vo(4, 2, e, t) }, useMemo: function (e, t) { var n = Co(); return t = void 0 === t ? null : t, e = e(), n.memoizedState = [e, t], e }, useReducer: function (e, t, n) { var r = Co(); return t = void 0 !== n ? n(t) : t, r.memoizedState = r.baseState = t, e = { pending: null, interleaved: null, lanes: 0, dispatch: null, lastRenderedReducer: e, lastRenderedState: t }, r.queue = e, e = e.dispatch = ti.bind(null, ho, e), [r.memoizedState, e] }, useRef: function (e) { return e = { current: e }, Co().memoizedState = e }, useState: Fo, useDebugValue: Go, useDeferredValue: function (e) { return Co().memoizedState = e }, useTransition: function () { var e = Fo(!1), t = e[0]; return e = Jo.bind(null, e[1]), Co().memoizedState = e, [t, e] }, useMutableSource: function () { }, useSyncExternalStore: function (e, t, n) { var r = ho, a = Co(); if (al) { if (void 0 === n) throw Error(l(407)); n = n() } else { if (n = t(), null === zu) throw Error(l(349)); 0 !== (30 & mo) || Ao(r, t, n) } a.memoizedState = n; var o = { value: n, getSnapshot: t }; return a.queue = o, $o(Mo.bind(null, r, o, e), [e]), r.flags |= 2048, Do(9, No.bind(null, r, o, n, t), void 0, null), n }, useId: function () { var e = Co(), t = zu.identifierPrefix; if (al) { var n = Xa; t = ":" + t + "R" + (n = (Ya & ~(1 << 32 - ot(Ya) - 1)).toString(32) + n), 0 < (n = _o++) && (t += "H" + n.toString(32)), t += ":" } else t = ":" + t + "r" + (n = So++).toString(32) + ":"; return e.memoizedState = t }, unstable_isNewReconciler: !1 }, ui = { readContext: xl, useCallback: Yo, useContext: xl, useEffect: qo, useImperativeHandle: Ko, useInsertionEffect: Wo, useLayoutEffect: Ho, useMemo: Xo, useReducer: Oo, useRef: Uo, useState: function () { return Oo(Po) }, useDebugValue: Go, useDeferredValue: function (e) { return Zo(To(), yo.memoizedState, e) }, useTransition: function () { return [Oo(Po)[0], To().memoizedState] }, useMutableSource: jo, useSyncExternalStore: Ro, useId: ei, unstable_isNewReconciler: !1 }, si = { readContext: xl, useCallback: Yo, useContext: xl, useEffect: qo, useImperativeHandle: Ko, useInsertionEffect: Wo, useLayoutEffect: Ho, useMemo: Xo, useReducer: zo, useRef: Uo, useState: function () { return zo(Po) }, useDebugValue: Go, useDeferredValue: function (e) { var t = To(); return null === yo ? t.memoizedState = e : Zo(t, yo.memoizedState, e) }, useTransition: function () { return [zo(Po)[0], To().memoizedState] }, useMutableSource: jo, useSyncExternalStore: Ro, useId: ei, unstable_isNewReconciler: !1 }; function ci(e, t) { try { var n = "", r = t; do { n += V(r), r = r.return } while (r); var a = n } catch (l) { a = "\nError generating stack: " + l.message + "\n" + l.stack } return { value: e, source: t, stack: a, digest: null } } function fi(e, t, n) { return { value: e, source: null, stack: null != n ? n : null, digest: null != t ? t : null } } function di(e, t) { try { console.error(t.value) } catch (n) { setTimeout((function () { throw n })) } } var pi = "function" === typeof WeakMap ? WeakMap : Map; function mi(e, t, n) { (n = Al(-1, n)).tag = 3, n.payload = { element: null }; var r = t.value; return n.callback = function () { Wu || (Wu = !0, Hu = r), di(0, t) }, n } function hi(e, t, n) { (n = Al(-1, n)).tag = 3; var r = e.type.getDerivedStateFromError; if ("function" === typeof r) { var a = t.value; n.payload = function () { return r(a) }, n.callback = function () { di(0, t) } } var l = e.stateNode; return null !== l && "function" === typeof l.componentDidCatch && (n.callback = function () { di(0, t), "function" !== typeof r && (null === Qu ? Qu = new Set([this]) : Qu.add(this)); var e = t.stack; this.componentDidCatch(t.value, { componentStack: null !== e ? e : "" }) }), n } function yi(e, t, n) { var r = e.pingCache; if (null === r) { r = e.pingCache = new pi; var a = new Set; r.set(t, a) } else void 0 === (a = r.get(t)) && (a = new Set, r.set(t, a)); a.has(n) || (a.add(n), e = Cs.bind(null, e, t, n), t.then(e, e)) } function vi(e) { do { var t; if ((t = 13 === e.tag) && (t = null === (t = e.memoizedState) || null !== t.dehydrated), t) return e; e = e.return } while (null !== e); return null } function bi(e, t, n, r, a) { return 0 === (1 & e.mode) ? (e === t ? e.flags |= 65536 : (e.flags |= 128, n.flags |= 131072, n.flags &= -52805, 1 === n.tag && (null === n.alternate ? n.tag = 17 : ((t = Al(-1, 1)).tag = 2, Nl(n, t, 1))), n.lanes |= 1), e) : (e.flags |= 65536, e.lanes = a, e) } var gi = _.ReactCurrentOwner, _i = !1; function Si(e, t, n, r) { t.child = null === e ? Xl(t, null, n, r) : Yl(t, e.child, n, r) } function wi(e, t, n, r, a) { n = n.render; var l = t.ref; return El(t, a), r = Eo(e, t, n, r, l, a), n = xo(), null === e || _i ? (al && n && el(t), t.flags |= 1, Si(e, t, r, a), t.child) : (t.updateQueue = e.updateQueue, t.flags &= -2053, e.lanes &= ~a, Wi(e, t, a)) } function ki(e, t, n, r, a) { if (null === e) { var l = n.type; return "function" !== typeof l || As(l) || void 0 !== l.defaultProps || null !== n.compare || void 0 !== n.defaultProps ? ((e = Ms(n.type, null, r, t, t.mode, a)).ref = t.ref, e.return = t, t.child = e) : (t.tag = 15, t.type = l, Ei(e, t, l, r, a)) } if (l = e.child, 0 === (e.lanes & a)) { var o = l.memoizedProps; if ((n = null !== (n = n.compare) ? n : ur)(o, r) && e.ref === t.ref) return Wi(e, t, a) } return t.flags |= 1, (e = Ns(l, r)).ref = t.ref, e.return = t, t.child = e } function Ei(e, t, n, r, a) { if (null !== e) { var l = e.memoizedProps; if (ur(l, r) && e.ref === t.ref) { if (_i = !1, t.pendingProps = r = l, 0 === (e.lanes & a)) return t.lanes = e.lanes, Wi(e, t, a); 0 !== (131072 & e.flags) && (_i = !0) } } return Ti(e, t, n, r, a) } function xi(e, t, n) { var r = t.pendingProps, a = r.children, l = null !== e ? e.memoizedState : null; if ("hidden" === r.mode) if (0 === (1 & t.mode)) t.memoizedState = { baseLanes: 0, cachePool: null, transitions: null }, Ca(Nu, Au), Au |= n; else { if (0 === (1073741824 & n)) return e = null !== l ? l.baseLanes | n : n, t.lanes = t.childLanes = 1073741824, t.memoizedState = { baseLanes: e, cachePool: null, transitions: null }, t.updateQueue = null, Ca(Nu, Au), Au |= e, null; t.memoizedState = { baseLanes: 0, cachePool: null, transitions: null }, r = null !== l ? l.baseLanes : n, Ca(Nu, Au), Au |= r } else null !== l ? (r = l.baseLanes | n, t.memoizedState = null) : r = n, Ca(Nu, Au), Au |= r; return Si(e, t, a, n), t.child } function Ci(e, t) { var n = t.ref; (null === e && null !== n || null !== e && e.ref !== n) && (t.flags |= 512, t.flags |= 2097152) } function Ti(e, t, n, r, a) { var l = Ra(n) ? za : Pa.current; return l = ja(t, l), El(t, a), n = Eo(e, t, n, r, l, a), r = xo(), null === e || _i ? (al && r && el(t), t.flags |= 1, Si(e, t, n, a), t.child) : (t.updateQueue = e.updateQueue, t.flags &= -2053, e.lanes &= ~a, Wi(e, t, a)) } function Pi(e, t, n, r, a) { if (Ra(n)) { var l = !0; La(t) } else l = !1; if (El(t, a), null === t.stateNode) qi(e, t), $l(t, n, r), Wl(t, n, r, a), r = !0; else if (null === e) { var o = t.stateNode, i = t.memoizedProps; o.props = i; var u = o.context, s = n.contextType; "object" === typeof s && null !== s ? s = xl(s) : s = ja(t, s = Ra(n) ? za : Pa.current); var c = n.getDerivedStateFromProps, f = "function" === typeof c || "function" === typeof o.getSnapshotBeforeUpdate; f || "function" !== typeof o.UNSAFE_componentWillReceiveProps && "function" !== typeof o.componentWillReceiveProps || (i !== r || u !== s) && ql(t, o, r, s), zl = !1; var d = t.memoizedState; o.state = d, Il(t, r, o, a), u = t.memoizedState, i !== r || d !== u || Oa.current || zl ? ("function" === typeof c && (Ul(t, n, c, r), u = t.memoizedState), (i = zl || Bl(t, n, i, r, d, u, s)) ? (f || "function" !== typeof o.UNSAFE_componentWillMount && "function" !== typeof o.componentWillMount || ("function" === typeof o.componentWillMount && o.componentWillMount(), "function" === typeof o.UNSAFE_componentWillMount && o.UNSAFE_componentWillMount()), "function" === typeof o.componentDidMount && (t.flags |= 4194308)) : ("function" === typeof o.componentDidMount && (t.flags |= 4194308), t.memoizedProps = r, t.memoizedState = u), o.props = r, o.state = u, o.context = s, r = i) : ("function" === typeof o.componentDidMount && (t.flags |= 4194308), r = !1) } else { o = t.stateNode, Rl(e, t), i = t.memoizedProps, s = t.type === t.elementType ? i : yl(t.type, i), o.props = s, f = t.pendingProps, d = o.context, "object" === typeof (u = n.contextType) && null !== u ? u = xl(u) : u = ja(t, u = Ra(n) ? za : Pa.current); var p = n.getDerivedStateFromProps; (c = "function" === typeof p || "function" === typeof o.getSnapshotBeforeUpdate) || "function" !== typeof o.UNSAFE_componentWillReceiveProps && "function" !== typeof o.componentWillReceiveProps || (i !== f || d !== u) && ql(t, o, r, u), zl = !1, d = t.memoizedState, o.state = d, Il(t, r, o, a); var m = t.memoizedState; i !== f || d !== m || Oa.current || zl ? ("function" === typeof p && (Ul(t, n, p, r), m = t.memoizedState), (s = zl || Bl(t, n, s, r, d, m, u) || !1) ? (c || "function" !== typeof o.UNSAFE_componentWillUpdate && "function" !== typeof o.componentWillUpdate || ("function" === typeof o.componentWillUpdate && o.componentWillUpdate(r, m, u), "function" === typeof o.UNSAFE_componentWillUpdate && o.UNSAFE_componentWillUpdate(r, m, u)), "function" === typeof o.componentDidUpdate && (t.flags |= 4), "function" === typeof o.getSnapshotBeforeUpdate && (t.flags |= 1024)) : ("function" !== typeof o.componentDidUpdate || i === e.memoizedProps && d === e.memoizedState || (t.flags |= 4), "function" !== typeof o.getSnapshotBeforeUpdate || i === e.memoizedProps && d === e.memoizedState || (t.flags |= 1024), t.memoizedProps = r, t.memoizedState = m), o.props = r, o.state = m, o.context = u, r = s) : ("function" !== typeof o.componentDidUpdate || i === e.memoizedProps && d === e.memoizedState || (t.flags |= 4), "function" !== typeof o.getSnapshotBeforeUpdate || i === e.memoizedProps && d === e.memoizedState || (t.flags |= 1024), r = !1) } return Oi(e, t, n, r, l, a) } function Oi(e, t, n, r, a, l) { Ci(e, t); var o = 0 !== (128 & t.flags); if (!r && !o) return a && Ia(t, n, !1), Wi(e, t, l); r = t.stateNode, gi.current = t; var i = o && "function" !== typeof n.getDerivedStateFromError ? null : r.render(); return t.flags |= 1, null !== e && o ? (t.child = Yl(t, e.child, null, l), t.child = Yl(t, null, i, l)) : Si(e, t, i, l), t.memoizedState = r.state, a && Ia(t, n, !0), t.child } function zi(e) { var t = e.stateNode; t.pendingContext ? Na(0, t.pendingContext, t.pendingContext !== t.context) : t.context && Na(0, t.context, !1), ro(e, t.containerInfo) } function ji(e, t, n, r, a) { return pl(), ml(a), t.flags |= 256, Si(e, t, n, r), t.child } var Ri, Ai, Ni, Mi, Li = { dehydrated: null, treeContext: null, retryLane: 0 }; function Ii(e) { return { baseLanes: e, cachePool: null, transitions: null } } function Fi(e, t, n) { var r, a = t.pendingProps, o = io.current, i = !1, u = 0 !== (128 & t.flags); if ((r = u) || (r = (null === e || null !== e.memoizedState) && 0 !== (2 & o)), r ? (i = !0, t.flags &= -129) : null !== e && null === e.memoizedState || (o |= 1), Ca(io, 1 & o), null === e) return sl(t), null !== (e = t.memoizedState) && null !== (e = e.dehydrated) ? (0 === (1 & t.mode) ? t.lanes = 1 : "$!" === e.data ? t.lanes = 8 : t.lanes = 1073741824, null) : (u = a.children, e = a.fallback, i ? (a = t.mode, i = t.child, u = { mode: "hidden", children: u }, 0 === (1 & a) && null !== i ? (i.childLanes = 0, i.pendingProps = u) : i = Is(u, a, 0, null), e = Ls(e, a, n, null), i.return = t, e.return = t, i.sibling = e, t.child = i, t.child.memoizedState = Ii(n), t.memoizedState = Li, e) : Di(t, u)); if (null !== (o = e.memoizedState) && null !== (r = o.dehydrated)) return function (e, t, n, r, a, o, i) { if (n) return 256 & t.flags ? (t.flags &= -257, Ui(e, t, i, r = fi(Error(l(422))))) : null !== t.memoizedState ? (t.child = e.child, t.flags |= 128, null) : (o = r.fallback, a = t.mode, r = Is({ mode: "visible", children: r.children }, a, 0, null), (o = Ls(o, a, i, null)).flags |= 2, r.return = t, o.return = t, r.sibling = o, t.child = r, 0 !== (1 & t.mode) && Yl(t, e.child, null, i), t.child.memoizedState = Ii(i), t.memoizedState = Li, o); if (0 === (1 & t.mode)) return Ui(e, t, i, null); if ("$!" === a.data) { if (r = a.nextSibling && a.nextSibling.dataset) var u = r.dgst; return r = u, Ui(e, t, i, r = fi(o = Error(l(419)), r, void 0)) } if (u = 0 !== (i & e.childLanes), _i || u) { if (null !== (r = zu)) { switch (i & -i) { case 4: a = 2; break; case 16: a = 8; break; case 64: case 128: case 256: case 512: case 1024: case 2048: case 4096: case 8192: case 16384: case 32768: case 65536: case 131072: case 262144: case 524288: case 1048576: case 2097152: case 4194304: case 8388608: case 16777216: case 33554432: case 67108864: a = 32; break; case 536870912: a = 268435456; break; default: a = 0 }0 !== (a = 0 !== (a & (r.suspendedLanes | i)) ? 0 : a) && a !== o.retryLane && (o.retryLane = a, Ol(e, a), rs(r, e, a, -1)) } return ys(), Ui(e, t, i, r = fi(Error(l(421)))) } return "$?" === a.data ? (t.flags |= 128, t.child = e.child, t = Ps.bind(null, e), a._reactRetry = t, null) : (e = o.treeContext, rl = sa(a.nextSibling), nl = t, al = !0, ll = null, null !== e && (Qa[Ka++] = Ya, Qa[Ka++] = Xa, Qa[Ka++] = Ga, Ya = e.id, Xa = e.overflow, Ga = t), t = Di(t, r.children), t.flags |= 4096, t) }(e, t, u, a, r, o, n); if (i) { i = a.fallback, u = t.mode, r = (o = e.child).sibling; var s = { mode: "hidden", children: a.children }; return 0 === (1 & u) && t.child !== o ? ((a = t.child).childLanes = 0, a.pendingProps = s, t.deletions = null) : (a = Ns(o, s)).subtreeFlags = 14680064 & o.subtreeFlags, null !== r ? i = Ns(r, i) : (i = Ls(i, u, n, null)).flags |= 2, i.return = t, a.return = t, a.sibling = i, t.child = a, a = i, i = t.child, u = null === (u = e.child.memoizedState) ? Ii(n) : { baseLanes: u.baseLanes | n, cachePool: null, transitions: u.transitions }, i.memoizedState = u, i.childLanes = e.childLanes & ~n, t.memoizedState = Li, a } return e = (i = e.child).sibling, a = Ns(i, { mode: "visible", children: a.children }), 0 === (1 & t.mode) && (a.lanes = n), a.return = t, a.sibling = null, null !== e && (null === (n = t.deletions) ? (t.deletions = [e], t.flags |= 16) : n.push(e)), t.child = a, t.memoizedState = null, a } function Di(e, t) { return (t = Is({ mode: "visible", children: t }, e.mode, 0, null)).return = e, e.child = t } function Ui(e, t, n, r) { return null !== r && ml(r), Yl(t, e.child, null, n), (e = Di(t, t.pendingProps.children)).flags |= 2, t.memoizedState = null, e } function Vi(e, t, n) { e.lanes |= t; var r = e.alternate; null !== r && (r.lanes |= t), kl(e.return, t, n) } function Bi(e, t, n, r, a) { var l = e.memoizedState; null === l ? e.memoizedState = { isBackwards: t, rendering: null, renderingStartTime: 0, last: r, tail: n, tailMode: a } : (l.isBackwards = t, l.rendering = null, l.renderingStartTime = 0, l.last = r, l.tail = n, l.tailMode = a) } function $i(e, t, n) { var r = t.pendingProps, a = r.revealOrder, l = r.tail; if (Si(e, t, r.children, n), 0 !== (2 & (r = io.current))) r = 1 & r | 2, t.flags |= 128; else { if (null !== e && 0 !== (128 & e.flags)) e: for (e = t.child; null !== e;) { if (13 === e.tag) null !== e.memoizedState && Vi(e, n, t); else if (19 === e.tag) Vi(e, n, t); else if (null !== e.child) { e.child.return = e, e = e.child; continue } if (e === t) break e; for (; null === e.sibling;) { if (null === e.return || e.return === t) break e; e = e.return } e.sibling.return = e.return, e = e.sibling } r &= 1 } if (Ca(io, r), 0 === (1 & t.mode)) t.memoizedState = null; else switch (a) { case "forwards": for (n = t.child, a = null; null !== n;)null !== (e = n.alternate) && null === uo(e) && (a = n), n = n.sibling; null === (n = a) ? (a = t.child, t.child = null) : (a = n.sibling, n.sibling = null), Bi(t, !1, a, n, l); break; case "backwards": for (n = null, a = t.child, t.child = null; null !== a;) { if (null !== (e = a.alternate) && null === uo(e)) { t.child = a; break } e = a.sibling, a.sibling = n, n = a, a = e } Bi(t, !0, n, null, l); break; case "together": Bi(t, !1, null, null, void 0); break; default: t.memoizedState = null }return t.child } function qi(e, t) { 0 === (1 & t.mode) && null !== e && (e.alternate = null, t.alternate = null, t.flags |= 2) } function Wi(e, t, n) { if (null !== e && (t.dependencies = e.dependencies), Iu |= t.lanes, 0 === (n & t.childLanes)) return null; if (null !== e && t.child !== e.child) throw Error(l(153)); if (null !== t.child) { for (n = Ns(e = t.child, e.pendingProps), t.child = n, n.return = t; null !== e.sibling;)e = e.sibling, (n = n.sibling = Ns(e, e.pendingProps)).return = t; n.sibling = null } return t.child } function Hi(e, t) { if (!al) switch (e.tailMode) { case "hidden": t = e.tail; for (var n = null; null !== t;)null !== t.alternate && (n = t), t = t.sibling; null === n ? e.tail = null : n.sibling = null; break; case "collapsed": n = e.tail; for (var r = null; null !== n;)null !== n.alternate && (r = n), n = n.sibling; null === r ? t || null === e.tail ? e.tail = null : e.tail.sibling = null : r.sibling = null } } function Qi(e) { var t = null !== e.alternate && e.alternate.child === e.child, n = 0, r = 0; if (t) for (var a = e.child; null !== a;)n |= a.lanes | a.childLanes, r |= 14680064 & a.subtreeFlags, r |= 14680064 & a.flags, a.return = e, a = a.sibling; else for (a = e.child; null !== a;)n |= a.lanes | a.childLanes, r |= a.subtreeFlags, r |= a.flags, a.return = e, a = a.sibling; return e.subtreeFlags |= r, e.childLanes = n, t } function Ki(e, t, n) { var r = t.pendingProps; switch (tl(t), t.tag) { case 2: case 16: case 15: case 0: case 11: case 7: case 8: case 12: case 9: case 14: return Qi(t), null; case 1: case 17: return Ra(t.type) && Aa(), Qi(t), null; case 3: return r = t.stateNode, ao(), xa(Oa), xa(Pa), co(), r.pendingContext && (r.context = r.pendingContext, r.pendingContext = null), null !== e && null !== e.child || (fl(t) ? t.flags |= 4 : null === e || e.memoizedState.isDehydrated && 0 === (256 & t.flags) || (t.flags |= 1024, null !== ll && (is(ll), ll = null))), Ai(e, t), Qi(t), null; case 5: oo(t); var a = no(to.current); if (n = t.type, null !== e && null != t.stateNode) Ni(e, t, n, r, a), e.ref !== t.ref && (t.flags |= 512, t.flags |= 2097152); else { if (!r) { if (null === t.stateNode) throw Error(l(166)); return Qi(t), null } if (e = no(Jl.current), fl(t)) { r = t.stateNode, n = t.type; var o = t.memoizedProps; switch (r[da] = t, r[pa] = o, e = 0 !== (1 & t.mode), n) { case "dialog": Dr("cancel", r), Dr("close", r); break; case "iframe": case "object": case "embed": Dr("load", r); break; case "video": case "audio": for (a = 0; a < Mr.length; a++)Dr(Mr[a], r); break; case "source": Dr("error", r); break; case "img": case "image": case "link": Dr("error", r), Dr("load", r); break; case "details": Dr("toggle", r); break; case "input": Y(r, o), Dr("invalid", r); break; case "select": r._wrapperState = { wasMultiple: !!o.multiple }, Dr("invalid", r); break; case "textarea": ae(r, o), Dr("invalid", r) }for (var u in be(n, o), a = null, o) if (o.hasOwnProperty(u)) { var s = o[u]; "children" === u ? "string" === typeof s ? r.textContent !== s && (!0 !== o.suppressHydrationWarning && Zr(r.textContent, s, e), a = ["children", s]) : "number" === typeof s && r.textContent !== "" + s && (!0 !== o.suppressHydrationWarning && Zr(r.textContent, s, e), a = ["children", "" + s]) : i.hasOwnProperty(u) && null != s && "onScroll" === u && Dr("scroll", r) } switch (n) { case "input": H(r), J(r, o, !0); break; case "textarea": H(r), oe(r); break; case "select": case "option": break; default: "function" === typeof o.onClick && (r.onclick = Jr) }r = a, t.updateQueue = r, null !== r && (t.flags |= 4) } else { u = 9 === a.nodeType ? a : a.ownerDocument, "http://www.w3.org/1999/xhtml" === e && (e = ie(n)), "http://www.w3.org/1999/xhtml" === e ? "script" === n ? ((e = u.createElement("div")).innerHTML = " + + + + +{{ end }} +``` + +* Replace the content of `swagger.html` with the following: + +``` + {{ define "swaggerPage" }} + {{ template "header" .}} + + {{ template "navigation" . }} +
+
+ +
+
+ {{ template "footer" .}} + + + +{{ end }} +``` + +* Restart your dashboard service + +* Browse your portal documentation + +Tyk Portal Catalogue API Documentation with ReDoc diff --git a/tyk-developer-portal/tyk-portal-classic/customise/customising-using-dashboard.mdx b/tyk-developer-portal/tyk-portal-classic/customise/customising-using-dashboard.mdx new file mode 100644 index 0000000000..6340a1d0cf --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/customise/customising-using-dashboard.mdx @@ -0,0 +1,123 @@ +--- +title: "Customize Pages with CSS and JavaScript" +order: 3 +robots: "noindex, nofollow" +sidebarTitle: "Customise Pages with CSS and JavaScript" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +The main customization that can be done with the Tyk Dashboard is via the CSS Editor. + +JS customization is also available in a programmatic way. + +#### Step 1: Open CSS Editor + +Click **CSS** from the **Portal Management** menu. + +Portal management menu + +#### Step 2: Make CSS Amendments + +In the CSS Editor, add the classes that you would like to override in the home page. For Tyk Cloud and Multi-Cloud users, this will already be filled in with some initial overrides for you: + +Portal CSS editor + +#### Step 3: Make Email CSS Amendments + +Email CSS editor + +If you wish to customize how emails are displayed to end-users, then you can also add new classes to the Email CSS editor, these classes will be added in-line to the email that is sent out. + +Once you have finished making your changes, click **Update** and the new CSS will be available on your site. + +### Updating CSS via API +Alternatively, as always, you can perform the above actions with an API call instead of through the Dashboard UI. + +First, we'll need to get the block ID of the CSS component in order to update it. This is stored in Mongo by the Dashboard. +To get the block ID, we have to make a REST call to the Dashboard API. + +To do so, run this `curl` command: + +```{.copyWrapper} +curl www.tyk-test.com:3000/api/portal/css \ +-H "Authorization:{DASHBOARD_API_KEY}" +``` +Response: +```{.copyWrapper} +{ + "email_css": "", + "id": "{CSS_BLOCK_ID}, + "org_id": "{ORG_ID}", + "page_css": ".btn-success {background-color: magenta1}" +} +``` +Now we can use the `id` and the `org_id` to update the CSS. +The below `curl` command will update the CSS for a specific organization. + +```{.copyWrapper} +curl -X PUT http://tyk-dashboard.com/api/portal/css \ + -H "authorization:{DASHBOARD_API_KEY}" \ + -d '{ + "email_css": "", + "id": "{CSS_BLOCK_ID}, + "org_id": "{ORG_ID}", + "page_css": ".btn-success {background-color: magenta}" + }' +``` + + [1]: /img/dashboard/portal-management/portal_man_css.png + [2]: /img/dashboard/portal-management/portal_site_css.png + + ### Updating JavaScript via API + + In order to initialize the portal JS object in the database use the following request where `console.log(1)` should be replaced by your JS snippet: + + ```{.copyWrapper} +curl -X POST www.tyk-test.com:3000/api/portal/js \ +-H "Authorization:{DASHBOARD_API_KEY}" \ +-d '{"page_js": "console.log(1)"}' +``` + +Request: +```{.copyWrapper} +{ + "page_js": "console.log(1)" +} +``` + +Response: +```{.copyWrapper} +{ + "Status": "OK", + "Message": "609b71df21c9371dd5906ec1", + "Meta": null +} +``` + +The endpoint will return the ID of the portal JS object, this can be used to update it. + + ```{.copyWrapper} +curl www.tyk-test.com:3000/api/portal/js \ +-H "Authorization:{DASHBOARD_API_KEY}" \ +--data '{"page_js": "console.log(2)", "id": "609b71df21c9371dd5906ec1"}' +``` + +Request: +```{.copyWrapper} +{ + "page_js": "console.log(2)", + "id": "609b71df21c9371dd5906ec1" +} +``` + +Response: +```{.copyWrapper} +{ + "page_js": "console.log(1)" +} +``` + +The JavaScript snippet that's added through this endpoint is injected at the bottom of the portal page using a ` + + + + +

Type something in the input field to search the table for first names, last names or emails:

+ +

+ +
+ +``` + + +And save. + +Now visit the portal at "http://dashboard-host:3000/portal/custom" + +custom_page_display + +You now have a searchable Input box that will dynamically filter the results of the table. diff --git a/tyk-developer-portal/tyk-portal-classic/customise/developer-meta-data.mdx b/tyk-developer-portal/tyk-portal-classic/customise/developer-meta-data.mdx new file mode 100644 index 0000000000..d6df79c6a1 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/customise/developer-meta-data.mdx @@ -0,0 +1,20 @@ +--- +title: "Customize the Developer Signup Form" +order: 4 +robots: "noindex, nofollow" +sidebarTitle: "Customise the Developer Signup Form" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +When a developer signs up to your developer Portal, you might wish to capture more information about the developer than is supplied by the default form. To enable new fields in this form (they are automatically added to the form as you add them), go to the **Portal Management > Settings** screen, and edit the **Sign up form customization** section: + +Tyk developer portal sign up form customization + +### Developer metadata and keys + +All developer metadata is automatically added to the key metadata when a token is generated, this can be useful if you need to add more information to your upstream requests. + +A developer username will also automatically be made the alias for an API token so that it is easy to identify in the analytics. diff --git a/tyk-developer-portal/tyk-portal-classic/developer-profiles.mdx b/tyk-developer-portal/tyk-portal-classic/developer-profiles.mdx new file mode 100644 index 0000000000..88bad67fd1 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/developer-profiles.mdx @@ -0,0 +1,160 @@ +--- +title: "Developer Profiles" +order: 2 +robots: "noindex, nofollow" +sidebarTitle: "Developer Profiles" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +Users that are signed up to your portal are called "Developers", these users have access to a Dashboard page which show them their API usage over the past 7 days as well as the policy and quota limits on their relevant keys. + +Developers can sign up to multiple APIs using the API catalog. + +Developer accounts belong to an organization ID, so accounts cannot be shared across organizations in a Tyk Dashboard setup. + +### Navigate to the Portal Developers Section + +Developer Menu + +#### Select Add Developer + +Developer Profile add + +### Add Basic Details + +Developer Profile Create Details + +### Developer Profile Overview + +The first panel in a developer profile will show you an avatar (if they have a Gravatar-enabled email address), as well as the basic fields of their signup: + +Developer profile detail + +### Developer Usage + +The next panel will show you their apI usage as an aggregate for all the tokens that they have generated with their developer access: + +Developer usage graph + +### Developer Keys + +In this panel, you will be able to see the various Keys the developer has access to, and the policies that are connected to the individual Key. + + + +From version 1.9, you can now apply multiple policies to an individual Key. + + + +To drill down into the specific usage patterns for each Key, click **ANALYTICS** for the Key. + +Developer Keys + +### Add a New Key + +To subscribe a developer to a new Key, from the Edit Developer screen, click **New Key**. From the pop-up screen, select one or more policies from the drop-down list and click **Request Key**. + + New Key Request + +### Changing Developer Policy Keys + +#### Step 1: View the Developer Profile + +Browse to the developers list view and select the developer that you wish to manage. + +Developer profile detail + +#### Step 2: View Keys List + +This sections shows you the Keys and the policies connected to them. This view will always try to match the access level to a catalog entry, if the policy assigned to a developer is not in the catalog, the entry will read "(No Catalog Entry)". We recommend that all policy levels are in your catalog, even if they are not all live. + +#### Step 3: Click Options + +From the Options drop-down for the Key, select **Change Policy**. + +Keys Sections + +#### Step 4: Select the New Policy + +Select a new policy to add to your Key from the **Policies** drop-down list. You can also remove existing policies connected to the Key. + +Change policy drop down list + +#### Step 5: Save the Change + +Click **CHANGE KEY POLICY** to save the changes. + +### Developer OAuth Clients + + +### Edit the Developer Profile + +All fields in the profile are editable. In this section you can select a field and modify that data for the developer. This will not affect any tokens they may have, but it will affect how it appears in their Developer Dashboard in your Portal. + +Developer edit form + +Developers can edit this data themselves in their accounts section. + +### Search for a Developer + +You can search for a developer (by email address) by entering their address in the Search field. + +This option is only available from Dashboard v1.3.1.2 and onwards. + +Developer Profile Search + +### Developer Edit Profile + +Once logged in, a developer can edit their profile. Select **Edit profile** from the **Account** menu drop-down list. + +Manage Profile + +A developer can change the following: +* Email +* Change Password +* Name +* Telephone +* Country Location + +### Reset Developer Password + +If a developer has forgotten their password, they can request a password reset email from the Login screen. + +Login Screen + +1. Click **Request password reset** +2. Enter your email address and click **Send Password reset email** + +Email Reset + +You will be sent an email with a link to reset your Developer password. Enter your new password and click **Update**. You can then login with your new details. + + + +Your password must be a minimum of 6 characters. + + + +Confirm password + + + + + + [1]: /img/dashboard/portal-management/developer_menu_2.5.png + [2]: /img/dashboard/portal-management/add_developer_2.5.png + [3]: /img/dashboard/portal-management/developer_details_2.5.png + [4]: /img/dashboard/portal-management/developer_overview_2.5.png + [5]: /img/dashboard/portal-management/developer_usage_2.5.png + [6]: /img/dashboard/portal-management/developer_subs_2.5.png + [7]: /img/dashboard/portal-management/developer_edit_2.5.png + [8]: /img/dashboard/portal-management/developer_search_2.5.png + [13]: /img/dashboard/portal-management/developer_edit_2.5.png + [14]: /img/dashboard/portal-management/keys_dev_profile.png + [15]: /img/dashboard/portal-management/change_key_policy.png + [16]: /img/dashboard/portal-management/new_key_request.png + + diff --git a/tyk-developer-portal/tyk-portal-classic/dynamic-client-registration.mdx b/tyk-developer-portal/tyk-portal-classic/dynamic-client-registration.mdx new file mode 100644 index 0000000000..13cb5abba3 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/dynamic-client-registration.mdx @@ -0,0 +1,43 @@ +--- +title: "Classic Portal - Dynamic Client Registration" +order: 3 +robots: "noindex, nofollow" +sidebarTitle: "Overview" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +## OAuth 2.0 Dynamic Client Registration Protocol (DCR) + +Available from version 3.2.0 onwards. + +## What is Dynamic Client Registration? + +DCR is a protocol of the Internet Engineering Task Force put in place to set standards in the dynamic registration of clients with authorization servers. +We will go into the specifics of how it works in the context of Tyk, but if you are interested in reading the full RFC, go to: https://tools.ietf.org/html/rfc7591 + +## Why should I use it? + +DCR is a way for you to integrate your developer portal with an external identity provider such as Keycloak, Gluu, Auth0, Okta etc... +The portal developer won't notice a difference. However when they create the app via Tyk Developer portal, Tyk will dynamically register that client on your authorization server. This means that it is the Authorization Server who will issue issue the Client ID and Client Secret for the app. +Some of our users leverage external Identity Providers because they provide a variety of features to support organizations in managing identity in one place across all their stack. + +This feature is optional and you can still have a great level of security only using Tyk as your authorization server. + +## Enabling Dynamic Client Registration + +We provide guides for the following identity providers: + +- [Gluu](/tyk-developer-portal/tyk-portal-classic/gluu-dcr). Official docs are available [here](https://gluu.org/docs/gluu-server/4.0/admin-guide/openid-connect/#dynamic-client-registration). +- [Curity](/tyk-developer-portal/tyk-portal-classic/curity-dcr). Official docs are available [here](https://curity.io/docs/idsvr/latest/token-service-admin-guide/dcr.html). +- [Keycloak](/tyk-developer-portal/tyk-portal-classic/keycloak-dcr). Official docs are available [here](https://www.keycloak.org/securing-apps/client-registration). +- [OKTA](/tyk-developer-portal/tyk-portal-classic/okta-dcr). Official docs are available [here](https://developer.okta.com/docs/reference/api/oauth-clients/). + + +In case your provider isn't on the list, use the "Other" provider option in the DCR settings. This mode would keep the interaction with your IDP as standard possible. Note that not all IDPs fully implement the standard. + +## Troubleshooting + +The DCR functionality abstracts most of the errors to the end user (in this case, the developer). In order to diagnose issues between Tyk and your IDP, please refer to the Tyk Dashboard logs. diff --git a/tyk-developer-portal/tyk-portal-classic/gluu-dcr.mdx b/tyk-developer-portal/tyk-portal-classic/gluu-dcr.mdx new file mode 100644 index 0000000000..6184bab1c4 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/gluu-dcr.mdx @@ -0,0 +1,147 @@ +--- +title: "Step by step guide using Gluu" +order: 3 +robots: "noindex, nofollow" +sidebarTitle: "Step by step guide using Gluu" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +We are going walk you through a basic integration of Tyk with [Gluu](https://gluu.org/) using the [OpenID Connect Dynamic Client Registration protocol](https://tools.ietf.org/html/rfc7591). Our current implementation provides support for the client credentials flow with support for JWT. + +The user journey is as follow: + +1. A developer signs up and creates a Dynamic Client Registration provider using your Developer Portal. + +2. Tyk sends the Dynamic Client Registration call to your IDP. The IDP replies with the client ID and secret. + +3. Using that information, the developer (or the application) triggers a call to the token endpoint of the IDP. + +4. Your developer (or the application) then triggers a call to Tyk, using the token that was generated by the IDP. Tyk validates this token using the JWKS provided by the IDP. + +### Requirements + +- A Gluu installation, more details [here](https://gluu.org/). +- A [Tyk Self Managed installation](/tyk-self-managed/install) (Gateway + Dashboard). + +### Getting started with Gluu + +In order to get started with Dynamic Client Registration you’ll need to get the OpenID Connect registration endpoint. Open your Gluu dashboard and select the "Configuration" section. Select "JSON Configuration" and toggle the "OxAuth Configuration" tab. + +Step 1 + +In this view you will find the registration endpoint: + +Step 2 + +Another endpoint that will be relevant for your setup is the Well-Known configuration endpoint. Keep both URLs handy as you’ll use them for our next steps. This endpoint typically looks as follows: https://gluu-server/.well-known/openid-configuration + +Because of known issues with Tyk’s JWT driver, you’ll set specific algorithms for the JWKS endpoint. In the same "OxAuth Configuration" tab, scroll down to "jwksAlgorithmsSupported" and select the following options: + +Step 3 + +Click "Save OxAuth Configuration" afterwards. + +For more information on this particular issue, refer to the Gluu documentation. + +### Setting up Tyk + +Now you're ready to set up Tyk. For compatibility reasons, check your `tyk_analytics.conf` and make sure that a proper `oauth_redirect_uri_separator` parameter is set. You can use the following value: + +```json + "oauth_redirect_uri_separator": ";", +``` + +Remember to restart the service after applying the above change. + +Now open the Tyk Dashboard and click **APIs** under **System Management**. Create a new API called "Gluu API": + +Step 4 + +After the first part of the API creation form was filled, click on "Configure API" and set the authentication settings as follows: + +Step 5 + + + +Where do I get the proper JWKS URI for my Gluu environment? + +The JWKS URI is a required field in the `.well-known/openid-configuration` endpoint of your OpenID Connect Provider metadata. Typically found as `"jwks_uri"`. Please see the spec https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse for further information. + + + +For the **Identity Source** field use `"client_id"` and for **Policy Field Name** use `"pol"`. + +Click "Save" and switch to the "Policies" button under "System Management". Once in this section, click on "Create a Policy" and call it "Gluu Policy". Use the default values for this one. Remember to select the previously created "Gluu API" in the access rights section. You will also need to set an expiration setting for the keys. + +After the policy is ready, switch back to the API settings and make sure that the API is using the appropriate policy: + +Step 6 + +Now you’re ready to add this API to the developer portal. Switch to the "Catalog" section under "Portal Management" on the navigation menu. Click on "Add New API", set a name for it and select the newly created policy. For this example use "Gluu Policy": + +Step 7 + +Hit "Save" and click on the recently created item again, switch to the "Settings" tab that’s next to "API Details". In "API Details" toggle the "Override global settings" option. + + + +Tyk lets you set global portal settings that apply to **all portal-listed APIs**, in this guide we assume you’re enabling and setting up DCR for a single API. In case you want to enable DCR for all the APIs, you should go to the **Settings** section under **Portal Management**, and in the **API Access** tab you can enter your DCR settings there. + + + +Once the "Override global settings" option is toggled, scroll down to the DCR section in the bottom and enter the following settings: + +Step 8 + +**Providers:** Different providers might implement the standard in slightly different ways. Tyk provides a specific driver for each one. For IDPs that aren’t on the list use the "Other" option. For this guide, pick "Gluu". + +**Grant Types:** The [OAuth 2.0 grant types](/api-management/authentication/oauth-2) types that will be used by the client, see the [specification](https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2) for more details. Set "Client Credentials". + +**Token Endpoint Auth Method:** defines the way the client will authenticate against the token endpoint. Use "Client Secret - Post". + +**Response Types:** OAuth 2.0 response types that will be used by the client. Set **Token**. + +**Identity Provider Host:** Base IDP URL, e.g. `https://gluu-server/` + +**Client Registration Endpoint:** OpenID Connect client registration endpoint. The value we use is `https://gluu-server/oxauth/restv1/register` + +This value is found in your well-known discovery document as `registration_endpoint`. The well-known location URL is typically `https://gluu-server/.well-known/openid-configuration` (replace "gluu-server" with your hostname). + +**Initial Registration Access Token:** the token that’s used to register new clients, this was generated in the early steps of the guide. + +### Testing the flow + +Now that both Tyk and Gluu are ready you can try the complete flow. Click "Developers" under "Portal Management", then click "Add developer" and enter some basic information here to create a developer user. + +After the developer is created, open the portal, click on the "OAuth Clients" navigation bar button and follow the wizard: + +Step 9 + +After clicking "Create first OAuth Client" you’ll see your previously created "Gluu API". Select it and click "Save and continue". The following screen will require you to enter a client name. It’s possible to set redirect URLs if you also plan to use this client for other flow types. This setting can be left blank for the purposes of this example. + +Step 10 + +Once you click "Create", Tyk will trigger a registration on your IDP and the details of your client will show up: + +Step 11 + +If you check the Gluu dashboard you will see new client (named "GluuClient"): + +Step 12 + +The next step is to generate a token and use it for accessing your "Gluu API". you can use Postman for this. You will need the token URL which it’s also present in the Well-Known URI of your organization. The field is named `"token_endpoint"`. +For this example use the following: https://gluu-server/oxauth/restv1/token + +Your Postman request should contain the following body, where `"client_id"` and `"client_secret"` are the values you got from the developer portal: + +Step 13 + +Note that you aren’t using any additional headers for this request, the client credentials are enough. + +Once you get a response from the IDP, you can copy the `"access_token"` and use it to access your "Gluu API", this request will be proxied by Tyk: + +Step 14 + diff --git a/tyk-developer-portal/tyk-portal-classic/graphql.mdx b/tyk-developer-portal/tyk-portal-classic/graphql.mdx new file mode 100644 index 0000000000..012e0178ea --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/graphql.mdx @@ -0,0 +1,51 @@ +--- +title: "Developer Portal GraphQL" +description: "How to publish GraphQL APIs to your Tyk Developer Portal" +keywords: "GraphQL, Playground, CORS, UDG" +order: 7 +robots: "noindex, nofollow" +sidebarTitle: "GraphQL with Classic Portal" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +As of Tyk v3.0.0, you can now publish GraphQL APIs, including [Universal Data Graph](/api-management/data-graph#overview) APIs(UDG) to the Tyk Developer Portal. + +When you do that, your API consumers can navigate through a GraphQL Playground, with an IDE complete with Intellisense. + +Portal GraphQL Playground + +## Video Walkthrough + +We have a YouTube walkthrough of how to publish a GraphQL API to your Developer Portal: + + + +## How To Set Up + +Simply create a GraphQL or Universal Data Graph API, create a Policy which protects it, and then publish it to the Developer Portal Catalog. + +In the "Create a Catalog" section, at the bottom, make sure you enable the "Display Playground" + + +Portal GraphQL Playground Setup + +And then, when your API consumers are on the Developer Portal Catalog and click on View Documentation, they will be taken to the GraphQL Playground. + +Portal GraphQL Playground View Docs + + +## Protected GraphQL Catalog + +If you have a protected API, your users won't be able to inspect the GraphQL schema or make API calls until they add their API Key to the Headers section: + +Portal GraphQL Playground Header Injection + +## CORS + +You may have to enable the following CORS settings in the "Advanced Options" of the API Designer to allow your consumers to access the GraphQL Playground: + + +Portal GraphQL Playground CORS diff --git a/tyk-developer-portal/tyk-portal-classic/key-requests.mdx b/tyk-developer-portal/tyk-portal-classic/key-requests.mdx new file mode 100644 index 0000000000..3aead7b16a --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/key-requests.mdx @@ -0,0 +1,47 @@ +--- +title: "Key Requests" +robots: "noindex, nofollow" +sidebarTitle: "Key Requests" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +## Key Requests + +A key request is a record that is generated when a developer requests an access token for an API published in the API Catalog. The Key request encompasses the following information: + +- The policy of which access is being requested +- The developer doing the requesting +- The catalog entry in question +- The reasoning of why the developer should have access (these are dynamic fields and can be configured) + +When a developer requests access to an API Catalog entry, this key request represents that request for access. The key request can then be acted on, either by the portal itself, or by an administrator. The key request does not grant a token yet, it simply marks the fact that a token has been requested and why. + +Tyk enables you to manage this flow in a few ways: + +- Auto-approve the key request. +- Have an admin approve the key-request. +- Hand off to a third-party system to manage the key-request (e.g. for billing or additional user validation). This is done via WebHooks or via the "Redirect Key Request" Portal Setting. + +## Key Approval +Once a key request is created, one of two things can be done to it: + +- It can be approved: Covered below +- It can be declined: In which case the request is deleted. + +A key request can be created using the Dashboard API too, in fact, the Key Request mechanism is a great way to create a mapping between an identity (a developer) and a token, and managing that process. + +### Secure Key Approval + +By default, the Key Approval flow is straight forward. Once a Key Request is approved, the Developer will be notified via an email which contains the API Key. + +As of Dashboard version `3.1.0`, it is now possible to turn on a more secure key approval flow. Once the "Request Key Approval" setting is enabled, we see an additional setting: +secure_key_approval_setting + +With this feature turn on, we prevent the API key from being sent in plain text via email. Instead, the once a key request is approved, the Developer will be sent a confirmation link in an email that directs them to the Portal: +secure_key_approval_email + +After clicking the `Generate Key` link and logging into the Portal, the key becomes available to the user: +secure_key_approval_generate \ No newline at end of file diff --git a/tyk-developer-portal/tyk-portal-classic/keycloak-dcr.mdx b/tyk-developer-portal/tyk-portal-classic/keycloak-dcr.mdx new file mode 100644 index 0000000000..a268ca98a5 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/keycloak-dcr.mdx @@ -0,0 +1,159 @@ +--- +title: "Step by step guide using Keycloak" +order: 1 +robots: "noindex, nofollow" +sidebarTitle: "Step by step guide using Keycloak" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +We are going walk you through a basic integration of Tyk with Keycloak using the [OpenID Connect Dynamic Client Registration protocol](https://tools.ietf.org/html/rfc7591). Our current implementation provides support for the client credentials flow with support for JWT. To the developer it works like this: + +1. An API with its corresponding security policy is created in Tyk. It is then added to the Developer Portal Catalog. + +2. A developer signs up and creates a Dynamic Client Registration provider using the Developer Portal. +Tyk sends the Dynamic Client Registration call to your IDP. The IDP replies with the client ID and secret. + +3. Using the previous information, the developer (or your application) triggers a call to the token endpoint of the IDP. +The developer (or your application) then triggers a call to Tyk, using the token that was generated by the IDP. Tyk validates this token using the JWKS provided by the IDP. + +### Requirements + +- A [Keycloak](https://www.keycloak.org/) instance. +- A [Tyk Self Managed installation](/tyk-self-managed/install) (Gateway + Dashboard). + +### Getting started with Keycloak + +To get started with Dynamic Client Registration in Keycloak you'll need to generate an [initial access token](https://openid.net/specs/openid-connect-registration-1_0.html#Terminology) using the Keycloak Administration Console. After logging in, click **Realm settings** under **Configure** and select the **Client Registration** tab: + +Step 1 + +To generate an initial access token, click **Create** and set the expiration time and maximum number of clients to be created using this token: + +Step 2 + +Click **Save** and the token will be created. Keep it safe as you'll use this token to configure Tyk. + +### Setting up Tyk + +Now you're ready to set up Tyk. For compatibility reasons, check your `tyk_analytics.conf` and make sure that a proper `oauth_redirect_uri_separator` parameter is set. You may use the following value: + +```json + "oauth_redirect_uri_separator": ";", +``` + +**Note:** If you're using a self-signed certificate on your Keycloak instance, you will need to set additional flags on both gateway and dashboard. For skipping DCR endpoint SSL verification, add the following flag to `tyk_analytics.conf`: + +```json + "dcr_ssl_insecure_skip_verify": true +``` + +Also add the following flag to `tyk.conf`, this will instruct the gateway to skip SSL verification when the JWT middleware is in use, particularly when JWKS are retrieved from your IDP: + +```json + "jwt_ssl_insecure_skip_verify": true +``` + +Remember to restart the services after applying the above changes. + +Open the Tyk Dashboard and click **APIs** under **System Management**. Create a new API called "Keycloak API": + +Step 3 + +Complete first part of the API creation form, then click **Configure API** and set the Authentication mode as in the image below: + +Step 4 + + + +Where do I get the proper JWKS URI for my Keycloak environment? + +The JWKS URI is a required field in the `.well-known/openid-configuration` endpoint of your OpenID Connect Provider metadata. Please see the [OpenID spec](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse) for further information. + + + + + +For the **Identity Source** field use `"sub"` and for **Policy Field Name** use `"pol"`. + +1. Click **Save** +2. Select **Policies** under **System Management** +3. Click **Create a Policy** and call it **Keycloak Policy**. Use the default values for this policy. +4. In the **Access rights** section, select your previously created **Keycloak API**. You will also need to enter an expiration setting for your keys. + +After the policy is created, switch back to the API settings and make sure that the API is using your **Keycloak API** policy: + +Step 5 + +Now you're ready to add this API to the Developer Portal. +1. Click **Catalog** under **Portal Management** on the navigation menu. +2. Click **Add New API**, enter a name for it and select the newly created policy. Again, you will use **Keycloak Policy**: + +Step 6 + +1. Click **Save** then open the API added again +2. Open the **Settings** tab. +3. In **API Details** select the **Override global settings** option. + + + + + Tyk lets you set global portal settings that apply to **all portal-listed APIs**, in this guide we assume you’re enabling and setting up DCR for a single API. In case you want to enable DCR for all the APIs, you should go to the **Settings** section under **Portal Management**, and in the **API Access** tab you can enter your DCR settings there. + + + +4. Scroll down to the DCR section and enter the following settings: + +Step 7 + +**Providers:** Different providers might implement the standard in slightly different ways, Tyk provides a specific driver for each one. For IDPs that aren’t on the list use the **Other** option. + +**Grant Types:** The [OAuth 2.0 grant types](/api-management/authentication/oauth-2) that will be used by the client, see the [specification](https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2) for more details. + +**Token Endpoint Auth Method:** defines the way the client will authenticate against the token endpoint. + +**Response Types:** OAuth 2.0 response types that will be used by the client. + +**Identity Provider Host:** Base IDP URL, e.g. `https://keycloak:8443/` + +**Client Registration Endpoint:** OpenID Connect client registration endpoint. This value is found in your well-known discovery document as `registration_endpoint`. The well-known location URL is typically `https://keycloak:8443/.well-known/openid-configuration` + +**Initial Registration Access Token:** the token that’s used to register new clients, this was generated in the early steps of the guide. + +### Testing the flow + +Now that both Tyk and Keycloak are ready we can test the complete flow. + +1. Click **Developers** under **Portal Management** +2. Click on **Add developer** and create a developer user. + +After the developer is created, open your Developer Portal, click on the **OAuth Clients** navigation bar button and follow the wizard: + +Step 8 + +Click **Create first OAuth Client**. You’ll see your previously created **Keycloak API**, select it and click **Save and continue**. The following screen will require you to enter a client name. It’s also possible to set redirect URLs if you also plan to use this client for other flow types. This setting can be left blank for the purposes of this guide. + +Step 9 + +Once you click **Create**, Tyk will trigger a registration on your IDP and the details of your client will be displayed: + +Step 10 + +If you check the Keycloak dashboard you will see this client too: + +Step 11 + +The next step is to generate a token and use it for accessing your **Keycloak API**. We'll use Postman for this. You will need your token URL which is also the well-known URL for your organization. +For this guide we use `https://keycloak:8443/auth/realms/master/protocol/openid-connect/token` + +Your Postman request should contain the following body, where `"client_id"` and `"client_secret"` are the credentials you got from the developer portal: + +Step 12 + +Note that we aren’t using any additional headers for this request, the client credentials are enough. + +Once we get a response from the IDP, we can copy the `"access_token"` and use it to access our **Keycloak API**, this request will be proxied by Tyk: + +Step 13 diff --git a/tyk-developer-portal/tyk-portal-classic/monetise.mdx b/tyk-developer-portal/tyk-portal-classic/monetise.mdx new file mode 100644 index 0000000000..37ee15daae --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/monetise.mdx @@ -0,0 +1,35 @@ +--- +title: "Monetize" +order: 11 +robots: "noindex, nofollow" +sidebarTitle: "Monetising your APIs" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +Out of the box, the Tyk Developer Portal does not have a billing component, however, this does not mean that it is not possible to enable monetization within a Portal developer access flow. + +### The Developer Key Request Flow + +When a developer enrolls for API access with a Tyk portal system, they will: + +1. Sign up +2. Select a catalog entry to participate in +3. Submit a key request form +4. Receive their token + +With Tyk, it is possible to prevent step 4, which auto-enables the key, and instead have the developer redirected to a third party app. This app can then handle any transactional process such as taking a credit card number or pre-validating the developer, before returning the developer to the Portal. + +When Tyk hands off to the redirected app, it will also add the key request ID to the request, so the application that handles the transaction can then use the Tyk Dashboard REST API to approve the key request (triggering the email that notifies the developer of their token, as well as notifying the calling application of the raw token), closing the loop. + +To enable the developer hand-off in a Tyk Portal, from the **Portal Settings** enable the redirect option: + +Redirect key requests form + +## Example Using Stripe + +In this video, we walk you through setting up Stripe to take payments via your Tyk Developer Portal. + + \ No newline at end of file diff --git a/tyk-developer-portal/tyk-portal-classic/okta-dcr.mdx b/tyk-developer-portal/tyk-portal-classic/okta-dcr.mdx new file mode 100644 index 0000000000..8ef5693ee4 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/okta-dcr.mdx @@ -0,0 +1,172 @@ +--- +title: "Step by step guide using Okta" +order: 2 +robots: "noindex, nofollow" +sidebarTitle: "Step by step guide using Okta" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +## Introduction + +We are going walk you through a basic integration of Tyk with Okta using the [OpenID Connect Dynamic Client Registration protocol](https://tools.ietf.org/html/rfc7591). Our current implementation provides support for the client credentials flow with support for JWT. + +The user journey is as follow: + +1. A developer signs up and creates a Dynamic Client Registration provider using the Developer Portal. + +2. Tyk sends the Dynamic Client Registration call to your IDP. The IDP replies with the client ID and secret. + +3. Using that information, the developer (or the application) triggers a call to the token endpoint of the IDP. + +4. The developer (or the application) then triggers a call to Tyk, using the token that was generated by the IDP. Tyk validates this token using the JWKS provided by the IDP. + +### Requirements + +- An OKTA account (a [trial account](https://www.okta.com/free-trial/) should be enough). +- A [Tyk Self Managed installation](/tyk-self-managed/install) (Gateway + Dashboard). + +### Getting started with OKTA + +First signup to OKTA, the initial screen looks like: + +Step 1 + +The first thing you’ll need for our integration is an API token from OKTA, the OpenID specification also calls this an [Initial Access Token](https://openid.net/specs/openid-connect-registration-1_0.html#Terminology) to differentiate it from other tokens that are used with this protocol. To create this token, click **API** option from the **Security** menu on the navigation bar: + +Step 2 + +From the API section, select the **Tokens** tab and click **Create Token** and enter a name for the token. For this guide we’re calling it "Tyk Integration": + +Step 3 + +Click **Create Token**. Keep it safe as you'll use this token to configure Tyk. + +Next you need to create a scope, from the **Authorization servers** tab in the API section, click **Add Scope**. You need to select the **Set as default scope** option: + +Step 4 + +### Setting up Tyk + +Now you're ready to set up Tyk. For compatibility reasons, check your `tyk_analytics.conf` and make sure that a proper `oauth_redirect_uri_separator` parameter is set. You may use the following value: + +```json + "oauth_redirect_uri_separator": ";", +``` + +Remember to restart the service after applying the above change. + +Now open the Tyk Dashboard and click **APIs** under **System Management**. Create a new API called "OKTA API": + +Step 5 + +Complete first part of the API creation form, then click **Configure API** and set the Authentication mode as in the image below: + +Step 6 + + + +Where do I get the proper JWKS URI for my Keycloak environment? + +From the OKTA Dashboard, open the **API** section under **Security**, take the base URL from the default Authorization Server and append the `/v1/keys` suffix, e.g. `https://tyk-testing.okta.com/oauth2/default/v1/keys`. + + + +For the **Identity Source** field use `"sub"` and for **Policy Field Name** use `"pol"`. + +1. Click **Save** +2. Select **Policies** under **System Management** +3. Click **Create a Policy** and call it **OKTA Policy**. Use the default values for this policy. +4. In the **Access rights** section, select your previously created **OKTA API**. You will also need to enter an expiration setting for your keys. + +After the policy is created, switch back to the API settings and make sure that the API is using your **OKTA Policy** policy: + +Step 7 + +Now you're ready to add this API to the Developer Portal. +1. Click **Catalog** under **Portal Management** on the navigation menu. +2. Click **Add New API**, enter a name for it and select the newly created policy. Again, you will use **OKTA API**: + +Step 8 + +1. Click **Save** then open the API added again +2. Open the **Settings** tab. +3. In **API Details** select the **Override global settings** option. + + + + + Tyk lets you set global portal settings that apply to **all portal-listed APIs**, in this guide we assume you’re enabling and setting up DCR for a single API. In case you want to enable DCR for all the APIs, you should go to the **Settings** section under **Portal Management**, and in the **API Access** tab you can enter your DCR settings there. + + + +4. Scroll down to the DCR section and enter the following settings: + + +Okta Grant Types + + +**Providers:** Different providers might implement the standard in slightly different ways, Tyk provides a specific driver for each one. For IDPs that aren’t on the list use the "Other" option. For this guide, pick "OKTA". + +**Grant Types:** The grant types that will be used by the client. See the [specification](https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2) for more details. You need to enter the following grant types: + * Client Credentials + * Implicit + * Authorization Code + +**Token Endpoint Auth Method:** defines the way the client will authenticate against the token endpoint. Use "Client Secret - Post". + +**Response Types:** OAuth 2.0 response types that will be used by the client. Set **Token**. + +**Identity Provider Host:** Base IDP URL, e.g. `https://tyk-testing.okta.com/` + +**Client Registration Endpoint:** OpenID Connect client registration endpoint. The value we use is `https://tyk-testing.okta.com/oauth2/v1/clients` + +This value is found in your well-known discovery document as `registration_endpoint`. The well-known location URL is typically `https://tyk-testing.okta.com/.well-known/openid-configuration` (replace "tyk-testing" with your org.). + +**Initial Registration Access Token:** the token that’s used to register new clients, this was generated in the early steps of the guide. + + + +A note on grant types and response types in OKTA + +It’s important to note that OKTA’s DCR endpoint supports a parameter called `"application_type"`, the application types aren’t standard across all IDPs, while the initial specification mentions `"native"` or `"web"` types, some IDPs implement their own. In the current implementation Tyk supports the usage of the `"web"` application type which is necessary in supporting the client credentials flow that’s described in this guide, as well as others, this is set automatically when OKTA is set as the provider. Currently, the ability to change the application type is available with the Enterprise Developer Portal. + + + +### Testing the flow + +Now that both Tyk and OKTA are ready we can test the complete flow. + +1. Click **Developers** under **Portal Management** +2. Click on **Add developer** and create a developer user. + +After the developer is created, open your Developer Portal, click on the **OAuth Clients** navigation bar button and follow the wizard: + +Step 10 + +Click **Create first OAuth Client**. You’ll see your previously created **OKTA API**, select it and click **Save and continue**. The following screen will require you to enter a client name. It’s also possible to set redirect URLs if you also plan to use this client for other flow types. This setting can be left blank for the purposes of this guide. + +Step 11 + +Once you click **Create**, Tyk will trigger a registration on your IDP and the details of your client will be displayed: + +Step 12 + +If you check the OKTA dashboard you will see this client too: + +Step 13 + +The next step is to generate a token and use it for accessing our **OKTA API**. We'll use Postman for this. You will need your token URL which is also the well-known URL for your organization. +For this guide you'll use `https://[org].okta.com/oauth2/default/v1/token` + +Your Postman request should contain the following body, where `"client_id"` and `"client_secret"` are the credentials you got from the developer portal: + +Step 14 + +Note that we aren’t using any additional header for this request, the client credentials are enough. We’re also passing our previously created `"tyk"` scope as value. + +Once we get a response from the IDP, we can copy the `"access_token"` and use it to access our **OKTA API**, this request will be proxied by Tyk: + +Step 15 diff --git a/tyk-developer-portal/tyk-portal-classic/portal-concepts.mdx b/tyk-developer-portal/tyk-portal-classic/portal-concepts.mdx new file mode 100644 index 0000000000..2b5e97f0f1 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/portal-concepts.mdx @@ -0,0 +1,94 @@ +--- +title: "Portal Concepts" +order: 1 +robots: "noindex, nofollow" +sidebarTitle: "Portal Concepts" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +## API Catalog + +The API Catalog is a list of APIs that you have published to your portal. + +The API Catalog entry is not a one-to-one map between an API you manage in Tyk, since you might want to compose multiple managed services into a single public-facing API Facade, a catalog entry is actually an entry that maps against a security policy. + +From the API Catalog, a user can either: + +- View the documentation for the API +- Request for a token to the API + +When a developer requests a token, a new Auth token is generated on the linked policy, instead of the actual API, since you may wish to publish multi-tier access to the same API (E.g. Bronze / Silver / Gold). + +## Key Requests + +A key request is a record that is generated when a developer requests an access token for an API published in the API Catalog. The Key request encompasses the following information: + +Read more about them in the [Key Request section](/tyk-developer-portal/tyk-portal-classic/key-requests) + +### Multiple APIs for a single Key Request + +New for v1.9, a developer can now request access to multiple APIs with a single key request. The APIs you group together via a single key should all be of the same authentication type. + +Multiple APIs per Key Request + +To enable this functionality, select **Enable subscribing to multiple APIs with a single key** from the Portal Management Settings. + +Multiple APIs + +### Edit APIs associated with a single Key Request + +New for v1.9.4, if you have **Enable subscribing to multiple APIs with a single key** selected you can edit the APIs associated with the Key. You can perform the following: + +* Remove access to existing APIs +* Subscribe to new APIs (of the same authentication type as the existing ones). + + Edit APIs + + +If a new API requires key approval, the new key request will be generated, and access to this API will be granted after your admin approves it. + + +## Policies + +In the context of the developer portal, a security policy is the main "element" being exposed to public access. The policy is the same as a standard policy, and the policy forms the baseline template that gets used when the portal generates a token for the developer. + +Security policies are used instead of a one-to-one mapping because they encapsulate all the information needed for a public API program: + +1. Rate limits +2. Quota +3. Access Lists (What APIs and which versions are permitted) +4. Granular access (Which methods and paths are allowed, e.g. you may want to only expose read-only access to the portal, so only GET requests are allowed) +5. Multi-policy-management (With a Key, you can assign more than one policy to an APIs and each policy will have it's own counter). + +Within the developer portal admin area, under a developer record, you will see their subscriptions. Those subscriptions represent the tokens they have and their policy level access. It is possible to then "upgrade" or "downgrade" a developers access without actually managing their token, but just assigning a new policy to that token. + +## Documentation + +Within the portal, documentation is what a developer can use to learn how to access and use your APIs. + +The developer portal supports two types of documentation, and will render them differently: + +1. API Blueprint - this is rendered to HTML templates using Jade and Aglio. +2. Swagger/OpenAPI (OpenAPI 2.0 and 3.0 are supported) - either by pasting your Swagger JSON or YAML content into the code editor, or by linking to any public facing Swagger URL. The URL version can be rendered using [Swagger UI](https://swagger.io/tools/swagger-ui/) which offers a sandbox environment where developers can interact with your API from the browser. + + + + + Support for API Blueprint is being deprecated. See [Importing APIs](/api-management/gateway-config-managing-classic#api-blueprint-is-being-deprecated) for more details. + + + +Within an API Catalog entry, documentation must be attached to the catalog entry for it to be published. + +## Developers + +Within the developer portal, a developer is an end-user that has access to the developer portal section of the portal website. This user is completely separate from Tyk Dashboard users and they do not ever intersect (they are also stored separately). + +A developer record consists of some basic sign-up information and a set of admin-definable fields that get attached to the developer as metadata. + +Within the developer view of the Tyk Dashboard, it is possible to manage all access of a developer, including the access levels of their tokens. + + diff --git a/tyk-developer-portal/tyk-portal-classic/portal-events-notifications.mdx b/tyk-developer-portal/tyk-portal-classic/portal-events-notifications.mdx new file mode 100644 index 0000000000..4992c36a1e --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/portal-events-notifications.mdx @@ -0,0 +1,111 @@ +--- +title: "Portal events and notifications" +order: 9 +robots: "noindex, nofollow" +sidebarTitle: "Events and Notifications" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +Tyk enables you to actively monitor both user and organization quotas. These active notifications are managed in the same way as webhooks and provides an easy way to notify your stakeholders, your own organization or the API end user when certain thresholds have been reached for their token. + +### Tyk Cloud Users + +Monitors are disabled by default in Tyk Cloud. Portal events are enabled and can be defined by raising a support ticket. + +### How to Enable Monitors + +See [Monitors](/api-management/gateway-events#monitoring-quota-consumption) for details of how to configure quota consumption monitors. + +### Portal Events + +The Tyk Dashboard and the Portal now support email notifications powered by Mandrill, Sendgrid, Mailgun and Amazon SES. + +#### How Email Notifications Work + +If you have enabled email notifications, the Portal will attempt to send notifications regarding a user's sign-up status or key request status to their username email address. These templates can be found in the `portal/email_templates` folder. + +The templates are available as text based or HTML. See the standard included ones to see the various template fields that can be customized. + +### Extra Dashboard And Portal Events + +The Dashboard and Portal also support a certain level of events that you can use to notify your system of various things that have happened in the Portal. + +To configure them, add an `event_options` section to an Organization when you are creating them. See [Creating an Organization via the Dashboard Admin API](https://tyk.io/docs/api-reference/organisations/create-an-organisation) for more details. + +Within this object, you can then register webhooks or/and an email address to notify when an event occurs: + +```{.copyWrapper} +event_options: { + api_event: { + webhook: "http://posttestserver.com/post.php?dir=tyk-events", + email: "test@test.com" + }, + key_event: { + webhook: "http://posttestserver.com/post.php?dir=tyk-key-events", + email: "test@test.com" + }, + key_request_event: { + webhook: "http://posttestserver.com/post.php?dir=tyk-key-events", + email: "test@test.com" + } +} +``` + +The following events are supported: + +* `api_event`: When an API is created, updated or deleted. + +* `key_event`: When a key is created, updated or deleted. + +* `key_request_event`: When a Portal key request is created or updated. + +Sample **Webhook** Payload for a **Key Request** Event: +```{.json} +{ + "event": "key_request_event.submitted", + "data": { + "id": "5e543dd0f56e1a4affdd7acd", + "org_id": "5e2743567c1f8800018bdf35", + "for_plan": "5e2744897c1f8800018bdf3b", + "apply_policies": [ + "5e2744897c1f8800018bdf3b" + ], + "by_user": "5e430ef68131890001b83d2e", + "approved": false, + "date_created": "2020-02-24T16:19:12.175113-05:00", + "portal_developer": { + "id": "5e430ef68131890001b83d2e", + "email": "dev@dev.ca", + "date_created": "2020-02-11T15:30:46.003-05:00", + "inactive": false, + "org_id": "5e2743567c1f8800018bdf35", + "keys": { + "6dc2dfc0": [ + "5e431f938131890001b83d30" + ] + }, + "subscriptions": { + "5e431f938131890001b83d30": "6dc2dfc0" + }, + "last_login_date": "2020-02-11T16:43:39.858-05:00" + }, + "catalogue_entry": { + "name":"frontend APIs", + "short_description":"", + "long_description":"", + "show":true, + "api_id":"", + "policy_id":"5e2744897c1f8800018bdf3b", + "documentation":"5e3b477a7c1f8800013603c6", + "version":"v2", + "is_keyless":false, + "config":{ + + } + } + } +} +``` \ No newline at end of file diff --git a/tyk-developer-portal/tyk-portal-classic/portal-oauth-clients.mdx b/tyk-developer-portal/tyk-portal-classic/portal-oauth-clients.mdx new file mode 100644 index 0000000000..4eaa301954 --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/portal-oauth-clients.mdx @@ -0,0 +1,54 @@ +--- +title: "Portal OAuth Clients" +order: 10 +robots: "noindex, nofollow" +sidebarTitle: "Portal OAuth Clients" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +From Tyk Dashboard v1.8, you can now create and manage OAuth clients from the Developer Portal. + +## Prerequisites + +1. An API created in your Dashboard using Tyk's ability to act as a OAuth provider. You need to have [OAuth 2.0](/api-management/authentication/oauth-2) selected as the Authentication mode. See [Create an API](/api-management/gateway-config-managing-classic#create-an-api) for more details. +2. A Policy created in your Dashboard with the API created above selected in the **Access Rights > Add access rule** drop-down. See [Create a Security Policy](/api-management/gateway-config-managing-classic#secure-an-api) for more details. +3. A Portal Catalog entry for the API created above with the Policy you created selected from the **Available policies** drop-down. See [Create a Portal Entry](/getting-started/tutorials/publish-api) for more details. +4. A developer account created in your Developer Portal. + +## Create the OAuth Client from the Portal + +1. Login to your Portal: + +Developer Portal Home Screen + +2. Select **OAuth Clients** from the top menu +3. If this is the first OAuth Client you are creating, the screen will be as below: + +Developer OAuth Home Screen + +4. Click **Create first OAuth Client** +5. Hover over the API you added to the Catalog with OAuth Authentication mode from the drop-down list: + +Select API Screen + +6. Click **Select API** +7. Then click **Save and continue**: + +Save + +8. You can now add details about your application, and set the redirect URL to the application. If you want to use this client for more than one application, you can add other redirect URLs as necessary. +9. Click **Create** + +Create + +10. You need to copy and save the displayed Client Secret, as you will not be able to view it from the Portal again. The secret is stored on the Dashboard and are listed for each developer under the **Portal Management > Developers** menu. + +secret + + +## Revoke OAuth Client Tokens + +See [Revoke OAuth Tokens](/api-management/authentication/oauth-2#revoking-access-tokens) for more details. \ No newline at end of file diff --git a/tyk-developer-portal/tyk-portal-classic/tyk-portal-classic/customise/customise-with-templates.mdx b/tyk-developer-portal/tyk-portal-classic/tyk-portal-classic/customise/customise-with-templates.mdx new file mode 100644 index 0000000000..4e7478de3f --- /dev/null +++ b/tyk-developer-portal/tyk-portal-classic/tyk-portal-classic/customise/customise-with-templates.mdx @@ -0,0 +1,106 @@ +--- +title: "Customize Page Templates" +order: 2 +robots: "noindex, nofollow" +sidebarTitle: "Customise Page Templates" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + +The Tyk Developer Portal can be fully customized using templates. The templates for the Portal are only available to Self-Managed users currently. These templates are located in the `/opt/tyk-dashboard/portal` folder of your Tyk installation. + +All templates are based on Twitter Bootstrap and are standard HTML with some Golang Template snippets to handle dynamic content rendering. + + + +The Portal process (`tyk-analytics`) must be restarted for template changes to take effect. This is because the application caches templates on startup. + + + + +### Adding new templates + +The Tyk content editor enables you to specify a template name to use when rendering templates. two are provided by default: + +* Default Home Page Template +* Default Page Template + +The third option is "Custom" and this allows you to enter a template name into the field editor that will set the template name to use on render. + +To set a new template name up in your Tyk installation, you will need to add the file to the `portal` folder and ensure it starts and ends with the templates directive: + +``` +{{ define "customPage" }} + Provider1[Tyk Dashboard] + Agent --> Provider2[AWS API Gateway] + Agent --> Provider3[Other Providers] + end + + subgraph "Governance Hub" + Hub[Governance Service] --- APIRepo[API Repository] + end + + Agent <-->|gRPC Streams| Hub +``` + +The agent establishes two persistent gRPC streams with the Governance Hub: + +1. **Health Stream**: Sends regular heartbeats to indicate the agent is alive and functioning +2. **Sync Stream**: Used for API synchronization operations + +When multiple agent replicas are deployed with leader election enabled, they use Kubernetes leader election to ensure only one instance actively performs synchronization, while others stand by as hot backups. + +### Synchronization Process + +Synchronization can be triggered in three ways: + +1. **Manual Trigger**: Through the Governance Hub UI or API + + + +2. **Scheduled Sync**: At regular intervals configured in the agent. See [Understanding Scheduled Synchronization](#understanding-scheduled-synchronization). + +3. **Initial Connection**: When an agent first connects to the Governance Hub + +#### Understanding Scheduled Synchronization + +You can configure scheduled synchronization for each API provider using the **Governance Hub API (Hub-side scheduling)**. + +Use the `/api/agents/{id}/sync-jobs` endpoint to configure provider-specific schedules: + +```sh +# Schedule sync for a specific provider +curl -X POST "${GOVERNANCE_URL}/api/agents/${AGENT_ID}/sync-jobs" \ + -H "X-API-Key: ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "repeat_every": "12h", + "provider": "tyk-provider", + "start_from": "2023-06-01T12:00:00Z" + }' +``` + +With this approach: +- The Governance Hub's scheduler component manages the schedule +- The Hub initiates synchronization by sending requests to the agent +- It's a "push" model where the Hub tells the agent when to sync +- You can set different schedules for different providers +- Supports Go duration format (e.g., "1h", "12h", "7d") + +During synchronization: + +1. The agent receives a sync request from the hub (or initiates it based on its schedule) +2. The agent queries each configured API provider for APIs +3. The agent processes and normalizes the API definitions +4. The agent streams the API definitions to the hub +5. The hub processes and stores the API definitions +6. The hub reconciles the API inventory, marking missing APIs as deleted + +### Deployment Scenarios + +#### Multi-Provider API Discovery + +Deploy agents to connect to different API providers across your organization, creating a comprehensive API inventory that spans platforms. + +```yaml +# Agent configuration with multiple providers +instances: + - name: "tyk-dashboard" + type: "tyk" + config: + host: "http://tyk-dashboard:3000" + auth: "your-tyk-api-key" + + - name: "aws-us-east" + type: "aws" + config: + accessKeyId: "your-aws-access-key" + accessKeySecret: "your-aws-secret-key" + region: "us-east-1" + + - name: "aws-eu-west" + type: "aws" + config: + accessKeyId: "your-aws-access-key" + accessKeySecret: "your-aws-secret-key" + region: "eu-west-1" +``` + +#### High Availability Agent Deployment + +Deploy multiple agent replicas in Kubernetes to ensure continuous API discovery even if some instances fail. + +```yaml +# Kubernetes deployment with leader election +apiVersion: apps/v1 +kind: Deployment +metadata: + name: governance-agent +spec: + replicas: 3 # Multiple replicas for redundancy + template: + spec: + containers: + - name: agent + env: + - name: POD_NAME # When leader election is enabled, POD_NAME environment variables must be set + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE # When leader election is enabled, POD_NAMESPACE environment variables must be set + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: TYK_AGENT_LEADERELECTION_ENABLED + value: "true" +``` + +## Configuration Options + +### Agent Configuration File + +The agent is configured using a YAML configuration file with the following sections: + +#### Core Configuration + +```yaml +# Governance Dashboard Connection +governanceDashboard: + server: + url: "your-governance-instance.tyk.io:50051" + auth: + token: "your-agent-token" + +# Log level (debug, info, warn, error) +logLevel: info + +# Health probe configuration +healthProbe: + server: + port: 5959 +``` + +#### API Provider Configuration + +```yaml +# API Provider Configurations +instances: + # Tyk Provider + - name: "tyk-provider" + type: "tyk" + config: + host: "http://dashboard:3000" + auth: "your-tyk-api-key" + + # AWS API Gateway Provider + - name: "aws-provider" + type: "aws" + config: + accessKeyId: "your-aws-access-key-id" + accessKeySecret: "your-aws-access-key-secret" + region: "us-east-1" + # Optional session token for temporary credentials + sessionToken: "your-aws-session-token" +``` + +#### gRPC Connection (New in v0.2) + +```yaml +rpc: + # Keepalive configures the keepalive settings for the gRPC connection. + keepalive: + # Enabled controls whether keepalive is enabled. + enabled: true + # Time is the duration after which if there are no activities, ping will be sent. + time: 30s + # Timeout is the duration the client waits for a response to a keepalive ping. + timeout: 20s + # PermitWithoutStream if true allows sending pings even without active streams. + permitWithoutStream: true +``` + +#### High Availability Configuration (New in v0.2) + +```yaml +leaderElection: + # Enable or disable leader election + enabled: true + # Name of the Kubernetes lease object used for leader election + leaseName: "governance-agent-lock" + # Namespace where the lease object will be created + # If not specified, the agent's namespace will be used + leaseNamespace: "" + # Duration that non-leader candidates will wait before attempting to acquire leadership + leaseDuration: "15s" + # Duration that the acting leader will retry refreshing leadership before giving up + renewDeadline: "10s" + # Duration the leader elector clients should wait between leadership acquisition attempts + retryPeriod: "2s" +``` + +### Environment Variables + +The agent supports configuration through environment variables: + +| Environment Variable | Description | Default Value | +| :--------------------- | :------------- | :--------------- | +| `TYK_AGENT_LICENSEKEY` | Your Tyk Governance license key | - | +| `TYK_AGENT_LOGLEVEL` | Log level (debug, info, warn, error) | `info` | +| `TYK_AGENT_GOVERNANCEDASHBOARD_SERVER_URL` | The gRPC endpoint URL of the Tyk Governance service | - | +| `TYK_AGENT_GOVERNANCEDASHBOARD_SERVER_TLS_ENABLED` | Enable TLS for gRPC connections | `false` | +| `TYK_AGENT_GOVERNANCEDASHBOARD_SERVER_TLS_CACERTPATH` | Path to CA certificate | - | +| `TYK_AGENT_GOVERNANCEDASHBOARD_SERVER_TLS_CLIENTCERTPATH` | Path to client certificate (for mTLS) | - | +| `TYK_AGENT_GOVERNANCEDASHBOARD_SERVER_TLS_CLIENTKEYPATH` | Path to client key (for mTLS) | - | +| `TYK_AGENT_GOVERNANCEDASHBOARD_SERVER_TLS_INSECURESKIPVERIFY` | Skip verification of server certificate | `false` | +| `TYK_AGENT_GOVERNANCEDASHBOARD_AUTH_TOKEN` | Authentication token for the agent | - | +| `TYK_AGENT_HEALTHPROBE_SERVER_PORT` | Port for health probe server | `5959` | + +### gRPC Connection Variables (New in v0.2) + +| Environment Variable | Description | Default Value | +| :--------------------- | :------------- | :--------------- | +|`TYK_AGENT_RPC_KEEPALIVE_ENABLED`|Enables/disables keepalive|`true`| +|`TYK_AGENT_RPC_KEEPALIVE_TIME`|Duration after which ping is sent|`30s`| +|`TYK_AGENT_RPC_KEEPALIVE_TIMEOUT`|Duration client waits for ping response from the server|`20s`| +|`TYK_AGENT_RPC_KEEPALIVE_PERMITWITHOUTSTREAM`|Allows sending pings without active streams|`true`| + +### Leader Election Variables (New in v0.2) + +| Environment Variable | Description | Default Value | +| :--------------------- | :------------- | :--------------- | +|`TYK_AGENT_LEADERELECTION_ENABLED`|Enable Kubernetes leader election|`false`| +|`TYK_AGENT_LEADERELECTION_LEASENAME`|Name of the lease object|`governance-agent-lock`| +|`TYK_AGENT_LEADERELECTION_LEASENAMESPACE`|Namespace for the lease object|Agent's namespace| +|`TYK_AGENT_LEADERELECTION_LEASEDURATION`|Duration for lease|`15s`| +|`TYK_AGENT_LEADERELECTION_RENEWDEADLINE`|Deadline for renewing leadership|`10s`| +|`TYK_AGENT_LEADERELECTION_RETRYPERIOD`|Period between retry attempts|`2s`| + +#### Required Environment Variables for Leader Election + +When leader election is enabled, the following environment variables must also be set: + +- `POD_NAME`: The name of the pod (used as the identity for leader election) +- `POD_NAMESPACE`: The namespace of the pod (used for creating the lease object) + +These are typically set automatically when deploying with Kubernetes using the downward API: + +```yaml +env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +``` + +## Use Cases + +### Multi-Region API Discovery + +For organizations with APIs deployed across multiple geographic regions, deploy region-specific agents to efficiently discover and monitor APIs while respecting network boundaries. + +**Implementation:** + +1. Deploy agents in each region where APIs are hosted +2. Configure each agent with the appropriate regional API providers +3. Use descriptive names to identify regional agents +4. Monitor all agents from the central Governance Hub + +**Benefits:** + +- Reduced latency for API discovery operations +- Respect for network boundaries and security zones +- Improved reliability with region-specific agents +- Clear organization of APIs by region + +### Secure Environment Bridging + +For organizations with strict network segmentation, use agents to securely bridge between isolated environments without compromising security boundaries. + +**Implementation:** + +1. Deploy agents in each network segment +2. Configure outbound-only connections from agents to the Governance Hub +3. Use separate agents for production and non-production environments +4. Implement appropriate network security controls around agent traffic + +**Benefits:** + +- Maintain network isolation while enabling governance +- No inbound connections required to sensitive environments +- Granular control over which APIs are discovered +- Separation of concerns between environments + +### Automated API Lifecycle Tracking + +Use scheduled synchronization to automatically track the lifecycle of APIs, including when they're created, updated, or deleted from source providers. + +**Implementation:** + +1. Configure agents with scheduled synchronization +2. Set appropriate sync intervals based on change frequency +3. Use the API Repository to monitor API status +4. Create reports showing API lifecycle events + +**Benefits:** + +- Automatic detection of API changes +- Historical record of API lifecycle events +- Reduced manual tracking effort +- Improved visibility into API landscape evolution + +## Best Practices and Recommendations + +- **Use descriptive agent names** that indicate their purpose and scope +- **Deploy agents close to API providers** to minimize latency and network issues +- **Configure appropriate sync intervals** based on how frequently your APIs change +- **Use leader election for high availability** when deploying multiple agent replicas +- **Monitor agent health regularly** to ensure continuous API discovery +- **Rotate API provider credentials periodically** for better security +- **Use the principle of least privilege** when configuring API provider credentials +- **Start with manual syncs** before enabling scheduled synchronization +- **Implement network security controls** around agent traffic +- **Maintain agent versions** to ensure compatibility with the Governance Hub + +## FAQs + + + +The number of agents depends on your API landscape. Generally, you should consider deploying separate agents for: + +- Different network segments or security zones +- Different geographic regions +- Different environments (production vs. non-production) +- Different API provider types with many APIs + +A single agent can connect to multiple API providers of different types, so you don't necessarily need one agent per provider. + + + +If an agent goes offline, it will be marked as "INACTIVE" in the Governance Hub after missing several heartbeats. APIs discovered by that agent will remain in the repository but won't be updated until the agent reconnects or another agent is configured to find out the same APIs. + +When using high availability with leader election, if the leader agent goes offline, another replica will automatically take over the leadership role and continue synchronization operations. + + + +All communication between agents and the Governance Hub is secured using: + +- TLS encryption for all traffic +- JWT token-based authentication +- Regular token validation +- Bidirectional stream validation + +The agent only requires outbound connectivity to the Governance Hub, with no inbound connections required. + + + +Agents require read-only access to API configurations: + +- For Tyk Dashboard: An API key with read access to APIs and policies +- For AWS API Gateway: IAM credentials with permissions to list and get API Gateway resources +- For other providers: Similar read-only access to API configurations + +The agent never requires write access to API providers. + + + +## Troubleshooting + + + +- Check if the agent process is running +- Verify network connectivity to the Governance Hub +- Ensure the agent token is valid and not expired +- Check agent logs for connection errors +- Verify the Governance Hub URL is correct +- Ensure the agent has outbound access to the hub's gRPC port. It is usually 50051 for self-managed instances. For Tyk Cloud managed instance, it is proxied through port 443. + + + +- Verify API provider credentials are correct +- Check agent logs for provider connection errors +- Ensure the provider has APIs configured +- Try triggering a manual sync operation +- Check agent configuration for correct provider URLs +- Verify the agent has network access to the API providers + + + +- Verify leader election is properly configured +- Check if agents are in the same Kubernetes namespace +- Ensure agents are using the same lock name +- Check Kubernetes permissions for leader election +- Verify Kubernetes API access from agent pods +- Check agent logs for leader election messages + + + +- Ensure the agent has been running long enough for a scheduled sync +- Check agent logs for scheduled sync messages +- Try restarting the agent to reset the schedule +- Verify the agent is the leader if using leader election + + + diff --git a/tyk-governance/api-evaluation.mdx b/tyk-governance/api-evaluation.mdx new file mode 100644 index 0000000000..8b4c8ec136 --- /dev/null +++ b/tyk-governance/api-evaluation.mdx @@ -0,0 +1,343 @@ +--- +title: "API Evaluation" +description: "Validate API specifications against governance policies before deployment to catch compliance issues early in the development lifecycle and reduce rework." +keywords: "Tyk Governance, API Evaluation, Rulesets, API Validation, Shift-Left Governance" +sidebarTitle: "API Evaluation" +--- + +## Availability + +- Version: Available since v0.2 + +## Overview + +API Evaluation enables you to validate API specifications against governance policies before deployment, without requiring the API to be published or stored in your API Repository. This feature helps you catch compliance issues early in the development lifecycle, reducing rework and accelerating the delivery of high-quality APIs. + +### Key Benefits + +- **Shift-Left Governance**: Catch compliance issues during design and development, not after deployment +- **Reduce Development Cycles**: Identify and fix issues before they reach code review or testing phases +- **Seamless Integration**: Easily incorporate governance checks into CI/CD pipelines and development workflows +- **Detailed Feedback**: Receive precise information about violations with line numbers and remediation guidance +- **No Storage Required**: Validate API specifications without storing them in your API Repository + +### Dependencies + +- Requires Tyk Governance v0.2 or higher +- Requires at least one governance ruleset to be defined + +## Quick Start + +In this tutorial, we'll validate an API specification against a governance ruleset before deployment. + +### Prerequisites + +- Access to Tyk Governance Hub +- A governance ruleset ID +- An API specification to validate (in OpenAPI format) + +### Step-by-Step + +1. **Identify Your Ruleset** + + Navigate to the Rulesets section in your Tyk Governance dashboard and note the ID of the ruleset you want to use for validation. + +2. **Prepare Your API Specification** + + Ensure your API specification is in a valid OpenAPI format (JSON or YAML). + +3. **Make an API Request** + + Use the API Evaluation endpoint to validate your specification: + + ```sh + curl -X POST https://your-governance-instance.tyk.io/api/rulesets/evaluate-spec \ + -H "Content-Type: application/json" \ + -H "X-API-Key: YOUR_API_KEY" \ + -d '{ + "rulesetId": "$RULESET_ID", + "apiSpec": { + "name": "My Test API", + "content": { + "openapi": "3.0.0", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "paths": { + "/example": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + } + } + } + } + }' + ``` + +4. **Review the Results** + + The response will include any violations found, with details about each issue: + + ```json + { + "status": "Success", + "message": "Rule violation found", + "errors": [ + { + "code": "info-contact", + "path": ["info"], + "message": "API must have contact information", + "severity": "error", + "range": { + "start": { "line": 3, "character": 2 }, + "end": { "line": 6, "character": 3 } + }, + "howToFix": "Add contact information to the info section" + } + ] + } + ``` + + If there are no violations found: + + ```json + { + "status" : "Success", + "message" : "No rule violation found", + "errors":[] + } + ``` + +### Validation + +- A successful request with no violations will return an empty errors array +- If violations are found, each will include: + - The rule code that was violated + - The path in the API specification where the violation occurred + - A message explaining the issue + - The severity level (error, warning, info, hint) + - The exact location in the file (line and character) + - Guidance on how to fix the issue (if available) + +## How It Works + +API Evaluation works by sending your API specification to the Tyk Governance Hub, where it's validated against a specified ruleset without being stored in your API Repository. The system applies each rule in the ruleset to your specification and returns detailed results. + +### Integration into Development Workflow + +#### Integrating with CI/CD Pipelines + +API Evaluation can be integrated into your CI/CD pipeline to validate API specifications before they're deployed automatically. This ensures that only compliant APIs make it to production. + +```yaml +# Example GitHub Actions workflow +name: API Governance Check + +on: + pull_request: + paths: + - 'api-specs/**' + +jobs: + validate-api: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Validate API Specification + run: | + SPEC_CONTENT=$(cat api-specs/my-api.yaml | awk '{printf "%s\\n", $0}') + curl -X POST https://your-governance-instance.tyk.io/api/rulesets/evaluate-spec \ + -H "Content-Type: application/json" \ + -H "X-API-Key: ${{ secrets.GOVERNANCE_API_KEY }}" \ + -d "{ + \"rulesetId\": \"your-ruleset-id\", + \"apiSpec\": { + \"name\": \"My API\", + \"content\": \"$SPEC_CONTENT\" + } + }" > validation-results.json + + # Fail if any errors are found + ERROR_COUNT=$(jq '.errors | length' validation-results.json) + if [ $ERROR_COUNT -gt 0 ]; then + echo "API validation failed with $ERROR_COUNT issues:" + jq '.errors' validation-results.json + exit 1 + fi + +``` + +#### Pre-commit Validation + +Developers can validate their API specifications before committing changes, ensuring they meet governance standards from the start. + +```bash +#!/bin/bash +# pre-commit hook for API validation + +# Get the API specification file +SPEC_FILE=$(git diff --cached --name-only | grep -E '\.json$|\.yaml$|\.yml$' | head -1) + +if [ -n "$SPEC_FILE" ]; then + echo "Validating API specification: $SPEC_FILE" + + # Convert the file content to JSON + if [[ $SPEC_FILE == *.yaml || $SPEC_FILE == *.yml ]]; then + SPEC_CONTENT=$(yq eval -o=json $SPEC_FILE) + else + SPEC_CONTENT=$(cat $SPEC_FILE) + fi + + # Validate the specification + RESPONSE=$(curl -s -X POST https://your-governance-instance.tyk.io/api/rulesets/evaluate-spec \ + -H "Content-Type: application/json" \ + -H "X-API-Key: YOUR_API_KEY" \ + -d "{ + \"rulesetId\": \"your-ruleset-id\", + \"apiSpec\": { + \"name\": \"$(basename $SPEC_FILE)\", + \"content\": $SPEC_CONTENT + } + }") + + # Check for errors + ERROR_COUNT=$(echo $RESPONSE | jq '.errors | length') + if [ $ERROR_COUNT -gt 0 ]; then + echo "API validation failed with $ERROR_COUNT issues:" + echo $RESPONSE | jq '.errors' + exit 1 + fi + + echo "API specification is valid!" +fi + +exit 0 +``` + +## Use Cases + +### Validating APIs During Design Phase + +Integrate API Evaluation with design tools to validate specifications during the design phase, before any code is written. + +**Benefits**: + +- Catch issues at the earliest possible stage +- Reduce rework and development cycles +- Ensure designs align with governance standards from the start + +**Implementation**: + +1. Design an API in your preferred tool +2. Export the OpenAPI specification +3. Validate using the API Evaluation endpoint +4. Review and address any issues +5. Repeat until the specification passes validation + +### Automated Testing in Development Workflows + +Incorporate API Evaluation into automated testing workflows to ensure continuous compliance during development. + +**Benefits**: + +- Maintain compliance throughout the development process +- Prevent regression of governance standards +- Provide immediate feedback to developers + +**Implementation**: + +1. Add API validation as a step in your testing pipeline +2. Run validation after any changes to the API specification +3. Fail the build if critical violations are found +4. Generate reports of issues for developers to address + +### Pre-release Validation Gate + +Use API Evaluation as a final check before releasing APIs to production or external consumers. + +**Benefits**: + +- Ensure only compliant APIs are released +- Maintain consistent quality standards +- Reduce security and compliance risks + +**Implementation**: + +1. Add a validation step in your release pipeline +2. Block releases with critical violations +3. Generate compliance reports for audit purposes +4. Track compliance metrics over time + +## Best Practices and Recommendations + +- **Integrate early in development**: Validate specifications before coding begins to avoid costly rework +- **Use appropriate rulesets**: Select rulesets that match the API's purpose and criticality +- **Automate validation**: Incorporate validation into CI/CD pipelines and development workflows +- **Review results carefully**: Understand the context of each violation before fixing +- **Prioritize by severity**: Address errors first, then warnings, then informational issues +- **Track compliance trends**: Monitor how compliance improves over time +- **Update specifications incrementally**: Fix critical issues first, then address less severe ones +- **Document exceptions**: When a rule violation is intentional, document the reason +- **Provide feedback on rules**: Help improve governance rules that generate false positives +- **Use with other governance tools**: Combine with API Repository and Ruleset Management for comprehensive governance + +## FAQs + + + +The `/rulesets/evaluate-spec` endpoint is designed for validating a single API specification. For batch validation of multiple specifications, you can make multiple requests or use the `/rulesets/evaluate` endpoint if the APIs are already in your API Repository. + + + +Currently, the API Evaluation feature supports OpenAPI 3.x specifications in both JSON and YAML formats. Support for additional formats is planned for future releases. + + + +Yes, there's a 10MB limit on the size of API specifications that can be evaluated. For very large specifications, we recommend breaking them into smaller, more manageable components. + + + +No, specifications submitted through the API Evaluation endpoint are not stored in your API Repository. They are processed in memory and then discarded, making this feature suitable for validating sensitive or in-development APIs. + + + +## Troubleshooting + + + +- Ensure your request body follows the correct JSON format +- Verify that the `content` field contains a valid OpenAPI specification + +- Check for JSON syntax errors in your request +- Make sure the `rulesetId` is valid and exists in your Governance Hub + + + +- Reduce the size and complexity of your API specification +- Ensure your ruleset doesn't contain overly complex rules +- Break large specifications into smaller components +- Check network connectivity between your client and the Governance Hub + + + +- Review the ruleset being used for evaluation +- Check the specific rule that's generating the violation +- Verify your API specification against the OpenAPI specification +- Consider if the rule needs adjustment for your specific use case + + + +- Verify your API key is valid and has not expired +- Ensure you're including the API key in the correct header ( `X-API-Key` ) +- Check that your user account has permission to access the API Evaluation feature +- Verify you're using the correct Governance Hub URL + + + diff --git a/tyk-governance/api-labeling.mdx b/tyk-governance/api-labeling.mdx new file mode 100644 index 0000000000..f53c816d85 --- /dev/null +++ b/tyk-governance/api-labeling.mdx @@ -0,0 +1,178 @@ +--- +title: "API Labeling and Categorization" +description: "Organize, classify, and filter your APIs using customizable metadata tags to create a structured taxonomy for your API landscape." +keywords: "Tyk Governance, API Labeling, API Categorization, API Metadata, API Organization" +sidebarTitle: "API Labeling and Categorization" +--- + +## Availability + +- Version: Available since v0.1 + +## Overview + +API Labeling and Categorization enables you to organize, classify, and filter your APIs using customizable metadata tags. This feature allows you to create a structured taxonomy for your API landscape, making it easier to search, filter, and apply governance policies based on business context, technical characteristics, or organizational ownership. + +### Key Benefits + +- Enables structured organization of APIs by business domain, criticality, and other dimensions +- Facilitates efficient search and filtering of APIs in large inventories +- Provides consistent metadata across APIs from different sources +- Supports governance policy application based on API characteristics (Governance Policy feature is coming soon) +- Enables reporting and analytics based on business context (Reporting feature is coming soon) + +## Quick Start + +In this tutorial, we'll explore how to use API labeling to categorize and filter APIs in your organization's API Repository. + +### Prerequisites + +- Access to the Tyk Governance Hub +- Governance Admin access for creating new label definitions (Note: only admin access is available at the moment) + +### Step-by-Step + +1. **Access the API Repository** + + Navigate to the API Repository section in your Tyk Governance dashboard. + +2. **Explore Default Labels** + + Tyk Governance comes with pre-configured default labels such as "Business Domain" and "API Criticality". + +3. **Apply Labels to APIs** + + Select an API and click "Edit" to apply or modify labels: + + - Set "Business Domain" to an appropriate value (e.g., "Finance", "Customer", "Product") + - Assign "API Criticality" based on the API's importance (Tier 1 for mission-critical, Tier 2 for important, Tier 3 for non-critical) + - Add any custom labels that your Governance Admin has defined + +4. **Filter APIs Using Labels** + + Use the search and filter functionality to find APIs based on their labels: + + - Filter to show only Tier 1 APIs + - Search for APIs in a specific business domain + - Combine multiple label filters for precise results + +5. **Create a Custom Label (Admin only)** + + Governance Admin users can create custom labels programmatically using the API: + + Example using cURL: + + ```bash + curl -X POST https://your-governance-instance.tyk.io/api/labels/ \ + -H "Content-Type: application/json" \ + -H "X-API-Key: YOUR_ADMIN_TOKEN" \ + -d '{ + "name": "compliance", + "values": ["PCI-DSS", "GDPR", "HIPAA"] + }' + ``` + + A successful request will return a 200 OK status code and the newly created label object: + + ```json + { + "id": "64a1b2c3d4e5f6a7b8c9d0e1", + "name": "compliance", + "values": ["PCI-DSS", "GDPR", "HIPAA"] + } + ``` + + **Notes**: + - The name field is required and must be unique + - The values field is optional. If provided, it defines the allowed values for this label + - If values is empty, the label will accept any value (free text) + - Only users with admin privileges can create labels + - Once created, labels can be applied to APIs using the `/api/{api-id}/labels` endpoint + - After creating a custom label, it will be available for selection when labeling APIs, either through the UI or via the API labeling endpoints + +### Validation + +- Labeled APIs will display their labels in the API details view +- Filtering by labels will show only matching APIs +- New custom labels will be available for application to APIs + +## How It Works + +API Labeling and Categorization works through a flexible key-value metadata system that allows both structured and free-form classification of APIs. + +### Labeling System Architecture + +1. **Bootstrap Default Labels**: During initial setup, Tyk Governance creates default label definitions such as "Business Domain" and "API Criticality" +2. **Label Definition**: Each label has: + - A unique key (e.g., "business_domain") + - A display name (e.g., "Business Domain") + - A value type (free text or predefined values) + - Optional predefined values (e.g., "Finance", "HR", "Operations") + +3. **Label Application**: Labels are applied to APIs as key-value pairs: + - Key: The label identifier (e.g., "business_domain") + - Value: The specific value for this API (e.g., "Finance") + +4. **Label Storage**: Labels are stored as metadata with each API in the repository database +5. **Search and Filter**: Tyk Governance indexes labels to enable efficient filtering and searching + +## Use Cases + +### Governance Policy Application + +Apply different governance rules based on API criticality tiers. For example, Tier 1 (mission-critical) APIs might require stricter security controls, more thorough documentation, and formal change management processes. + +### Compliance Management + +Tag APIs with relevant compliance requirements (PCI-DSS, GDPR, HIPAA) to ensure appropriate controls are applied and to facilitate compliance reporting and audits. + +### Team Ownership and Responsibility + +Label APIs by owning team or department to clarify responsibility for maintenance, support, and governance compliance. + +### API Lifecycle Management + +Use labels to indicate lifecycle stage (Development, Testing, Production, Deprecated) to manage API transitions and communicate status to consumers. + +## Best Practices and Recommendations + +- **Establish a clear labeling taxonomy** before implementing across your organization +- **Keep predefined value lists manageable** – too many options create confusion and inconsistency +- **Use hierarchical naming for related labels** (e.g., security.authentication.method, security.data.classification) +- **Document the meaning and intended use** of each label for consistent application +- **Assign label management responsibility** to a specific role or team to maintain consistency +- **Review and update labels periodically** to ensure they remain relevant as your API landscape evolves +- **Include label application in API onboarding workflows** to ensure consistent metadata from the start +- **Use consistent labeling conventions** across all APIs to facilitate effective filtering and governance +- **Combine multiple labels in filters** for more precise API discovery +- **Use criticality and domain labels** as the foundation of your governance strategy + +## FAQs + + + +Yes, Governance Administrators can create custom labels with either free text values or a predefined list of acceptable values. + + + +Labels are structured key-value pairs that can be validated and used for governance, while tags are typically simpler, unstructured text values mainly used for search. + + + +Yes, the discovery process attempts to map source system metadata to corresponding labels in the governance hub where possible. + + + +## Troubleshooting + + +- Ensure a Governance Admin has properly defined the label +- Check that at least one API has been tagged with this label +- Refresh the browser cache if the label was recently added + + + +- For predefined value labels, check that the value you're trying to add is in the allowed list +- Verify you have sufficient permissions to modify the API's labels +- Ensure the label hasn't been deprecated or replaced + diff --git a/tyk-governance/api-repository.mdx b/tyk-governance/api-repository.mdx new file mode 100644 index 0000000000..77bcf3be91 --- /dev/null +++ b/tyk-governance/api-repository.mdx @@ -0,0 +1,130 @@ +--- +title: "API Repository" +description: "Learn how Tyk Governance automatically discovers and catalogs APIs across multiple sources to create a comprehensive inventory of all APIs in your organization." +keywords: "Tyk Governance, API Repository, API Discovery, API Inventory" +sidebarTitle: "Federated API Repository" +--- + +## Availability + +- Version: Available since v0.1 + +## Overview + +API Repository automatically discovers and catalogs APIs across multiple sources (Tyk, AWS API Gateway, etc) to create a comprehensive inventory of all APIs in your organization. This feature addresses API sprawl, identifies shadow APIs, and provides complete visibility into your API landscape. + +### Key Benefits + +- Creates a single source of truth for all APIs across the organization +- Identifies security risks from undocumented or unmanaged APIs +- Enables better resource management and prevents duplication +- Provides visibility into API ownership and usage patterns + +### Dependencies + +- Requires the governance agent for API discovery from non-Tyk Cloud-managed control planes and non-Tyk platforms. + +## Quick Start + +In this tutorial, we'll explore how to use the API Repository to view and manage discovered APIs in your organization. + +### Prerequisites + +- Access to the Tyk Governance Hub +- Governance agent deployed and connected to your API providers (non Tyk Cloud sources only) + +For detailed installation and configuration instructions, please refer to the [Installation and Setup](/tyk-governance/installation) page. + +### Step-by-Step + +1. **Access the API Repository** + + Navigate to the API Repository section in your Tyk Governance Hub to view discovered APIs. + +2. **Explore the API inventory** + + The dashboard provides a comprehensive view of all discovered APIs across your organization, with filtering and search capabilities. + + + +3. **Examine API details** + + Click on any API to view detailed information, including specifications, ownership, authentication methods, and governance status. + + + +## How It Works + +The API Repository works by deploying agents that connect to various API sources, extract metadata, and synchronize this information with the central governance hub. Think of it as an automated API census that continuously updates your API inventory. + +### Discovery Process + +1. **Agent Deployment**: Agents are deployed to connect with various API sources. +2. **API Source Connection**: Agents authenticate and connect to configured API sources. +3. **Metadata Extraction**: Agents extract API metadata including routes, authentication methods, and specifications. +4. **Synchronization**: Extracted data is sent to the governance hub through secure gRPC streams. +5. **Inventory Creation**: APIs are cataloged in a centralized repository with relevant metadata. +6. **Classification**: APIs can be tagged and categorized based on extracted and custom metadata. +7. **Continuous Updates**: Regular scans maintain an up-to-date inventory and identify changes. + +## Use Cases + +### Centralizing API Inventory Across Multiple Gateways + +When your organization uses multiple API gateways (Tyk, AWS, etc.), maintaining a single view of all APIs becomes challenging. API Discovery automatically aggregates APIs from all sources into a unified inventory, providing a complete picture of your API landscape without manual tracking. + +### Identifying and Managing Shadow APIs + +Shadow APIs—those created outside official processes—pose security and governance risks. The discovery feature continuously scans your infrastructure to identify undocumented APIs, allowing you to bring them under governance or decommission them as appropriate. + +### Streamlining API Onboarding with Automated Discovery + +For organizations with numerous APIs, manual registration is time-consuming and prone to errors. Automated discovery accelerates the onboarding process by automatically detecting new APIs and pre-populating their metadata, thereby reducing the time required to bring APIs under governance. + +### Tracking API Changes for Compliance and Audit + +When APIs change without proper documentation, it creates compliance risks. The continuous discovery process detects changes to existing APIs, maintaining an accurate, up-to-date inventory that serves as an audit trail for compliance purposes. + +### Enabling API Reuse Through Comprehensive Cataloging + +Developers often recreate APIs because they're unaware of existing ones. A complete API inventory with rich metadata enables developers to discover and reuse existing APIs, reducing duplication and development costs. + +## Best Practices and Recommendations + +- **Configure all relevant API sources** to ensure complete coverage of your API landscape +- **Implement a review process** for newly discovered APIs to ensure proper classification and ownership assignment +- **Integrate discovery with your CI/CD pipeline** to synchronize new APIs as they're deployed automatically +- **Establish clear ownership** for each API to ensure accountability for governance and maintenance + +## FAQs + + +The discovery process uses secure authentication methods for each provider and transmits data via encrypted channels. The agent requires minimal permissions—just enough to read API configurations. + + + +The discovery process is designed to be lightweight and non-intrusive, primarily reading configuration data rather than analyzing traffic, thereby minimizing any performance impact. + + +## Troubleshooting + + + +- Check the agent logs for authentication errors +- Verify the provider configuration in the governance agent config +- Ensure the agent has network access to the API source + + + +- Some API sources may not expose all metadata +- Check if the API definition in the source is complete +- Consider enhancing the API definition at the source + + + +- Verify the governance URL and token in the agent configuration +- Check network connectivity between the agent and governance hub +- Examine the agent logs for specific connection errors + + + diff --git a/tyk-governance/core-concepts.mdx b/tyk-governance/core-concepts.mdx new file mode 100644 index 0000000000..5a961d2cc1 --- /dev/null +++ b/tyk-governance/core-concepts.mdx @@ -0,0 +1,278 @@ +--- +title: "Core Concepts" +description: "Detailed explanation of key technical concepts that form the foundation of Tyk Governance, including federated API management, governance rulesets, and the technical architecture." +keywords: "Tyk Governance, API Governance, Federated API Management, Governance Concepts" +sidebarTitle: "Core Concepts" +--- + +This section provides a detailed explanation of the key technical concepts that form the foundation of Tyk Governance. + +## What is Tyk Governance? + +Tyk Governance is a comprehensive API Governance Hub designed to provide centralized visibility, control, and policy enforcement across distributed API ecosystems. It enables organizations to establish and maintain consistent standards, security practices, and compliance requirements across multiple API gateways and management platforms. + +At its core, Tyk Governance is a federated control plane that sits above your existing API infrastructure, regardless of whether you're using Tyk exclusively or a mix of different API management solutions. It collects, analyzes, and governs API definitions from various sources, ensuring they adhere to your organization's standards and best practices. + +Tyk API Governance Architecture + +## Federated API Management + +Federated API management refers to the practice of managing APIs across multiple, distributed platforms while maintaining consistent governance, visibility, and control. + +Organizations struggle with API sprawl, with many lacking visibility into their total number of APIs. Multiple gateways across teams create security vulnerabilities, governance gaps, and inefficiency. This "API debt" results in inconsistent security protocols, missed reuse opportunities, and increased risk from shadow APIs. Large enterprises need a solution that balances centralized governance with team autonomy—enabling visibility across different gateways while allowing teams to use their preferred tools. The ideal approach provides unified oversight without forcing consolidation, ensuring compliance while preserving innovation and operational independence. + +### Tyk Governance Solutions + +Tyk Governance addresses these challenges through: + +1. **Unified API Repository**: A central inventory of all APIs across different providers. +2. **Cross-Platform Policy Enforcement**: Consistent application of governance policies regardless of the underlying API provider. +3. **Automated Compliance Checking**: Continuous validation of APIs against organizational standards and regulatory requirements. +4. **Maturity Assessment**: Evaluation and scoring of APIs based on design, security, documentation, and performance criteria. +5. **Centralized Reporting**: Comprehensive visibility into API compliance and governance status. + +## Governance Rulesets + +A governance ruleset in Tyk Governance is a set of rules and standards that APIs must adhere to. These rulesets define the requirements for API design, security, documentation, and operational characteristics. + +### Ruleset Components + +1. **Rules**: Individual checks that validate specific aspects of an API definition. +2. **Severity Levels**: Categorization of rules by importance (error, warning, info). +3. **Validation Functions**: The specific logic used to evaluate API definitions against rules. +4. **Remediation Guidance**: Instructions on how to fix issues when rules are violated. + +### Spectral Ruleset Compatibility + +Tyk Governance rulesets are compatible with the [Spectral ruleset](https://meta.stoplight.io/docs/spectral/01baf06bdd05a-rulesets) format, a widely adopted API linting and governance standard. This compatibility offers several advantages: + +1. **Familiar Format**: Teams already using Spectral can easily migrate their existing rulesets. +2. **Ecosystem Integration**: Leverage the broader ecosystem of pre-built Spectral rules. +3. **Extensibility**: Create custom rules using the same format and functions as Spectral. +4. **IDE Integration**: Use existing Spectral plugins for popular code editors. + +The Spectral-compatible format allows for declarative rule definitions with given/then patterns, custom functions, and detailed error messaging. + +### Basic Ruleset Examples + +```yaml +# Security ruleset requiring HTTPS +owasp-security-hosts-https-oas3: + description: All server interactions MUST use the https protocol + severity: error + then: + function: owaspHostsHttps + +# Rate limiting ruleset +rate-limit-exists: + description: Ensure rateLimit exists under upstream + severity: error + given: "$['x-tyk-api-gateway'].upstream" + then: + - field: rateLimit + function: truthy +``` + +Rulesets can be customized to meet organizational needs and evolve as API best practices and security requirements change. + +## Supported API Providers + +Tyk Governance is designed to work with a wide range of API management platforms, allowing organizations to maintain governance regardless of their existing API infrastructure. + +### API Provider Compatibility + +| API Provider | Tested Version | Supported API Types | Supported Features | +| :--------------- | :-------------- | :------------- | :------------------------------------------------- | +| Tyk Dashboard | 5.3+ | Tyk OAS | Complete integration with all governance features | +| AWS API Gateway | All | Rest APIs | API definition export, OAS schema export | +| Azure | - | - | Coming Soon | +| Kong | - | - | Coming Soon | +| WSO2 | - | - | Coming Soon | + +### Integration Capabilities + +Tyk Governance integrates with these providers through specialized agents that: + +1. Connect to the platform's management APIs +2. Extract API definitions and configurations +3. Convert proprietary formats to OpenAPI Specification (OAS) +4. Apply Tyk-specific extensions where applicable +5. Synchronize definitions with the central governance repository + +### Future API Provider Support + +The Tyk Governance roadmap includes plans to expand support to additional platforms and API Types. + +## How It Works + +Tyk Governance operates through a distributed architecture that combines a centralized cloud-hosted governance service with distributed agents that run in your environments. + +How Tyk API Governance Works + +### Technical Architecture + +```mermaid +flowchart LR + subgraph "Tyk Cloud" + GS["Governance Service"] --- DB[(Database)] + GS --- RE["Rule Engine"] + GS <--> A4["Agent 4"] --- P4["Cloud Control Plane"] + end + + subgraph "Customer Environment" + A1["Agent 1"] --- P1["Tyk Dashboard (Self-Managed)"] + A2["Agent 2"] --- P2["AWS API Gateway"] + end + + A1 <-->|"TLS + Auth"| GS + A2 <-->|"TLS + Auth"| GS +``` + +### Hosted Service Model + +The Governance Core is a Tyk Cloud hosted and managed service, providing several benefits: + +1. **Zero Infrastructure Overhead**: No need to deploy and maintain governance infrastructure. +2. **Automatic Updates**: Always access the latest features and security patches. +3. **Scalable Performance**: Handles growing API ecosystems without additional configuration. +4. **High Availability**: Built-in redundancy and failover capabilities. + +### Customer-Hosted Agents + +While the core service is cloud-hosted, customers can host their own agents within their environments: + +1. **Credential Isolation**: Tyk Governance never directly accesses your API providers; all credentials remain within your environment. +2. **Network Security**: The agent requires accepting inbound traffic from the cloud-based governance dashboard. All communication between agents and the dashboard is secured via TLS encryption. +3. **Deployment Flexibility**: Deploy agents in any environment where they can access your API platforms. +4. **Lightweight Footprint**: Agents have minimal resource requirements and can run in containers or VMs. + +### Process Sequence + +```mermaid +sequenceDiagram + participant Agent + participant Governance + participant RuleEngine + + Agent->>Governance: Register with governance hub + Governance->>Agent: Issue authentication token + Governance->>Agent: Send sync request + Agent->>Agent: Get APIs from API providers + Agent->>Governance: Stream API definitions + Governance->>Governance: Store in repository + Governance->>RuleEngine: Validate against rules + RuleEngine->>Governance: Return validation results + Governance->>Governance: Generate reports +``` + +### Synchronization Mechanisms + +Tyk Governance uses a secure bidirectional streaming protocol for efficient synchronization: + +1. **Registration**: Agents register with the Governance Hub and establish a secure connection. +2. **Heartbeat**: Agents maintain a health check stream to indicate their status. +3. **Sync Request**: The Governance Hub can trigger a sync operation on demand or on schedule. +4. **Streaming Response**: Agents stream API definitions back to the governance hub as they are extracted. +5. **Incremental Updates**: Only changed APIs are synchronized to minimize network traffic. + +### Security Measures + +The synchronization between agents and the Governance service includes multiple security layers: + +1. **TLS Encryption**: All communications are encrypted using TLS 1.2+ to prevent eavesdropping. +2. **Authentication Tokens**: Agents authenticate using secure tokens that can be rotated and revoked. +3. **Minimal Privilege**: Agents use read-only access to API platforms whenever possible. +4. **Data Minimization**: Only API definitions and metadata are transmitted, not actual API traffic or payloads. +5. **Audit Logging**: All synchronization activities are logged for security monitoring. + +### Data Exchange + +The information exchanged between agents and the Governance service includes: + +1. **From Agent to Governance**: + - API definitions in OpenAPI format + - API metadata (name, version, endpoints, security schemes) + - Provider-specific configuration converted to standard formats + - Agent status and capability information + - Sync operation status and results + +2. **From Governance to Agent**: + - Sync requests and configuration + - Authentication tokens and renewal information + +Notably, the following are NOT transmitted: + +- API keys or credentials for accessing APIs +- Actual API request/response payloads +- Customer data processed by APIs +- Internal network information beyond what's in API definitions + +## Glossary of Terms + +### Agent + +A component that connects to API Providers (Tyk, AWS API Gateway, etc.) to extract and sync API definitions. + +### API Maturity + +A measure of how well an API adheres to best practices in design, security, documentation, and performance. + +### API Provider + +A system or platform where APIs are hosted or managed, which Tyk Governance can discover and monitor. Examples include Tyk Dashboard and AWS API Gateway. + +### API Repository + +A federated catalog that aggregates APIs from multiple API providers, providing a centralized view of all APIs within an organization. + +### Federated API Management + +An approach to managing APIs across multiple platforms and environments while maintaining centralized governance. + +### Label + +A key-value pair assigned to APIs or API providers for categorization and governance purposes. Examples include `domain:storefront`, `environment:production`, or `pii:true`. + +### Ruleset + +A collection of governance rules that can be applied to APIs to enforce best practices and compliance requirements. + +### Rule + +A specific condition that can be evaluated against APIs to ensure they meet governance standards. Rules include severity levels, messages, and descriptions. + +### Ruleset Template + +A predefined ruleset containing common governance rules that can be applied as a starting point for governance policies. + +### Governance Report + +A summary of API compliance with governance rules, identifying violations and suggesting remediations. + +### Violation + +An instance where an API fails to meet defined governance standards, categorized by severity level. + +### Compliance + +The degree to which an API adheres to defined governance policies and standards. + +### Remediation + +The structured process of addressing and resolving API governance violations. + +### Remediation Priority + +Indicates how urgently an API issue should be addressed based on its risk level and potential impact. This priority helps teams focus their efforts on the most critical issues first. + +### Risk Level + +A summary metric that reflects API governance compliance across multiple APIs, considering their adherence to selected governance rulesets. + +### Sync + +The process of extracting API definitions from management platforms and updating the governance repository. + +### Tyk-OAS Governance Extensions + +Tyk-specific extensions to the OpenAPI Specification that enable advanced governance features. \ No newline at end of file diff --git a/tyk-governance/governance-rulesets.mdx b/tyk-governance/governance-rulesets.mdx new file mode 100644 index 0000000000..e507bf9659 --- /dev/null +++ b/tyk-governance/governance-rulesets.mdx @@ -0,0 +1,404 @@ +--- +title: "Governance Rulesets" +description: "Define, manage, and enforce API standards across your organization through customizable rules that act as executable policies for API governance requirements." +keywords: "Tyk Governance, Rulesets, API Standards, Governance Policies, API Compliance" +sidebarTitle: "Governance Rulesets" +--- + +## Availability + +- Version: Available since v0.2 + +## Overview + +Governance Rulesets enable you to define, manage, and enforce API standards across your organization through customizable rules. These rulesets act as executable policies that define your organization's API governance requirements, helping you establish consistent standards for security, design, and documentation. + +### Key Benefits + +- **Standardize API Development**: Define consistent patterns and practices for all APIs +- **Centralize Governance Policies**: Maintain standards in a single location accessible to all teams +- **Customize to Your Needs**: Create organization-specific rules or use pre-built templates +- **Evolve Standards Gradually**: Adjust rule severity and scope as your governance program matures +- **Share Knowledge**: Embed best practices and remediation guidance directly in rules + +### Dependencies + +- Requires Tyk Governance v0.2 or higher + +## Quick Start + +In this tutorial, we'll create a simple governance ruleset that can be used to validate APIs. + +### Prerequisites + +- Access to Tyk Governance Hub + +### Step-by-Step + +1. **Access the Rulesets Section** + + Navigate to the Rulesets section in your Tyk Governance dashboard. + +2. **Create a New Ruleset** + + Click the "Create new ruleset" button to create a new ruleset. + + + +3. **Choose a Template** + + Select "Start from Template" and choose the "vacuum-owasp" template. + + + +4. **Customize Your Ruleset** + + Review the pre-configured rules. You can enable/disable specific rules or adjust their severity levels. + + + + Then, provide a name and description for your ruleset. + + + +5. **Save Your Ruleset** + + Click **Finish** to create your new ruleset. + +6. **View Your Ruleset** + + Your new ruleset will appear in the rulesets list. Click on it to view details and manage individual rules. + +### Validation + +- Successful ruleset creation will be confirmed with a success message +- The ruleset will appear in your rulesets list +- You can now use this ruleset to [evaluate APIs](/tyk-governance/api-evaluation) + +## How It Works + +Governance Rulesets use a powerful rule engine based on the Spectral format to define standards for API specifications. Each rule consists of a selector that identifies parts of the API specification to evaluate, a function that performs the evaluation, and metadata that provides context and remediation guidance. + +### Rule Structure + +A typical rule in a ruleset includes: + +- **Given**: A JSONPath expression that selects parts of the API specification +- **Then**: Functions to apply to the selected parts +- **Severity**: The importance level (error, warn, info, hint) +- **Message**: A description of what the rule checks +- **HowToFix**: Guidance on resolving any violations + +When you create a ruleset, you're defining a collection of these rules that work together to enforce your governance standards. + +### Example Rulesets + +#### Security Standards Ruleset + +Create rulesets that define security requirements for APIs, such as authentication requirements, secure endpoints, and protection against common vulnerabilities. + +```yaml +security-auth-required: + description: APIs must require authentication + severity: error + given: $.paths.*.* + then: + field: security + function: truthy + howToFix: "Add a security requirement to this operation" +``` + +#### API Design Standards Ruleset + +Define rules that enforce naming conventions, URL patterns, and response structures to maintain consistency across your API portfolio. + +```yaml +path-case-convention: + description: Path segments must use kebab-case + severity: warn + given: $.paths + then: + field: "@key" + function: pattern + functionOptions: + match: "^\/([a-z0-9-]+|{[a-zA-Z0-9_]+})(\/{[a-zA-Z0-9_]+}|\/[a-z0-9-]+)*$" + howToFix: "Rename path segments to use kebab-case (lowercase with hyphens)" +``` + +#### Documentation Standards Ruleset + +Create rules that check for complete and accurate documentation, including descriptions, examples, and response schemas. + +```yaml +operation-description: + description: All operations must have descriptions + severity: warn + given: $.paths.*.* + then: + field: description + function: truthy + howToFix: "Add a meaningful description to this operation" +``` + +## Manage Rulesets + +### Creating Rulesets + +Rulesets define governance standards and ensure API compliance with security, performance, and reliability requirements. You can create rulesets through the Governance UI or programmatically via the API. + +#### Using the UI + +The Governance UI provides a user-friendly interface for creating rulesets: + +1. Navigate to the Rulesets section +2. Click **Create new ruleset** +3. Choose how to create your ruleset (import from file, paste definition, or start from template) +4. Provide basic information (name, description) +5. Save your ruleset + +#### Using the API + +You can also create rulesets programmatically using the API. + +**Creating a Ruleset with JSON Payload** + +```sh +curl -X POST https://your-governance-instance.tyk.io/api/rulesets \ + -H "Content-Type: application/json" \ + -H "X-API-Key: YOUR_API_KEY" \ + -d '{ + "metadata": { + "name": "Security Standards", + "description": "Security rules for all APIs", + "active": true + }, + "ruleset": { + "rules": { + "security-auth-required": { + "description": "APIs must require authentication", + "severity": "error", + "given": "$.paths.*.*", + "then": { + "field": "security", + "function": "truthy" + }, + "howToFix": "Add a security requirement to this operation" + } + } + } + }' +``` + +**Creating Rulesets from Files** + +For more complex rulesets or when you maintain your rulesets as files in your development environment, you can create rulesets directly from files using a multipart form request. + +To create a ruleset from a file, you need to send a multipart form request with two key components: + +1. `metadata`: JSON object containing ruleset metadata (name, description, etc.) +2. `ruleset`: The ruleset definition file content (in YAML or JSON format) + +Here's how to create a ruleset from a YAML file using curl: + +```sh +curl -X POST https://your-governance-instance.tyk.io/api/rulesets \ + -H "X-API-Key: YOUR_API_KEY" \ + -F "metadata={\"name\":\"API Security Ruleset\",\"description\":\"Enforces API security best practices\",\"active\":true}" \ + -F "ruleset=@/path/to/your/ruleset.yaml" +``` + +### Managing Rulesets + +Once created, rulesets can be managed through the UI or API: + +#### Viewing Rulesets + +- Navigate to the Rulesets section to see all rulesets +- Click on a ruleset to view its details and rules +- Use search to find specific rulesets + +#### Editing Rulesets + +- From the ruleset details page, click **Configure ruleset** +- Modify ruleset metadata or individual rules +- Save your changes + +{/* #### Deleting Rulesets + +- From the ruleset details page, click "Delete" +- Confirm the deletion */} + +#### Using Templates + +- When creating a new ruleset, select **Start from Template** +- Choose from pre-built templates for common standards +- Customize the template to meet your specific needs + +### Testing Rulesets Against APIs + +After creating your ruleset, you'll want to test it against your APIs to ensure it correctly identifies compliance issues. You can test a ruleset through the Governance UI: + +1. Navigate to the Ruleset Details Page + + - Go to the **Rulesets** section and select the ruleset you want to test + +2. Run an Evaluation + + - In the ruleset details page, locate the "**Test ruleset**" section + - Select an API from the dropdown menu + - Click the "**Run ruleset**" button + + + +3. Review Results + + - The evaluation results will display any rule violations found in the API + - Results are categorized by severity (High, Medium, Low) + - Click "View issue info" on any violation to see detailed information, including: + - The specific rule that was violated + - The affected area in the API specification + - Guidance on how to fix the issue + + + + + +## Understanding Remediation Priority + +In Tyk Governance, "Remediation Priority" indicates the urgency with which an API issue should be addressed, based on its risk level and potential impact. This priority helps teams focus their efforts on the most critical issues first. + +### Severity Mapping + +Remediation priority is directly derived from the severity level defined in the rule. When a rule violation is detected during evaluation, its severity level is mapped to a corresponding remediation priority: + +| Severity Level | Remediation Priority | Visual Indicator | +| :---------------- | :---------------------- | :------------------ | +| error | High | Red pill | +| warn | Medium | Yellow/orange pill | +| info | Low | Green pill | + +## Use Cases + +### Establishing Tiered Governance Standards + +Create different rulesets for different API tiers based on criticality, allowing for appropriate governance without over-restricting less critical APIs. + +**Implementation:** + +1. Create a "Tier 1" ruleset with strict security, design, and documentation rules for mission-critical APIs +2. Create a "Tier 2" ruleset with moderate requirements for important but less critical APIs +3. Create a "Tier 3" ruleset with basic requirements for internal or non-critical APIs +4. Apply these rulesets selectively based on API classification + +**Benefits:** + +- Appropriate governance based on API importance +- More efficient use of development resources +- Clear expectations for different types of APIs + +### Implementing Industry-Specific Standards + +Create rulesets that enforce industry-specific regulations and best practices for APIs in regulated sectors. + +**Implementation:** + +1. Identify relevant industry standards (e.g., FAPI for financial services, HIPAA for healthcare) +2. Create rulesets that codify these standards as executable rules +3. Include detailed remediation guidance specific to the industry context +4. Apply these rulesets to APIs in the relevant domains + +**Benefits:** + +- Ensure compliance with industry regulations +- Reduce audit preparation time +- Standardize compliance approaches across teams + +### Evolving Governance Standards Over Time + +Use rulesets to gradually implement and evolve governance standards as your organization's API program matures. + +**Implementation:** + +1. Start with a basic ruleset focusing on critical security and fundamental design principles +2. Gradually add more rules as teams become familiar with the standards +3. Adjust severity levels over time (e.g., start as warnings, later promote to errors) +4. Incorporate feedback from development teams to refine rules + +**Benefits:** + +- Avoid overwhelming teams with too many rules at once +- Build governance maturity incrementally +- Gain buy-in through collaborative evolution + +## Best Practices and Recommendations + +- **Start with templates** for common standards like OWASP or OpenAPI best practices +- **Customize gradually** by adding organization-specific rules over time +- **Use appropriate severity levels** - reserve "error" for critical issues that must be fixed +- **Include clear remediation guidance** in the "howToFix" field for each rule +- **Group related rules** into focused rulesets (security, design, documentation) +- **Review and update rulesets regularly** as standards evolve +- **Collect feedback from developers** on rule clarity and usefulness +- **Document the purpose** of each ruleset for better organizational understanding +- **Maintain version control** for rulesets as they evolve +- **Assign ownership** to specific individuals or teams for each ruleset + +## FAQs + + + +Tyk Governance supports Spectral-compatible rulesets in both YAML and JSON formats. This makes it compatible with existing Spectral rulesets and allows for easy migration from other tools. + + + +Currently, Tyk Governance supports the standard functions provided by the Spectral/Vacuum engine. Custom functions are planned for future releases. + + + +There's no hard limit on the number of rules in a ruleset, but performance may degrade with very large rulesets (100+ rules). We recommend organizing related rules into separate rulesets for better manageability and performance. + + + +Yes, you can import existing Spectral rulesets in YAML or JSON format. This allows you to leverage your existing governance rules in Tyk Governance. + + + +Once you've created rulesets, you can use them to validate APIs through the [API Evaluation](/tyk-governance/api-evaluation) feature, which allows you to check API specifications against your governance standards. + + + +## Troubleshooting + + + +- Verify the ruleset is in valid YAML or JSON format +- Check that all required fields are present (given, then, severity) +- Ensure JSONPath expressions are valid +- Look for syntax errors in function options +- Try importing a smaller portion of the ruleset to identify problematic rules + + + +- Check that the rule definition follows the correct format +- Verify that the rule wasn't disabled during import +- Ensure the rule has a unique name within the ruleset +- Try adding the rule manually if it was part of an import + + + +- Test your JSONPath expression with a sample API specification +- Verify the syntax follows JSONPath standards +- Check for typos or missing elements in the path +- Consider simplifying complex expressions +- Use online JSONPath evaluators to debug expressions + + + +- Ensure you have the necessary permissions +- Check for validation errors in the ruleset definition +- Verify you're clicking the final save button after making changes +- Try refreshing the page and making changes again +- Check browser console for any JavaScript errors + + + diff --git a/tyk-governance/installation.mdx b/tyk-governance/installation.mdx new file mode 100644 index 0000000000..97ac1ea4ef --- /dev/null +++ b/tyk-governance/installation.mdx @@ -0,0 +1,538 @@ +--- +title: "Installation and Setup" +description: "Step-by-step instructions for installing and configuring Tyk Governance, including cloud-hosted options and deploying agents in your environment." +keywords: "Tyk Governance, Installation, Configuration, Agent Setup, Deployment" +sidebarTitle: "Installation" +--- + +This section moves from concepts to hands-on implementation, providing the practical steps needed to start with Tyk Governance. + +## Prerequisites + +Before beginning the installation and setup process for Tyk Governance, ensure your environment meets the following requirements: + +### License Requirements +- Valid Tyk license with Governance feature enabled + +### System Requirements + +**For Tyk Governance Hub:** +- No local system requirements as Tyk Governance is fully hosted and managed by Tyk in the cloud + +**For Tyk Governance Agent:** +- Minimum 1 CPU core +- 512MB RAM +- 1GB available disk space +- Linux-based operating system (Ubuntu 18.04+, CentOS 7+, or equivalent) +- Docker (if using containerized deployment) +- Kubernetes (optional, for orchestrated deployments) + +### Permission Requirements + +**For Agent Installation:** +- Read access to your API providers (Tyk, AWS API Gateway, etc.) +- Ability to create and manage containers or services in your environment +- Network configuration permissions to establish outbound connections +- Permission to create and manage secrets for storing API credentials + +### Network Requirements + +**For Tyk Governance Agent:** +- Inbound access from the Tyk Governance Hub (default 50051 for gRPC) +- Outbound access to your API providers +- Outbound HTTPS (port 443) access to the Tyk Governance Hub +- If API Provider gateways run on different networks, network routes must allow the agent to communicate with those networks + + +Running on Podman, containerd, or another container runtime? See [Container Runtimes](/deployment-and-operations/container-runtimes). + + +## System Architecture + +Tyk Governance follows a cloud-hosted service model with customer-deployed agents, creating a secure and flexible architecture that respects your network boundaries while providing centralized governance. + +### High-Level Architecture + +```mermaid +flowchart LR + subgraph "Tyk Cloud" + GS["Governance Service"] --- DB[(Database)] + GS --- RE["Rule Engine"] + GS <--> A4["Agent 3"] --- P3["Cloud Control Plane"] + end + + subgraph "Customer Environment" + A1["Agent 1"] --- P1["Tyk Dashboard (Self-Managed)"] + A2["Agent 2"] --- P2["AWS API Gateway"] + end + + A1 <-->|"TLS + Auth"| GS + A2 <-->|"TLS + Auth"| GS +``` + +### Deployment Models + +#### Tyk Cloud with Automatic Agent + +```mermaid +flowchart LR + subgraph "Tyk Cloud" + GS["Governance Service"] + CP["Cloud Control Plane"] + AG["Auto-deployed Agent"] + + GS --- AG + AG --- CP + end + + User["User"] --> GS +``` + +**When to use this model:** +- You exclusively use Tyk Cloud for API management +- You want the simplest possible setup with minimal configuration +- You don't have any APIs on other platforms that need governance + +#### Tyk Cloud with Customer-Deployed Agents + +```mermaid +flowchart LR + subgraph "Tyk Cloud" + GS["Governance Service"] + end + + subgraph "Customer Environment" + A1["Agent"] --- TG["Tyk Dashboard (Self-Managed)"] + A2["Agent"] --- AWS["AWS API Gateway"] + end + + A1 <--> GS + A2 <--> GS + + User["User"] --> GS +``` + +**When to use this model:** +- You use self-managed Tyk deployments (not Tyk Cloud) +- You use AWS API Gateway or other supported providers +- You need to govern APIs across providers that aren't in Tyk Cloud + +#### Hybrid Deployment + +```mermaid +flowchart LR + subgraph "Tyk Cloud" + GS["Governance Service"] + CP["Cloud Control Plane"] + AG["Auto-deployed Agent"] + + GS --- AG + AG --- CP + end + + subgraph "Customer Environment" + A2["Agent"] --- AWS["AWS API Gateway"] + end + + A2 <--> GS + + User["User"] --> GS +``` + +**When to use this model:** +- You use a combination of Tyk Cloud and other API platforms +- You have a mix of cloud and on-premises API deployments +- You need comprehensive governance across your entire API ecosystem + +## Installation + +The installation process for Tyk Governance varies depending on whether you're an existing Tyk Cloud customer and which deployment model you use. + +### Requesting Access to Tyk Governance + +1. **Contact Tyk for Access** + - Reach out to your Tyk Account Manager or visit [tyk.io/contact-book-a-demo](https://tyk.io/contact-book-a-demo/) + - Specify that you're interested in access to Tyk Governance + - Provide information about your current API management environment + +2. **Receive Access Credentials** + - After your request is processed, you'll receive an email with: + - URL to access the Tyk Governance Hub + - Admin credentials for initial login + - Instructions for next steps + +3. **Initial Login** + - Navigate to the provided Governance Hub URL + - Enter the admin credentials from the email + - You'll be prompted to change your password on first login + +### Enabling Governance Feature for Cloud Control Planes + +For existing Tyk Cloud managed control planes, enabling governance is straightforward: + +1. **Log in to Tyk Cloud Dashboard** + - Navigate to your Tyk Cloud dashboard + - Ensure you have administrative privileges + +2. **Access Control Plane Settings** + - Select the Control Plane you want to enable governance for + - Click on "Edit Details" button + +3. **Enable Governance Feature** + - Locate the "Governance Agent" toggle + - Enable the feature + - Save your changes + +4. **Verification** + - An agent will be automatically deployed for your Tyk Control Plane + - You can now access the Governance dashboard via "Governance" in the Cloud UI sidebar + +### Installing a Local Agent + +For environments where you need to install agents manually (non-Tyk platforms or on-premises deployments), follow these steps: + +**Prerequisites for Agent Installation:** +- Access to the Governance Hub to generate agent tokens +- Network connectivity between the agent and both the Governance Hub and your API provider +- Docker or Kubernetes for container-based deployment (recommended) + +#### Generate Agent Token from Governance Hub UI + +1. From the Agents page, click the **New agent** button in the top-right corner. + + + +2. In the New agent form, enter: + + - **Name**: A descriptive name for the agent (required) + - **Description**: Details about the agent's purpose or location (required) + + Click **Create agent** to save the new agent. + + + +3. Click "Generate new access token" + + + +4. Use the copy icon to copy the token to your clipboard + + + + + +#### Generate Agent Token using API + +You can also use API to create a token. After receiving your Governance Hub credentials, follow these steps: + +1. **Obtain an API Key**: + - Log in to the Governance Hub using the credentials provided in your welcome email + - Check your Access key under the "Settings > User Profile" section + + + +2. **Create an Agent using the API**: + + ```bash + # Replace these values with your actual information + GOVERNANCE_URL="https://your-governance-instance.tyk.io" + API_KEY="your-access-key" + AGENT_NAME="My AWS Agent (US)" + + # Create agent first + curl -s -X POST --location "${GOVERNANCE_URL}/api/agents/" \ + -H "X-API-Key: ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "'"${AGENT_NAME}"'" + }' + ``` + + Example response that shows an agent is created in INACTIVE state: + + ```json + { + "id": "a51d9bd0-bafe-4749-8285-e18641b151f2", + "name": "My AWS agent (US)", + "description": "", + "last_heartbeat": "0001-01-01T00:00:00Z", + "status": "inactive", + "providers": null, + "token": "", + "version": "" + } + ``` + + ```bash + # Extract agent ID from response + AGENT_ID="a51d9bd0-bafe-4749-8285-e18641b151f2" + ``` + +3. **Generate an Agent Token using the API**: + + Now you can generate an access token for the agent. + + ```bash + # API call to create an agent token + curl -X POST "${GOVERNANCE_URL}/api/auth/token/" \ + -H "X-API-Key: ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "agent_id": "'"${AGENT_ID}"'" + }' + ``` + + Example response: + + ```json + { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + } + ``` + +4. **Save the token securely**: + + - Copy the `token` value from the response + - Store it securely, as you'll need it for agent configuration + - Note: This token cannot be retrieved later, so make sure to save it + +#### Prepare Configuration + +Create a configuration file named `agent-config.yaml` with the following structure: + +```yaml +#============================================================================== +# Tyk Governance Agent Configuration +#============================================================================== + +# Your Tyk Governance license key - required for agent authentication +# This is provided by Tyk when you subscribe to the Governance service +licenseKey: "your-tyk-governance-license-key" + +# Configuration for connecting to the Tyk Governance dashboard/service +governanceDashboard: + server: + # The gRPC endpoint URL of the Tyk Governance service + # Format: hostname:port (without protocol) + # This is in the format of prefixing "grpc-" to your Governance Hub URL. + url: "grpc-your-governance-instance.tyk.io:443" + + auth: + # Authentication token for this agent + # Generated via API call to /auth/token endpoint + # This token identifies and authorizes this specific agent + token: "my-agent-token" + +#============================================================================== +# API Provider Configurations +#============================================================================== +# List of API providers this agent will connect to +# Each agent can connect to multiple providers of different types +instances: + #-------------------------------------------------------------------------- + # Tyk Provider Configuration + #-------------------------------------------------------------------------- + - name: "tyk-provider" # Descriptive name for this provider instance + type: "tyk" # Provider type: must be "tyk" for Tyk Dashboard + config: + # The URL of your Tyk Dashboard + # For Kubernetes deployments, this might be an internal service URL + host: "http://dashboard-svc-tyk-stack-tyk-dashboard.tyk.svc.cluster.local:3000" + + # API key with read access to the Tyk Dashboard + # Can be obtained in Tyk Dashboard under "User" > "User Details": "Tyk Dashboard API Access Credentials" + # Requires read permissions for APIs and policies + auth: "your-auth-key" + + #-------------------------------------------------------------------------- + # AWS API Gateway Provider Configuration + #-------------------------------------------------------------------------- + - name: "aws-provider" # Descriptive name for this AWS API Gateway instance + type: "aws" # Provider type: must be "aws" for AWS API Gateway + config: + # AWS IAM credentials with permissions to list and get API Gateway resources + # Recommended: Use an IAM role with minimal required permissions + accessKeyId: "your-aws-access-key-id" + accessKeySecret: "your-aws-access-key-secret" + + # AWS region where your API Gateway APIs are deployed + # Example: us-east-1, eu-west-1, ap-southeast-2, etc. + region: "us-east-1" + + # Optional: Temporary session token if using temporary credentials + # Required only when using AWS STS temporary credentials + sessionToken: "your-aws-session-token" + +#============================================================================== +# Agent Settings +#============================================================================== + +# Log level controls verbosity of agent logs +# Options: debug, info, warn, error +# Recommended: info for production, debug for troubleshooting +logLevel: debug + +# Health probe configuration for monitoring agent health +# Used by container orchestration systems like Kubernetes +healthProbe: + server: + # Port on which the health probe server will listen + # Ensure this port is not used by other services + port: 5959 +``` + +#### Deploy the Agent + +**Docker Deployment:** + +```bash +# Replace it with your Tyk Governance license key +LICENSE_KEY="tyk-governance-license-key" + +# Replace with an available version tag +VERSION="latest" + +docker run -d --name tyk-governance-agent \ + -v $(pwd)/agent-config.yaml:/app/config.yaml \ + -e TYK_AGENT_LICENSEKEY="$LICENSE_KEY" \ + tykio/governance-agent:$VERSION +``` + +**Kubernetes Deployment:** + +1. Create a Kubernetes secret for the configuration: + +```bash +kubectl create secret generic agent-config \ + --from-file=config.yaml=./agent-config.yaml \ + -n your-namespace +``` + +2. Apply the following manifest: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: governance-agent + namespace: your-namespace # Replace with your namespace +spec: + replicas: 1 + selector: + matchLabels: + app: tyk-governance-agent + template: + metadata: + labels: + app: tyk-governance-agent + spec: + containers: + - name: agent + image: tykio/governance-agent:latest # Replace with an available version tag + env: + - name: TYK_AGENT_LICENSEKEY + value: your-governance-license #Replace with your license key + ports: + - name: health + containerPort: 5959 + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: health + readinessProbe: + httpGet: + path: /live + port: health + volumeMounts: + - mountPath: /app/config.yaml + name: agent-config + subPath: config.yaml + volumes: + - name: agent-config + secret: + secretName: agent-config + items: + - key: config.yaml + path: config.yaml +``` + +Apply with: + +```bash +kubectl apply -f agent-deployment.yaml +``` + +#### Verify Agent Connection + +1. Check if the agent is running properly: + + ```bash + # For Docker + docker logs tyk-governance-agent + + # For Kubernetes + kubectl logs -l app=tyk-governance-agent -n your-namespace + ``` + + 2. Look for log messages indicating a successful connection: + + ``` + Starting license validation... + License validated successfully. Valid till: ... + starting agent + agent started successfully + waiting agent to establish health check + starting health probes HTTP server","addr":":5959 + authenticated and established health stream + health check established, waiting for sync stream + agent registered successfully and established sync stream with governance dashboard + waiting for sync requests from the dashboard + ``` + +#### Trigger Initial Sync + +1. In the Governance Hub, navigate to "API Repository" +2. Click the "ReSync" button to initiate synchronisation + + + +3. Monitor the sync progress in the UI or refresh the page manually. + +## Examples + +The following examples demonstrate common deployment scenarios and configurations for Tyk Governance. + +### Example 1: Tyk Cloud with Automatic Agent + +This is the simplest deployment model for existing Tyk Cloud customers. + +**Configuration Steps:** + +1. Requesting Access to Tyk Governance +2. Enable the Governance feature in Tyk Cloud Control Plane as described in [Enabling Governance Feature for Cloud Control Planes](#enabling-governance-feature-for-cloud-control-planes) +3. Wait for automatic agent deployment +4. Access the Governance Hub from the Cloud UI sidebar +5. Navigate to "API Repository" to view your automatically discovered APIs +6. Trigger "ReSync" to pull updates from the control planes + +**Expected Outcome:** +- All APIs from your Tyk Control Plane will be automatically discovered and displayed in the API Repository + +### Example 2: Multi-Platform Governance with Custom Agents + +This example demonstrates how to set up governance across multiple API providers. + +**Configuration Steps:** + +1. Requesting Access to Tyk Governance +2. Generate agent tokens for each provider as described in [Installing a Local Agent](#installing-a-local-agent) +3. Create configuration files for each agent +4. Deploy each agent using Docker or Kubernetes as described in [Installing a Local Agent](#installing-a-local-agent) +5. Verify agent connections +6. Access the Governance Hub with the provided URL +7. Navigate to "API Repository" to view your automatically discovered APIs +8. Trigger "ReSync" to pull updates from all agents + +**Expected Outcome:** +- APIs from all providers will be discovered and displayed in a unified repository diff --git a/tyk-governance/overview.mdx b/tyk-governance/overview.mdx new file mode 100644 index 0000000000..deaf0032ea --- /dev/null +++ b/tyk-governance/overview.mdx @@ -0,0 +1,66 @@ +--- +title: "Tyk Governance Overview" +description: "Introduction to Tyk Governance, a universal API governance hub that enables organizations to establish, enforce, and monitor governance policies across multiple API platforms and gateways." +keywords: "Tyk Governance, API Governance, API Management" +sidebarTitle: "Overview" +--- + +## Overview + +Tyk Governance is a universal API governance hub that enables organizations to establish, enforce, and monitor governance policies across multiple API platforms and gateways. It solves the challenge of fragmented API management by providing a centralized approach to governance, regardless of where your APIs are hosted or which technologies they use. + +In today's complex API ecosystems, organizations struggle with inconsistent standards, security vulnerabilities, and compliance gaps across different API platforms. Tyk Governance bridges these gaps by creating a unified governance layer that works seamlessly with Tyk and extends to third-party API platforms, such as AWS API Gateway. + +Tyk Governance provides centralized visibility and organizations across different API platforms + +## Key Benefits + +* **Universal Governance** - Define and enforce consistent policies across multiple API platforms and styles (REST, GraphQL, event-driven) from a single control plane +* **Reduced Duplication** - Identify redundant or shadow APIs across different departments, reducing maintenance costs and security risks +* **Shift-Left Governance** - Catch governance violations during design and development, not after deployment, reducing rework by up to 60% +* **Collaborative Improvement** - Enable teams to work together with shared visibility and clear ownership of APIs across the organization +* **Measurable API Maturity** - Track and improve API quality with quantifiable metrics across technical excellence, business impact, and developer experience + +## Who Should Use Tyk Governance + +Tyk Governance transforms API governance from a fragmented, post-deployment concern into a proactive, continuous, and scalable process across your entire API ecosystem. + +```mermaid +flowchart LR + A[Enterprise Architects and Security Leads] -->|Define Policies| B[Tyk Governance] + C[Platform Engineers] -->|Integrate Tools| B + B -->|Provide Feedback| D[API Developers] + D -->|Create Compliant APIs| B + B -->|Compliance Reporting| A[Enterprise Architects and Security Leads] +``` + +### Enterprise Architects & Security Leads + +Enterprise architects and security leads use Tyk Governance to establish organization-wide standards and ensure strategic alignment of the API program. They benefit from: + +* Centralized visibility across all API platforms +* Comprehensive compliance reporting +* The ability to define tiered governance policies based on API criticality +* Pre-built governance templates aligned with industry standards + +**Example:** An enterprise architect at a financial services company uses Tyk Governance to ensure all customer-facing APIs comply with security and regulatory requirements, while allowing internal APIs to follow a lighter governance model. + +### Platform Engineers + +Platform engineers leverage Tyk Governance to build and maintain internal developer platforms that streamline API development. They value: + +* Seamless integration of governance into CI/CD pipelines +* Self-service tools that empower developers +* Automated API discovery with scheduled synchronization + +**Example:** A platform engineer integrates Tyk Governance into Tyk Dashboard and other API platforms, providing API templates that automatically incorporate security best practices and compliance requirements. + +### API Developers + +API developers rely on Tyk Governance to design and implement APIs that meet organizational standards from day one. They appreciate: + +* Clear guidance on governance requirements +* Real-time feedback during development +* Reduced rework and faster release cycles + +**Example:** An API developer receives immediate feedback that their new payment API is missing required rate-limiting policies, allowing them to fix the issue before submitting for review. \ No newline at end of file diff --git a/tyk-identity-broker/dashboard-sso.mdx b/tyk-identity-broker/dashboard-sso.mdx new file mode 100644 index 0000000000..961f994eb2 --- /dev/null +++ b/tyk-identity-broker/dashboard-sso.mdx @@ -0,0 +1,327 @@ +--- +title: "Tyk Dashboard Single-Sign On" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard, allowing users to log in with their existing identity provider credentials." +keywords: "Tyk Dashboard, SSO, Single Sign-On, Tyk Identity Broker, TIB, Authentication, Identity Provider" +sidebarTitle: "Dashboard SSO" +--- + +Tyk Identity Broker (TIB) enables Single Sign-On (SSO) for Tyk Dashboard, allowing users to log in using their existing identity provider (IdP) credentials rather than with a Tyk Dashboard user account and password. + + +We recommend that you read the [Tyk Identity Broker overview](/tyk-identity-broker/overview) before configuring SSO for Dashboard. + + +## How It Works + +When a user logs in via SSO, TIB authenticates them against the configured IdP and then calls the Tyk Dashboard API to obtain a one-time nonce. TIB then redirects the user's browser to the Dashboard's `/tap` endpoint with the nonce appended. Tyk Dashboard validates the nonce and creates a session automatically. No further action is required from the user. + +```mermaid +sequenceDiagram + actor User + participant IDP as Identity Provider (IdP) + participant TIB as Tyk Identity Broker + participant Dashboard as Tyk Dashboard + + User->>TIB: Log in + TIB->>IDP: Verify identity + IDP->>TIB: Identity confirmed + TIB->>Dashboard: Request SSO nonce + Dashboard->>TIB: One-time nonce (valid 60 seconds) + TIB-->>User: Browser redirected to /tap?nonce=... + Note over User,Dashboard: Browser follows redirect automatically + Dashboard->>User: Session created, logged in +``` + +Tyk Dashboard identifies SSO users by their email address. + +What happens after authentication is controlled by two settings: + +| Setting | Configured in | Purpose | +|---|---|---| +| [`sso_enable_user_lookup`](/tyk-dashboard/configuration#sso_enable_user_lookup) | Tyk Dashboard configuration | Whether to match the user's email address against existing Tyk Dashboard accounts. | +| `SSOOnlyForRegisteredUsers` | TIB profile | Whether to deny login if no matching account is found. | + +```mermaid +flowchart LR + A[User authenticates via TIB] --> B{Look up existing account?} + B -- No --> C[Unregistered User Login] + B -- Yes --> D{Matching account found by email?} + D -- Yes --> E[Registered User Login] + D -- No --> F{Registered users only?} + F -- Yes --> G[Login Denied] + F -- No --> C +``` +### TIB Service Account + +TIB requires valid credentials to [generate the one-time nonce](https://tyk.io/docs/api-reference/single-sign-on/generate-authentication-token) from the Tyk Dashboard API. + +Typically you would create a dedicated **TIB service account** on the Dashboard and set its **Tyk Dashboard API Access Credentials** key (available from **System Management > Users** in the Dashboard UI) in the TIB profile. The service account does not need any special permissions, any active account is sufficient. + +### TIB Profile Management + +Dashboard users who need to create and manage TIB profiles require the **Identity Management (TIB)** [permission](/platform-management/user-permissions). + +## Profile Configuration + +Dashboard SSO uses the `GenerateOrLoginUserProfile` [action](/tyk-identity-broker/overview#actions) in the TIB profile. The following profile fields are required for all Dashboard SSO configurations: + +| Field | Value | +|---|---| +| `ActionType` | `GenerateOrLoginUserProfile` | +| `ReturnURL` | `http://{dashboard-host}/tap` | +| `IdentityHandlerConfig.DashboardCredential` | API key for the dedicated [TIB service account](#tib-service-account) | + +The `ProviderName`, `ProviderConfig`, and `Type` fields depend on your identity provider and are covered in the [IdP-specific guides](#set-up-sso-with-your-identity-provider). + +The user permission-related fields (`SSOOnlyForRegisteredUsers`, `UserGroupMapping`, `DefaultUserGroupID`, and `CustomUserGroupField`) are optional and used for [unregistered users](#unregistered-user-login). + +Two additional optional fields allow you to override which claims TIB reads from the IdP response: + +| Field | Description | +|---|---| +| `CustomEmailField` | The name of the IdP claim to use as the user's email address. If not set, TIB uses the standard email claim returned by the IdP. | +| `CustomUserIDField` | The name of the IdP claim to use as the user's unique identifier. If not set, TIB uses the standard subject or user ID claim. | + +These are useful when your IdP returns email or user ID under a non-standard claim name. + + +The `GenerateOrLoginUserProfile` action type is also used to log admin users into Tyk Developer Portal. See [SSO into Tyk Developer Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) for full details. + + +## Registered User Login + +You can track the activity of users and assign them individual permissions by enabling the Dashboard's **user lookup** system. + +1. Enable `sso_enable_user_lookup: true` in the Tyk Dashboard configuration: + + ```json + { + "sso_enable_user_lookup": true + } + ``` + +2. Create accounts for the users in Tyk Dashboard and set their permissions. + +When a user logs in via SSO, Tyk Dashboard looks for an account with a matching email address. If found, a session is created using that account's permissions. The account persists between logins, the user appears in the Dashboard user list, and their last login is recorded. If no matching account is found, the user is treated as an [Unregistered User Login](#unregistered-user-login). + +## Unregistered User Login + +By default, any user authenticated by the IdP is granted access: + +- Tyk Dashboard constructs a temporary in-memory user from the IdP data. +- No account is created in the user database. +- The user will not appear in the Dashboard user list and their last login is not recorded. + +There are three ways to configure permissions for unregistered users, which can be used individually or combined: + +| Option | Configured in | Purpose | +|---|---|---| +| [IdP Permission Claims](#idp-permission-claims) | TIB profile | Maps IdP group claims to Tyk Dashboard user groups, applying different permissions based on the user's IdP group membership. | +| [Default User Group](#default-user-group) | Tyk Dashboard configuration | Assigns a fallback [Tyk Dashboard user group](/platform-management/user-groups) when no group mapping applies. | +| [Default User](#default-user) | Tyk Dashboard configuration | Sets a baseline set of permissions for all unregistered SSO users. | + +When combined, the Default User Permissions provide the baseline and group permissions are merged on top. Where there is a conflict, the higher permission level wins. If the user is an [admin](/platform-management/user-permissions#admin-users), group permissions are skipped to preserve admin rights. + +### IdP Permission Claims + +When a user authenticates, the IdP returns a set of attributes about them, such as their name, email address, and group membership. TIB receives these attributes as a key-value map. + +The following TIB profile fields control how these attributes are mapped to Tyk Dashboard user groups: + +| Profile Field | Description | +|---|---| +| `CustomUserGroupField` | The key in the IdP attributes map that contains the user's group membership. | +| `UserGroupMapping` | Maps IdP group values to Tyk Dashboard user group IDs. | +| `DefaultUserGroupID` | The Tyk Dashboard user group to use when no mapping matches. | +| `UserGroupSeparator` | The separator character when a single IdP group claim contains multiple group values. | + +**Example: single group** + +The IdP returns the following attributes for the authenticated user: + +```json +{ + "email": "alice@example.com", + "groups": "developers" +} +``` + +The TIB profile is configured as follows: + +```json +{ + "CustomUserGroupField": "groups", + "UserGroupMapping": { + "developers": "{tyk-developer-group-id}", + "analytics": "{tyk-analytics-group-id}" + }, + "DefaultUserGroupID": "{tyk-default-group-id}" +} +``` + +TIB reads the `groups` claim, finds `"developers"` in `UserGroupMapping`, and the Dashboard applies the permissions of the `{tyk-developer-group-id}` user group to Alice's session. + +**Example: multiple groups** + +The IdP returns a comma-separated list of groups: + +```json +{ + "email": "alice@example.com", + "groups": "developers,analytics" +} +``` + +The TIB profile is configured as follows: + +```json +{ + "CustomUserGroupField": "groups", + "UserGroupMapping": { + "developers": "{tyk-developer-group-id}", + "analytics": "{tyk-analytics-group-id}" + }, + "DefaultUserGroupID": "{tyk-default-group-id}", + "UserGroupSeparator": "," +} +``` + +With `UserGroupSeparator` set to `","`, TIB splits the value in `groups` and checks for both values in the `UserGroupMapping`. The permissions from `{tyk-developer-group-id}` and `{tyk-analytics-group-id}` are merged, with the higher permission level winning any conflicts. + + +**Example: no matching group claims** + +The IdP returns the following attributes for the authenticated user: + +```json +{ + "email": "alice@example.com", + "groups": "sysadmin" +} +``` + +The TIB profile is configured as follows: + +```json +{ + "CustomUserGroupField": "groups", + "UserGroupMapping": { + "developers": "{tyk-developer-group-id}", + "analytics": "{tyk-analytics-group-id}" + }, + "DefaultUserGroupID": "{tyk-default-group-id}" +} +``` + +Alice's group claim (`sysadmin`) does not match any entry in the `UserGroupMapping` so she is assigned the permissions from `{tyk-default-group-id}`. + + +### Default User Group + + +If TIB sends no group information to the Dashboard and no `sso_default_group_id` is configured, Tyk Dashboard treats the user as an [admin](/platform-management/user-permissions#admin-users). This applies when neither IdP permission claims nor a default group is configured. + + +Setting [`sso_default_group_id`](/tyk-dashboard/configuration#sso_default_group_id) in the Tyk Dashboard config assigns all SSO users that TIB has not assigned to a group to a specific [user group](/platform-management/user-groups), from which they will inherit permissions. + + +`sso_default_group_id` must reference a valid, existing user group. If the group ID does not exist, the user will authenticate successfully but will have no permissions. The admin fallback is still prevented (because a group ID is set), but the user will be unable to perform any actions in Tyk Dashboard. + + +```json +{ + "sso_default_group_id": "{user-group-id}" +} +``` + +These permissions will be merged with any configured for the [default user](#default-user). + +### Default User + +In addition to the [default user group](#default-user-group) mechanism, setting [`sso_permission_defaults`](/tyk-dashboard/configuration#sso_permission_defaults) in the Tyk Dashboard config assigns SSO users that have not been assigned to a user group by TIB to a specific set of permissions, for example: + +```json +{ + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + } +} +``` + +These permissions will be merged with any configured for the [default user group](#default-user-group). + + +## Login Denied + +To restrict SSO login to registered users only, set both: + +- `sso_enable_user_lookup: true` in the Tyk Dashboard configuration +- `SSOOnlyForRegisteredUsers: true` in the TIB profile + +If no matching Dashboard account is found, login is denied. + + +Both flags must be set - if only one is set, the restriction has no effect. + + +## Create a TIB Profile using Dashboard UI + +TIB profiles are managed from the **Identity Management Profiles** page in **User Management > User Settings**. + +1. Select **Create Profile** + Creating a new TIB profile +2. Provide a **Name** for the profile - this will be stored as the profile `id` so must contain only alphanumeric characters and hyphens +3. Provide URLs to which users should be redirected on success and failure +4. Click **Next** +5. Choose the **Provider Type** (method) and click **Next** + Choose the TIB method (provider type) +6. Complete the configuration, which will depend upon the method chosen + Complete the TIB profile configuration +7. Click **Create Profile** + +You can view and edit the profile JSON directly from the **Raw Editor** view. + + TIB profile editor view + + +## Using Standalone TIB + +By default, Tyk Dashboard uses its embedded TIB for SSO from v3.0 onwards. If you need to use a standalone TIB instance instead, configure the `identity_broker` block in `tyk_analytics.conf`: + +```json +{ + "identity_broker": { + "enabled": false, //default: true + "host": { + "connection_string": "http://{tib-host}:{tib-port}", + "secret": "{tib-api-secret}" + }, + "ssl_insecure_skip_verify": false + } +} +``` + +| Setting | Description | +|---|---| +| `enabled` | Enables or disables the embedded TIB. Defaults to `true`. Set to `false` to use external TIB (or to disable SSO entirely). | +| `host.connection_string` | URL of the standalone TIB instance. When set, Tyk Dashboard proxies TIB API calls to this URL instead of using the embedded TIB. | +| `host.secret` | [Shared secret](/tyk-identity-broker/standalone-tib#management-api-secret) between Tyk Dashboard and the standalone TIB instance. Must match the `Secret` field in `tib.conf`. | +| `ssl_insecure_skip_verify` | Skip TLS verification when connecting to the standalone TIB instance. Not recommended for production. | + +For installation and configuration of standalone TIB, see [Install Standalone TIB](/tyk-identity-broker/standalone-tib). + +## Set Up SSO with Your Identity Provider + +Select your identity provider to get started: + +| Identity Provider | Guide | +|---|---| +| Microsoft Entra ID (OIDC or SAML), ADFS | [SSO with Microsoft Entra ID](/tyk-identity-broker/sso-entra-id) | +| Okta (OIDC or SAML) | [SSO with Okta](/tyk-identity-broker/sso-okta) | +| Auth0 | [SSO with Auth0](/tyk-identity-broker/sso-auth0) | +| Keycloak | [SSO with Keycloak](/tyk-identity-broker/sso-keycloak) | +| Active Directory, OpenLDAP | [SSO with LDAP](/api-management/single-sign-on-ldap) | +| Google, GitHub, LinkedIn, and other OAuth providers | [SSO with Social Providers](/api-management/single-sign-on-social-idp) | +| Custom or legacy authentication endpoints | [SSO with Proxy Provider](/api-management/custom-auth-with-proxy-identity-provider) | diff --git a/tyk-identity-broker/overview.mdx b/tyk-identity-broker/overview.mdx new file mode 100644 index 0000000000..630ba0fe98 --- /dev/null +++ b/tyk-identity-broker/overview.mdx @@ -0,0 +1,232 @@ +--- +title: "Tyk Identity Broker" +description: "Learn how to use Tyk Identity Broker to connect external identity providers to Tyk Dashboard, Tyk Developer Portal, and Tyk Gateway." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, Identity Provider, LDAP, OIDC, SAML, Authentication" +sidebarTitle: "Overview" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; + +## What is Tyk Identity Broker? + +Tyk Identity Broker (TIB) is a service that connects external identity providers (IdPs) to your Tyk installation. It handles the interaction with the IdP on your behalf, then on successful authentication instructs Tyk to take a specific action, such as logging a user into Tyk Dashboard or issuing an API access token. + +```mermaid +graph LR + User(["User"]) + + subgraph TIB["Tyk Identity Broker"] + Profile["Profile + ─────────── + Method + Action"] + end + + subgraph IDP["Identity Provider (IdP)"] + direction TB + Google["Google / GitHub"] + OIDC["OpenID Connect"] + SAML["SAML"] + LDAP["LDAP"] + end + + subgraph Tyk["Tyk"] + Dashboard["Tyk Dashboard"] + Portal["Tyk Developer Portal"] + Gateway["Tyk Gateway"] + end + + User -->|"Authentication request"| TIB + TIB <-->|"Validate identity"| IDP + TIB -->|"Execute action"| Tyk +``` + +## Use Cases + +TIB supports two distinct use cases, each with a different outcome. + +### Single Sign-On + +A human user wants to log in to Tyk Dashboard or Tyk Developer Portal using their existing IdP credentials. TIB handles the interaction with the IdP and creates a user session on the Tyk platform. + +```mermaid +sequenceDiagram + actor User + participant IDP as Identity Provider (IdP) + participant TIB as Tyk Identity Broker + participant Tyk as Tyk Dashboard / Portal + + User->>TIB: Log in + TIB->>IDP: Verify identity + IDP->>TIB: Identity confirmed + TIB->>Tyk: Create SSO user session + Tyk->>User: Logged in +``` + +### API Token Generation + +An API proxy deployed on Tyk Gateway is secured using [Auth Token](/api-management/authentication/bearer-token) or [OAuth 2.0](/api-management/authentication/oauth-2). A client application authenticates its users via an external IdP, receiving a dynamically generated access token that grants access to the API proxy. There is no need for an administrator to generate the access token. + +```mermaid +sequenceDiagram + actor User + participant IDP as Identity Provider (IdP) + participant TIB as Tyk Identity Broker + participant GW as Tyk Gateway / Dashboard + + User->>TIB: Authenticate + TIB->>IDP: Verify identity + IDP->>TIB: Identity confirmed + TIB->>GW: Generate token + GW->>User: API access token +``` + +## How TIB Works + +Every authentication flow in TIB is driven by three core concepts: a Profile that defines the flow, a Method that determines how TIB interacts with the IdP, and an Action that determines what happens on success. + +### Profile + +A *profile* is a configuration object stored in TIB that defines a complete authentication flow. It specifies how TIB should connect to the IdP, which [method](#method) to use and therefore what protocol to follow, which [action](#actions) to take on success, and where to send the user afterwards. + +Profiles are managed via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or, when using embedded TIB, through the Tyk Dashboard or Portal UI. + +Each profile has a unique ID, which forms part of the TIB authentication URL: + +``` +/auth/{id}/{provider} +``` + +`{id}` is the unique ID of the profile. `{provider}` identifies the IdP to authenticate against - its value depends on the [authentication method](#method) configured in the profile. + +When a user or application makes a request to that URL, TIB loads the corresponding profile based on the provided `{id}`. + +Once the profile has been loaded, TIB initiates authentication with the IdP using the configured [method](#method). + +### Method + +The method defines how TIB communicates with the IdP. TIB supports two flow types and four authentication methods, one per supported protocol. + +**Redirect flow** - TIB redirects the user's browser to the IdP login page. The IdP authenticates the user and redirects back to TIB with the result. TIB maintains state across the redirect using a signed session cookie. See [Session Cookie](#redirect-session-cookie) for configuration details. + +**Passthrough flow** - TIB validates the user's credentials directly against the IdP without a browser redirect. Credentials are submitted to TIB, which proxies the validation inline. + +| Method | `ProviderName` | Flow | +|---|---|---| +| Social | `SocialProvider` | Redirect | +| SAML | `SAMLProvider` | Redirect | +| LDAP | `ADProvider` | Passthrough | +| Proxy | `ProxyProvider` | Passthrough | + +#### Social (`SocialProvider`) + +Supports [OAuth 2.0](https://oauth.net/2/) and [OpenID Connect](https://openid.net/connect/)-compatible IdPs. Supports enterprise IdPs such as Auth0, Keycloak, Okta, and Microsoft Entra ID, as well as social logins such as Google, GitHub, and LinkedIn. `SocialProvider` also supports [JSON Web Encryption (JWE)](/api-management/single-sign-on-social-idp#json-web-encryption-jwe) for IdPs that encrypt their ID tokens. + +#### SAML (`SAMLProvider`) + +TIB acts as a [SAML 2.0](https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) Service Provider. The user is redirected to the IdP, which posts a signed assertion back to TIB. Commonly used with enterprise IdPs such as Microsoft Entra ID and ADFS. + +#### LDAP (`ADProvider`) + +TIB validates the user's credentials directly against an [LDAP](https://datatracker.ietf.org/doc/html/rfc4511) server such as Active Directory or OpenLDAP. No browser redirect is involved; credentials are submitted directly to TIB. + +#### Proxy (`ProxyProvider`) + +TIB forwards the authentication request to an external HTTP endpoint and treats the response as the authentication result. Useful for integrating with custom or legacy authentication systems. + +### Provider Configuration + +Each method requires IdP-specific connection settings in the `ProviderConfig` block of the profile. This is where you configure the details TIB needs to communicate with your IdP, for example the callback URL, the client credentials, and the discovery endpoint. + +The structure of `ProviderConfig` depends entirely on the method: + +| Method | Key `ProviderConfig` fields | +|---|---| +| `SocialProvider` | `CallbackBaseURL`, `FailureRedirect`, `UseProviders` (array containing `Name`, `Key`, `Secret`, and optionally `DiscoverURL` for OIDC) | +| `SAMLProvider` | `SAMLBaseURL`, `IDPMetadataURL`, `CertLocation`, `FailureRedirect` | +| `ADProvider` | `LDAPServer`, `LDAPPort`, `LDAPUserDN`, `FailureRedirect` | +| `ProxyProvider` | `TargetHost`, `OKCode`, `OKResponse`, `OKRegex` | + +For the full set of fields for each method, see the [Identity Provider guides](#what-would-you-like-to-do). + +### Actions + +The *action* defines what TIB should do after the IdP has confirmed the user's identity and is configured using the `ActionType` field in the profile. + +There are four actions, grouped by [use case](#use-cases): + +**Single Sign-On** + +| `ActionType` | Target | Behavior | +|---|---|---| +| `GenerateOrLoginUserProfile` | Tyk Dashboard or Tyk Developer Portal (admin users) | Logs a user into [Tyk Dashboard](/tyk-identity-broker/dashboard-sso) or the Developer Portal's [Admin Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). | +| `GenerateOrLoginDeveloperProfile` | Tyk Developer Portal (API consumer users) | Logs a user into the Developer Portal's [Live Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). | + +For full details of the SSO use case, see: +- [Single Sign-On for Tyk Dashboard](/tyk-identity-broker/dashboard-sso) +- [Single Sign-On for Tyk Developer Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) + +**API Token Generation** + +| `ActionType` | Token type | Behavior | +|---|---|---| +| `GenerateTemporaryAuthToken` | Auth Token | Tyk generates a [Session](/api-management/access-control/sessions-and-keys/understanding-sessions) and returns the Key (auth token) to the client application. | +| `GenerateOAuthTokenForClient` | OAuth 2.0 token | Uses the configured OAuth client to obtain an OAuth 2.0 token from Tyk Gateway's built-in authorization server on behalf of the authenticated user, and returns it to the client application via a redirect. | + +For full details of the API Token use case, see [Issuing Tokens using TIB](/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib). + +## Embedded vs Standalone TIB + +TIB is embedded in Tyk Dashboard (from v3.0) and Tyk Developer Portal (from v1.12.0). For most use cases you do not need to install or run TIB separately. + +Standalone TIB is required only in the following situations: + +- You are using TIB to issue API access tokens via an external IdP, which involves TIB communicating directly with Tyk Gateway. +- You are running a version of Tyk Dashboard or Tyk Developer Portal that pre-dates the embedded TIB integration. +- You have a specific infrastructure requirement that prevents use of the embedded TIB. + +If you need to run standalone TIB, see [Install Standalone TIB](/tyk-identity-broker/standalone-tib). + +## Redirect Session Cookie + +When using a [method](#method) that uses the redirect flow (`SocialProvider` or `SAMLProvider`), TIB needs to maintain state across the browser round-trip to the IdP and back, for example which profile is being used. It does this by storing a session cookie in the user's browser. + +The cookie is signed using HMAC-SHA256 with a secret key, which prevents it from being tampered with. The content of the cookie is base64-encoded but not encrypted, so it should not contain sensitive data. The signing key is configured via the `TYK_IB_SESSION_SECRET` environment variable. + +When using redirect flow with: + +- Embedded TIB in Tyk Dashboard, if `TYK_IB_SESSION_SECRET` is not explicitly set, Tyk Dashboard automatically falls back to its own [`admin_secret`](/tyk-dashboard/configuration#admin_secret). +- Embedded TIB in Tyk Developer Portal, no fallback is applied and `TYK_IB_SESSION_SECRET` must be set. +- Standalone TIB, `TYK_IB_SESSION_SECRET` must always be set. + +Setting `TYK_IB_SESSION_SECRET` explicitly is recommended in all production deployments. + +## What Would You Like to Do? + + + + +Log users into Tyk Dashboard using an external identity provider. + + + +Log users into Tyk Developer Portal using an external identity provider. + + + +Use TIB to authenticate users against an external IdP and issue auth tokens or OAuth tokens on their behalf. + + + +Install TIB when you don't want to or can't use it embedded in Tyk Dashboard or Tyk Developer Portal. + + + +Manage TIB programmatically using the Tyk Identity Broker API. + + + +Full reference for the TIB configuration file (tib.conf) and environment variables. + + + diff --git a/tyk-identity-broker/sso-auth0.mdx b/tyk-identity-broker/sso-auth0.mdx new file mode 100644 index 0000000000..a99a2f9845 --- /dev/null +++ b/tyk-identity-broker/sso-auth0.mdx @@ -0,0 +1,251 @@ +--- +title: "SSO with Auth0" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard or Tyk Developer Portal using Auth0 via OpenID Connect (OIDC), with worked examples for Dashboard and Portal." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, Auth0, OIDC, OpenID Connect, Authentication" +sidebarTitle: "Auth0" +--- + +## Introduction + +[Auth0](https://auth0.com/) is an OIDC-compatible identity provider. TIB connects to Auth0 using `SocialProvider` with the `openid-connect` provider type. + +Before configuring your IdP and TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +This page covers the Auth0-specific configuration only. + +## Configure Auth0 + +1. Log in to the [Auth0 Dashboard](https://manage.auth0.com/) and navigate to **Applications > Applications**. +2. Click **Create Application**, give it a name, select **Regular Web Application**, and click **Create**. + Auth0 Application information +3. From the application's **Settings** tab, note the **Domain**, **Client ID**, and **Client Secret**. You will need all three for the TIB profile. + Auth0 Application Basic information +4. In the **Allowed Callback URLs** field, add the TIB callback URL: + ``` + http://{tib-host}/auth/{profile-id}/openid-connect/callback + ``` + Replace `{tib-host}` with the hostname of your TIB instance and `{profile-id}` with the ID you will assign to the TIB profile. +5. Click **Save Changes**. + +The Auth0 OIDC discovery URL for your tenant is: +``` +https://{auth0-domain}/.well-known/openid-configuration +``` + +Where `{auth0-domain}` is the **Domain** value from your Auth0 application settings (for example, `your-tenant.auth0.com`). + +## TIB Profile + +The Auth0-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SocialProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + "CallbackBaseURL": "http://{tib-host}", + "FailureRedirect": "http://{failure-redirect-url}", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{auth0-client-id}", + "Secret": "{auth0-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{auth0-domain}/.well-known/openid-configuration" + } + ] + } +} +``` + +The Auth0-specific `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `CallbackBaseURL` | The base URL of your TIB instance. TIB appends the callback path automatically. | +| `FailureRedirect` | URL to redirect the user to on authentication failure. | +| `UseProviders.Name` | Must be `openid-connect`. This value routes TIB to the OpenID Connect provider implementation. | +| `UseProviders.Key` | The Auth0 Client ID. | +| `UseProviders.Secret` | The Auth0 Client Secret. | +| `UseProviders.Scopes` | OAuth scopes to request. `openid` and `email` are required. | +| `UseProviders.DiscoverURL` | The Auth0 OIDC discovery URL for your tenant. | + +### JSON Web Encryption (JWE) + +If Auth0 is configured to encrypt ID tokens, TIB can decrypt them using JWE. Add a `JWE` block to `ProviderConfig` to enable this: + +```json +{ + "ProviderConfig": { + "UseProviders": [...], + "JWE": { + "Enabled": true, + "PrivateKeyLocation": "{certificate-id-or-path}" + } + } +} +``` + +For embedded TIB in Tyk Dashboard, set `PrivateKeyLocation` to the certificate ID from the Tyk Dashboard certificate manager. For standalone TIB, set it to the file path of a PEM file containing the private key. The key must correspond to the public key registered with Auth0 for token encryption. + +Requires Tyk Identity Broker v1.6.1+ and Tyk Dashboard v5.7.0+. + +## Worked Examples + +These examples use embedded TIB, so the `CallbackBaseURL` is the same as the Dashboard or Portal respectively; TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "auth0-dashboard-oidc", + "Name": "Auth0 Dashboard SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://dashboard.example.com:3000", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{auth0-client-id}", + "Secret": "{auth0-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{auth0-domain}/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `Key` to the Auth0 **Client ID** +- set `Secret` to the Auth0 **Client Secret** +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Auth0 callback URL** + +Ensure the following URL is listed in **Allowed Callback URLs** in your Auth0 application settings. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://dashboard.example.com:3000/auth/auth0-dashboard-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/auth0-dashboard-oidc/openid-connect +``` + +In production, present this as a "Log in with Auth0" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "auth0-portal-oidc", + "Name": "Auth0 Portal SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://portal.example.com:3001", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{auth0-client-id}", + "Secret": "{auth0-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{auth0-domain}/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `Key` to the Auth0 **Client ID** +- set `Secret` to the Auth0 **Client Secret** +- set `DashboardCredential` to the [PORTAL_API_SECRET](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Auth0 callback URL** + +Ensure the following URL is listed in **Allowed Callback URLs** in your Auth0 application settings. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://portal.example.com:3001/auth/auth0-portal-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/auth/auth0-portal-oidc/openid-connect +``` + +In production, present this as a "Log in with Auth0" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + diff --git a/tyk-identity-broker/sso-entra-id.mdx b/tyk-identity-broker/sso-entra-id.mdx new file mode 100644 index 0000000000..781626eac3 --- /dev/null +++ b/tyk-identity-broker/sso-entra-id.mdx @@ -0,0 +1,493 @@ +--- +title: "SSO with Microsoft Entra ID" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard or Tyk Developer Portal using Microsoft Entra ID, via OpenID Connect or SAML." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, Microsoft Entra ID, Azure AD, OIDC, SAML, Authentication" +sidebarTitle: "Microsoft Entra ID" +--- + +## Introduction + +[Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) (formerly Azure AD) supports both [OpenID Connect (OIDC)](#sso-with-openid-connect) and [SAML 2.0](#sso-with-saml). + +- For most new deployments, OIDC is recommended as it is simpler to configure and is Microsoft's preferred modern authentication protocol. +- Use SAML if your organization requires it for policy or compatibility reasons. + +Before configuring your IdP and TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +This page covers the Entra ID-specific configuration only. + +## SSO with OpenID Connect + +### Configure Entra ID + +1. In the Azure Portal, navigate to **Microsoft Entra ID** and select **App registrations**. +2. Select **New registration**. Give the application a name and register it. +3. From the app's **Overview** page, note the **Application (client) ID** and **Directory (tenant) ID**. You will need both for the TIB profile. +4. Navigate to **Certificates and secrets** and create a new **Client secret**. Copy the secret **Value** (not the Secret ID) immediately as it will not be shown again. + Entra ID app registration Overview page showing Application (client) ID and Directory (tenant) ID +5. Navigate to **Authentication** and add a **Redirect URI** of type **Web**. Set it to: + ``` + http://{tib-host}/auth/{profile-id}/openid-connect/callback + ``` + Replace `{tib-host}` with the hostname of your TIB instance and `{profile-id}` with the ID you will assign to the TIB profile. + + Redirect URL + +The Entra ID OIDC discovery URL for your tenant is: +``` +https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration +``` + +### TIB Profile + +The Entra ID-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SocialProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + "CallbackBaseURL": "http://{tib-host}", + "FailureRedirect": "http://{failure-redirect-url}", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{entra-client-id}", + "Secret": "{entra-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" + } + ] + } +} +``` + +The Entra ID-specific `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `CallbackBaseURL` | The base URL of your TIB instance. TIB appends the callback path automatically. | +| `FailureRedirect` | URL to redirect the user to on authentication failure. | +| `UseProviders.Name` | Must be `openid-connect`. This value routes TIB to the OpenID Connect provider implementation. | +| `UseProviders.Key` | The Entra ID Application (client) ID. | +| `UseProviders.Secret` | The Entra ID client secret value. | +| `UseProviders.Scopes` | OAuth scopes to request. `openid` and `email` are required. | +| `UseProviders.DiscoverURL` | The Entra ID OIDC discovery URL for your tenant. | + +#### Custom Email Claim + +Tyk Dashboard identifies SSO users by their email address. If your Entra ID configuration does not return the user's email address in the standard `email` claim, you must specify the correct claim name using the `CustomEmailField` property at the root of the TIB profile. + +This is often required for Entra ID when the email address is returned in the `preferred_username` or `upn` claims instead of `email`. + +```json +{ + "CustomEmailField": "preferred_username", + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + // ... + } +} +``` + +For more details on profile configuration, see the [Dashboard SSO](/tyk-identity-broker/dashboard-sso#profile-configuration) documentation. + +#### JSON Web Encryption (JWE) + +If Entra ID is configured to encrypt ID tokens, TIB can decrypt them using JWE. Add a `JWE` block to `ProviderConfig` to enable this: + +```json +{ + "ProviderConfig": { + "UseProviders": [...], + "JWE": { + "Enabled": true, + "PrivateKeyLocation": "{certificate-id-or-path}" + } + } +} +``` + +For embedded TIB in Tyk Dashboard, set `PrivateKeyLocation` to the certificate ID from the Tyk Dashboard certificate manager. For standalone TIB, set it to the file path of a PEM file containing the private key. The key must correspond to the public key registered with Entra ID for token encryption. + +Requires Tyk Identity Broker v1.6.1+ and Tyk Dashboard v5.7.0+. + +### Worked Examples (OIDC) + +These examples use embedded TIB, so the `CallbackBaseURL` is the same as the Dashboard or Portal respectively; TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "entra-dashboard-oidc", + "Name": "Entra ID Dashboard SSO (OIDC)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://dashboard.example.com:3000", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{entra-client-id}", + "Secret": "{entra-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `Key` to the Entra ID **Application (client) ID** +- set `Secret` to the Entra ID client secret **Value** +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Entra ID redirect URI** + +Ensure the following URL is listed in your Entra ID app registration under **Authentication > Redirect URIs**. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://dashboard.example.com:3000/auth/entra-dashboard-oidc/openid-connect/callback +``` + + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/entra-dashboard-oidc/openid-connect +``` + +In production, present this as a "Log in with Entra ID" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "entra-portal-oidc", + "Name": "Entra ID Portal SSO (OIDC)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://portal.example.com:3001", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{entra-client-id}", + "Secret": "{entra-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `Key` to the Entra ID **Application (client) ID** +- set `Secret` to the Entra ID client secret **Value** +- set `DashboardCredential` to the [PORTAL_API_SECRET](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Entra ID redirect URI** + +Ensure the following URL is listed in your Entra ID app registration under **Authentication > Redirect URIs**. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://portal.example.com:3001/auth/entra-portal-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/auth/entra-portal-oidc/openid-connect +``` + +In production, present this as a "Log in with Entra ID" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + + +## SSO with SAML + +### Configure Entra ID + +1. In the Azure Portal, navigate to **Microsoft Entra ID** and select **Enterprise applications**. +2. Select **New application** and then **Create your own application**. Give it a name and select **Integrate any other application you don't find in the gallery**. +3. Navigate to **Single sign-on** and select **SAML**. +4. TIB exposes a SAML Service Provider (SP) metadata endpoint for each profile. For embedded TIB, this is on the same host as the Tyk Dashboard (or Developer Portal), for example: + ``` + http://dashboard.example.com:3000/auth/{profile-id}/saml/metadata + ``` + You can use this URL to configure Entra ID automatically, or manually set the **Entity ID** and **Reply URL (ACS URL)**. The ACS URL is: + ``` + http://dashboard.example.com:3000/auth/{profile-id}/saml/callback + ``` +5. From the **SAML Certificates** section, copy the **App Federation Metadata URL**. You will need this for the TIB profile `IDPMetadataURL` field. +6. Under **Attributes and Claims**, ensure the email claim is mapped. By default Entra ID maps email to `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`. + +### TIB Profile + +The Entra ID-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SAMLProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SAMLProvider", + "Type": "redirect", + "ProviderConfig": { + "SAMLBaseURL": "http://{tib-host}", + "IDPMetadataURL": "https://login.microsoftonline.com/{tenant-id}/federationmetadata/2007-06/federationmetadata.xml", + "CertLocation": "/path/to/sp-cert-and-key.pem", + "FailureRedirect": "http://{failure-redirect-url}", + "SAMLEmailClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + "SAMLForenameClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", + "SAMLSurnameClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", + "ForceAuthentication": false + } +} +``` + +The Entra ID-specific `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `SAMLBaseURL` | The base URL of your TIB instance. Used to construct the SP metadata and ACS URLs. | +| `IDPMetadataURL` | The Entra ID federation metadata URL for your tenant. | +| `CertLocation` | Path to a PEM file containing the SP certificate and private key concatenated. When using Tyk Dashboard with embedded TIB, this can be a certificate ID from the Tyk Certificate Store. | +| `SAMLEmailClaim` | The SAML claim name for the user's email address. | +| `SAMLForenameClaim` | The SAML claim name for the user's first name. | +| `SAMLSurnameClaim` | The SAML claim name for the user's last name. | +| `ForceAuthentication` | Set to `true` to force Entra ID to re-authenticate the user on every request, ignoring any existing session. | + +### Worked Examples (SAML) + +These examples use embedded TIB, so `SAMLBaseURL` is the same as the Dashboard or Portal respectively. TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**Certificate** + +Upload the Service Provider certificate pair that will be used to sign SAML requests to the Tyk Certificate Store (**API Security > TLS/SSL Certificates**), noting the assigned certificate ID to be used in the TIB profile. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "entra-dashboard-saml", + "Name": "Entra ID Dashboard SSO (SAML)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SAMLProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "SAMLBaseURL": "http://dashboard.example.com:3000", + "IDPMetadataURL": "https://login.microsoftonline.com/{tenant-id}/federationmetadata/2007-06/federationmetadata.xml", + "CertLocation": "{certificate-id-from-dashboard}", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "SAMLEmailClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + "SAMLForenameClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", + "SAMLSurnameClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", + "ForceAuthentication": false + } +} +``` + +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials +- set `CertLocation` to the certificate ID for the SAML SP certificate + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/entra-dashboard-saml/saml +``` + +In production, present this as a "Log in with Entra ID" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**Certificate** + +Unlike the Dashboard, the Portal's embedded TIB loads certificates from the filesystem rather than from a certificate store. Set `CertLocation` to the file path of a PEM file containing the SP certificate and private key concatenated, accessible on the server running the Portal. + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "entra-portal-saml", + "Name": "Entra ID Portal SSO (SAML)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SAMLProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "SAMLBaseURL": "http://portal.example.com:3001", + "IDPMetadataURL": "https://login.microsoftonline.com/{tenant-id}/federationmetadata/2007-06/federationmetadata.xml", + "CertLocation": "/path/to/sp-cert-and-key.pem", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "SAMLEmailClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + "SAMLForenameClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", + "SAMLSurnameClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", + "ForceAuthentication": false + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `CertLocation` to the file path of the SP certificate PEM file on the Portal server +- set `DashboardCredential` to the [PORTAL_API_SECRET](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/auth/entra-portal-saml/saml +``` + +In production, present this as a "Log in with Entra ID" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + + +## Active Directory Federation Services (ADFS) + +Active Directory Federation Services (ADFS) is Microsoft's on-premises federation service. It uses the same SAML 2.0 protocol as Entra ID. Follow the [SSO with SAML](#sso-with-saml) instructions above, with the following differences: + +**Configure ADFS (replaces "Configure Entra ID")** + +Instead of configuring an Enterprise Application in the Azure Portal, configure a Relying Party Trust in the ADFS management console. Provide the TIB SP metadata URL to configure the trust automatically: + +``` +http://{tib-host}/auth/{profile-id}/saml/metadata +``` + +**TIB profile differences** + +- `IDPMetadataURL` - use the ADFS federation metadata endpoint instead: + ``` + https://{adfs-host}/FederationMetadata/2007-06/FederationMetadata.xml + ``` +- `SAMLEmailClaim`, `SAMLForenameClaim`, `SAMLSurnameClaim` - ADFS claim URIs depend on your claim issuance policy and may differ from the Entra ID defaults. Check the claims configured in your ADFS Relying Party Trust and update these values accordingly. + +All other profile fields, worked examples, and login URL patterns are identical to the SAML section above. diff --git a/tyk-identity-broker/sso-keycloak.mdx b/tyk-identity-broker/sso-keycloak.mdx new file mode 100644 index 0000000000..2b5e46a743 --- /dev/null +++ b/tyk-identity-broker/sso-keycloak.mdx @@ -0,0 +1,259 @@ +--- +title: "SSO with Keycloak" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard or Tyk Developer Portal using Keycloak via OpenID Connect (OIDC), with worked examples for Dashboard and Portal." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, Keycloak, OIDC, OpenID Connect, Authentication" +sidebarTitle: "Keycloak" +--- + +## Introduction + +[Keycloak](https://www.keycloak.org/) is an open-source identity provider that supports OpenID Connect. TIB connects to Keycloak using `SocialProvider` with the `openid-connect` provider type. + +Before configuring your IdP and TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +This page covers the Keycloak-specific configuration only. + +## Configure Keycloak + +1. In your Keycloak Admin Console, navigate to the realm you want to use and select **Clients**. + Create Client + +2. Click **Create client**, set the **Client type** to **OpenID Connect**, enter a **Client ID**, and click **Next**. + Set Client Type and ID + +3. Enable **Client authentication** and click **Next**, then **Save**. + Enable Client Auth + +4. From the client's **Credentials** tab, copy the **Client Secret**. + Retrieve Client Secret + +5. From the client's **Settings** tab, add the TIB callback URL to **Valid redirect URIs**: + ``` + http://{tib-host}/auth/{profile-id}/openid-connect/callback + ``` + Replace `{tib-host}` with the hostname of your TIB instance and `{profile-id}` with the ID you will assign to the TIB profile. +6. Click **Save**. + +The Keycloak OIDC discovery URL for your realm is accessible from **Realm Settings > General > OpenID Endpoint Configuration**: +``` +https://{keycloak-host}/realms/{realm-name}/.well-known/openid-configuration +``` + + Keycloak discovery endpoint + + +## TIB Profile + +The Keycloak-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SocialProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + "CallbackBaseURL": "http://{tib-host}", + "FailureRedirect": "http://{failure-redirect-url}", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{keycloak-client-id}", + "Secret": "{keycloak-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{keycloak-host}/realms/{realm-name}/.well-known/openid-configuration" + } + ] + } +} +``` + +The Keycloak-specific `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `CallbackBaseURL` | The base URL of your TIB instance. TIB appends the callback path automatically. | +| `FailureRedirect` | URL to redirect the user to on authentication failure. | +| `UseProviders.Name` | Must be `openid-connect`. This value routes TIB to the OpenID Connect provider implementation. | +| `UseProviders.Key` | The Keycloak Client ID. | +| `UseProviders.Secret` | The Keycloak Client Secret. | +| `UseProviders.Scopes` | OAuth scopes to request. `openid` and `email` are required. | +| `UseProviders.DiscoverURL` | The Keycloak OIDC discovery URL for your realm. | + +### JSON Web Encryption (JWE) + +If Keycloak is configured to encrypt ID tokens, TIB can decrypt them using JWE. Add a `JWE` block to `ProviderConfig` to enable this: + +```json +{ + "ProviderConfig": { + "UseProviders": [...], + "JWE": { + "Enabled": true, + "PrivateKeyLocation": "{certificate-id-or-path}" + } + } +} +``` + +For embedded TIB in Tyk Dashboard, set `PrivateKeyLocation` to the certificate ID from the Tyk Dashboard certificate manager. For standalone TIB, set it to the file path of a PEM file containing the private key. The key must correspond to the public key registered with Keycloak for token encryption. + +Requires Tyk Identity Broker v1.6.1+ and Tyk Dashboard v5.7.0+. + +## Worked Examples + +These examples use embedded TIB, so the `CallbackBaseURL` is the same as the Dashboard or Portal respectively; TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "keycloak-dashboard-oidc", + "Name": "Keycloak Dashboard SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://dashboard.example.com:3000", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{keycloak-client-id}", + "Secret": "{keycloak-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{keycloak-host}/realms/{realm-name}/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `Key` to the Keycloak **Client ID** +- set `Secret` to the Keycloak **Client Secret** +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Keycloak redirect URI** + +Ensure the following URL is listed in **Valid redirect URIs** in your Keycloak client settings. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://dashboard.example.com:3000/auth/keycloak-dashboard-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/keycloak-dashboard-oidc/openid-connect +``` + +In production, present this as a "Log in with Keycloak" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "keycloak-portal-oidc", + "Name": "Keycloak Portal SSO", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://portal.example.com:3001", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{keycloak-client-id}", + "Secret": "{keycloak-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{keycloak-host}/realms/{realm-name}/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `Key` to the Keycloak **Client ID** +- set `Secret` to the Keycloak **Client Secret** +- set `DashboardCredential` to the [PORTAL_API_SECRET](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Keycloak redirect URI** + +Ensure the following URL is listed in **Valid redirect URIs** in your Keycloak client settings. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://portal.example.com:3001/auth/keycloak-portal-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/auth/keycloak-portal-oidc/openid-connect +``` + +In production, present this as a "Log in with Keycloak" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + diff --git a/tyk-identity-broker/sso-okta.mdx b/tyk-identity-broker/sso-okta.mdx new file mode 100644 index 0000000000..0e6da3e915 --- /dev/null +++ b/tyk-identity-broker/sso-okta.mdx @@ -0,0 +1,448 @@ +--- +title: "SSO with Okta" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Dashboard or Tyk Developer Portal using Okta, via OpenID Connect or SAML." +keywords: "Tyk Identity Broker, TIB, SSO, Single Sign-On, Okta, OIDC, SAML, Authentication" +sidebarTitle: "Okta" +--- + +## Introduction + +[Okta](https://www.okta.com/) supports both [OpenID Connect (OIDC)](#sso-with-openid-connect) and [SAML 2.0](#sso-with-saml). + +- For most new deployments, OIDC is recommended as it is simpler to configure. +- Use SAML if your organization requires it for policy or compatibility reasons. + +Before configuring your IdP and TIB profile, read [Dashboard SSO](/tyk-identity-broker/dashboard-sso) or [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) to understand the `ActionType`, `ReturnURL`, and `IdentityHandlerConfig` fields required for your use case. + +This page covers the Okta-specific configuration only. + +## SSO with OpenID Connect + +### Configure Okta + +1. Log in to your Okta Admin Console and navigate to **Applications > Applications**. +2. Click **Create App Integration**, select **OIDC - OpenID Connect** as the sign-in method and **Web Application** as the application type, then click **Next**. +3. Give the application a name. +4. Under **Sign-in redirect URIs**, add the TIB callback URL: + ``` + http://{tib-host}/auth/{profile-id}/openid-connect/callback + ``` + Replace `{tib-host}` with the hostname of your TIB instance and `{profile-id}` with the ID you will assign to the TIB profile. +5. Under **Assignments**, configure which users or groups can access the application. +6. Click **Save**. +7. From the application's **General** tab, note the **Client ID** and **Client Secret**. + +The Okta OIDC discovery URL for your org is: +``` +https://{okta-domain}/.well-known/openid-configuration +``` + +Where `{okta-domain}` is your Okta org domain (for example, `your-org.okta.com`). If you are using a custom Authorization Server, the discovery URL is: +``` +https://{okta-domain}/oauth2/{auth-server-id}/.well-known/openid-configuration +``` + +### TIB Profile + +The Okta-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SocialProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SocialProvider", + "Type": "redirect", + "ProviderConfig": { + "CallbackBaseURL": "http://{tib-host}", + "FailureRedirect": "http://{failure-redirect-url}", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{okta-client-id}", + "Secret": "{okta-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{okta-domain}/.well-known/openid-configuration" + } + ] + } +} +``` + +The Okta-specific `ProviderConfig` fields are: + +| Field | Description | +|---|---| +| `CallbackBaseURL` | The base URL of your TIB instance. TIB appends the callback path automatically. | +| `FailureRedirect` | URL to redirect the user to on authentication failure. | +| `UseProviders.Name` | Must be `openid-connect`. This value routes TIB to the OpenID Connect provider implementation. | +| `UseProviders.Key` | The Okta Client ID. | +| `UseProviders.Secret` | The Okta Client Secret. | +| `UseProviders.Scopes` | OAuth scopes to request. `openid` and `email` are required. | +| `UseProviders.DiscoverURL` | The Okta OIDC discovery URL for your org or Authorization Server. | + +#### JSON Web Encryption (JWE) + +If Okta is configured to encrypt ID tokens, TIB can decrypt them using JWE. Add a `JWE` block to `ProviderConfig` to enable this: + +```json +{ + "ProviderConfig": { + "UseProviders": [...], + "JWE": { + "Enabled": true, + "PrivateKeyLocation": "{certificate-id-or-path}" + } + } +} +``` + +For embedded TIB in Tyk Dashboard, set `PrivateKeyLocation` to the certificate ID from the Tyk Dashboard certificate manager. For standalone TIB, set it to the file path of a PEM file containing the private key. The key must correspond to the public key registered with Okta for token encryption. + +Requires Tyk Identity Broker v1.6.1+ and Tyk Dashboard v5.7.0+. + +### Worked Examples (OIDC) + +These examples use embedded TIB, so the `CallbackBaseURL` is the same as the Dashboard or Portal respectively; TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "okta-dashboard-oidc", + "Name": "Okta Dashboard SSO (OIDC)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://dashboard.example.com:3000", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{okta-client-id}", + "Secret": "{okta-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{okta-domain}/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `Key` to the Okta **Client ID** +- set `Secret` to the Okta **Client Secret** +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials + +**Okta redirect URI** + +Ensure the following URL is listed in **Sign-in redirect URIs** in your Okta application settings. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://dashboard.example.com:3000/auth/okta-dashboard-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/okta-dashboard-oidc/openid-connect +``` + +In production, present this as a "Log in with Okta" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "okta-portal-oidc", + "Name": "Okta Portal SSO (OIDC)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SocialProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "CallbackBaseURL": "http://portal.example.com:3001", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "UseProviders": [ + { + "Name": "openid-connect", + "Key": "{okta-client-id}", + "Secret": "{okta-client-secret}", + "Scopes": ["openid", "email", "profile"], + "DiscoverURL": "https://{okta-domain}/.well-known/openid-configuration" + } + ] + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `Key` to the Okta **Client ID** +- set `Secret` to the Okta **Client Secret** +- set `DashboardCredential` to the [PORTAL_API_SECRET](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Okta redirect URI** + +Ensure the following URL is listed in **Sign-in redirect URIs** in your Okta application settings. The `ID` in the registered URL must exactly match the `ID` in your TIB profile; a mismatch will result in a `400 Bad Request` error: + +``` +http://portal.example.com:3001/auth/okta-portal-oidc/openid-connect/callback +``` + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/auth/okta-portal-oidc/openid-connect +``` + +In production, present this as a "Log in with Okta" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + + +## SSO with SAML + +### Configure Okta + +1. In the Okta Admin Console, navigate to **Applications > Applications** and click **Create App Integration**. +2. Select **SAML 2.0** as the sign-in method and click **Next**. +3. Give the application a name and click **Next**. +4. In the **SAML Settings** section, set the following. For embedded TIB, the values are based on your Dashboard or Portal host: + - **Single sign-on URL** (ACS URL): `http://{tib-host}/auth/{profile-id}/saml/callback` + - **Audience URI (SP Entity ID)**: `http://{tib-host}/auth/{profile-id}/saml/metadata` +5. Under **Attribute Statements**, map the email attribute. Add a statement with name `email` and value `user.email`. +6. Click **Next**, complete the feedback form, and click **Finish**. +7. From the application's **Sign On** tab, copy the **Metadata URL**. You will need this for `IDPMetadataURL` in the TIB profile. + +### TIB Profile + +The Okta-specific configuration goes in the `ProviderConfig` block of the TIB profile. Set `ProviderName` to `SAMLProvider` and `Type` to `redirect`. + +```json expandable +{ + "ProviderName": "SAMLProvider", + "Type": "redirect", + "ProviderConfig": { + "SAMLBaseURL": "http://{tib-host}", + "IDPMetadataURL": "https://{okta-domain}/app/{app-id}/sso/saml/metadata", + "CertLocation": "/path/to/sp-cert-and-key.pem", + "FailureRedirect": "http://{failure-redirect-url}", + "SAMLEmailClaim": "email", + "SAMLForenameClaim": "firstName", + "SAMLSurnameClaim": "lastName", + "ForceAuthentication": false + } +} +``` + +| Field | Description | +|---|---| +| `SAMLBaseURL` | The base URL of your TIB instance. Used to construct the SP metadata and ACS URLs. | +| `IDPMetadataURL` | The Okta application SAML metadata URL from step 7. | +| `CertLocation` | Path to a PEM file containing the SP certificate and private key concatenated. When using Tyk Dashboard with embedded TIB, this can be a certificate ID from the Tyk Certificate Store. | +| `SAMLEmailClaim` | The SAML attribute name for the user's email address, as configured in Okta's Attribute Statements. | +| `SAMLForenameClaim` | The SAML attribute name for the user's first name. | +| `SAMLSurnameClaim` | The SAML attribute name for the user's last name. | +| `ForceAuthentication` | Set to `true` to force Okta to re-authenticate the user on every request. | + +### Worked Examples (SAML) + +These examples use embedded TIB, so `SAMLBaseURL` is the same as the Dashboard or Portal respectively; TIB handles requests on the same host and port. + + + + +In this example, Tyk Dashboard is running at `http://dashboard.example.com` on port `3000`; replace the example values with your own. + +**Tyk Dashboard configuration** + +```json +{ + "sso_enable_user_lookup": true, + "sso_permission_defaults": { + "apis": "write", + "keys": "write", + "policies": "write" + }, + "sso_default_group_id": "{tyk-user-group-id}" +} +``` + +With this configuration, registered users (with a Tyk Dashboard user account) get their own permissions; unregistered users fall back to the group specified in `sso_default_group_id`. See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for full details. + +**Certificate** + +Upload the Service Provider certificate pair to the Tyk Certificate Store (**API Security > TLS/SSL Certificates**), noting the assigned certificate ID to be used in the TIB profile. + +**TIB profile** + +The TIB profile is created via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) or the [Tyk Dashboard UI](/tyk-identity-broker/dashboard-sso#create-a-tib-profile-using-dashboard-ui). + +```json expandable +{ + "ID": "okta-dashboard-saml", + "Name": "Okta Dashboard SSO (SAML)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginUserProfile", + "Type": "redirect", + "ProviderName": "SAMLProvider", + "ReturnURL": "http://dashboard.example.com:3000/tap", + "IdentityHandlerConfig": { + "DashboardCredential": "{tib-service-user-api-key}" + }, + "ProviderConfig": { + "SAMLBaseURL": "http://dashboard.example.com:3000", + "IDPMetadataURL": "https://{okta-domain}/app/{app-id}/sso/saml/metadata", + "CertLocation": "{certificate-id-from-dashboard}", + "FailureRedirect": "http://dashboard.example.com:3000/?fail=true", + "SAMLEmailClaim": "email", + "SAMLForenameClaim": "firstName", + "SAMLSurnameClaim": "lastName", + "ForceAuthentication": false + } +} +``` + +- set `DashboardCredential` to the [TIB service account's](/tyk-identity-broker/dashboard-sso#tib-service-account) Dashboard credentials +- set `CertLocation` to the certificate ID from the Tyk Certificate Store + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://dashboard.example.com:3000/auth/okta-dashboard-saml/saml +``` + +In production, present this as a "Log in with Okta" button or link on a custom login page, rather than expecting users to navigate to it directly. + +See [Dashboard SSO](/tyk-identity-broker/dashboard-sso) for details on session behavior, permissions, and user group mapping. + + + + +In this example, Tyk Developer Portal is running at `http://portal.example.com` on port `3001`; replace the example values with your own. + +**Tyk Developer Portal configuration** + +Enable embedded TIB in the Portal configuration: + +```json +{ + "TIB": { + "Enable": true + } +} +``` + +**Certificate** + +Unlike the Dashboard, the Portal's embedded TIB loads certificates from the filesystem. Set `CertLocation` to the file path of a PEM file containing the SP certificate and private key concatenated, accessible on the server running the Portal. + +**TIB profile** + +The TIB profile is created via the Tyk Developer Portal UI under **Settings > SSO Profiles**. + +```json expandable +{ + "ID": "okta-portal-saml", + "Name": "Okta Portal SSO (SAML)", + "OrgID": "{tyk-org-id}", + "ActionType": "GenerateOrLoginDeveloperProfile", + "Type": "redirect", + "ProviderName": "SAMLProvider", + "ReturnURL": "http://portal.example.com:3001/sso", + "IdentityHandlerConfig": { + "DashboardCredential": "{portal-api-secret}" + }, + "ProviderConfig": { + "SAMLBaseURL": "http://portal.example.com:3001", + "IDPMetadataURL": "https://{okta-domain}/app/{app-id}/sso/saml/metadata", + "CertLocation": "/path/to/sp-cert-and-key.pem", + "FailureRedirect": "http://portal.example.com:3001/?fail=true", + "SAMLEmailClaim": "email", + "SAMLForenameClaim": "firstName", + "SAMLSurnameClaim": "lastName", + "ForceAuthentication": false + } +} +``` + +- set `ActionType` and `OrgID` based on the audience: + - Admin Portal (API owners): `ActionType: "GenerateOrLoginUserProfile"`, `OrgID: "0"` + - Live Portal (API consumers): `ActionType: "GenerateOrLoginDeveloperProfile"`, `OrgID` is not required +- set `CertLocation` to the file path of the SP certificate PEM file on the Portal server +- set `DashboardCredential` to the [PORTAL_API_SECRET](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_api_secret) used to authenticate with the Portal's management API + +**Login URL** + +This URL initiates the SSO login flow: + +``` +http://portal.example.com:3001/auth/okta-portal-saml/saml +``` + +In production, present this as a "Log in with Okta" button or link on the Portal login page. + +For details on user group mapping and admin vs developer profiles, see [Portal SSO](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso). + + + diff --git a/tyk-identity-broker/standalone-tib.mdx b/tyk-identity-broker/standalone-tib.mdx new file mode 100644 index 0000000000..3fd7ebc9de --- /dev/null +++ b/tyk-identity-broker/standalone-tib.mdx @@ -0,0 +1,246 @@ +--- +title: "Standalone Tyk Identity Broker" +description: "Learn how to install and configure Tyk Identity Broker (TIB) as a standalone service using Docker, Linux packages, or Kubernetes, including Redis and profile storage setup." +keywords: "Tyk Identity Broker, TIB, installation, standalone, Docker, Helm, Kubernetes, packages" +sidebarTitle: "Standalone TIB" +--- + +This guide covers the installation of Tyk Identity Broker (TIB) as a standalone service, which is required for the [OAuth token generation](/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib#issuing-oauth-tokens) use case where TIB communicates directly with Tyk Gateway on behalf of authenticated users. + + +If you are setting up Single Sign-On for Tyk Dashboard or Tyk Developer Portal, TIB is already embedded in those products and you do not need to follow this guide. See [SSO into Tyk Dashboard](/tyk-identity-broker/dashboard-sso) or [SSO into Tyk Developer Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso) instead. + + +## Prerequisites + +- Redis +- And either: + - Tyk Gateway v1.9.1+ (for the `GenerateOAuthTokenForClient` action) + - Tyk Dashboard v0.9.7.1+ (for the `GenerateTemporaryAuthToken` action) + +## Installation + +### Docker + +Pull and run the TIB image from [Docker Hub](https://hub.docker.com/r/tykio/tyk-identity-broker/), which includes full run instructions and a reference for passing configuration via environment variables. + +### Linux Packages + +Install via [deb or rpm packages](https://packagecloud.io/tyk/tyk-identity-broker/install#bash-deb) on packagecloud. + +### Kubernetes + +The Tyk Helm charts do not include a standalone TIB deployment. If you need to run standalone TIB in Kubernetes, deploy it using the [Docker image](https://hub.docker.com/r/tykio/tyk-identity-broker/) and provide your own Kubernetes manifests. When deploying in Kubernetes, you must pass configuration via [environment variables](/tyk-configuration-reference/tyk-identity-broker-configuration). + +To manage TIB profiles in Kubernetes, mount `profiles.json` from a ConfigMap: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: tib-profiles +data: + profiles.json: | + [ ] +``` + +Mount the ConfigMap into the TIB pod and pass the path via the `-p` flag: + +```yaml expandable +containers: + - name: tib + image: tykio/tyk-identity-broker:{version} # replace with a specific version tag + args: ["-c", "/etc/tib/tib.conf", "-p", "/etc/tib/profiles.json"] + volumeMounts: + - name: tib-profiles + mountPath: /etc/tib/profiles.json + subPath: profiles.json +volumes: + - name: tib-profiles + configMap: + name: tib-profiles +``` + +For profile content, see [SSO into Tyk Dashboard](/tyk-identity-broker/dashboard-sso), [SSO into Tyk Developer Portal](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso), or [Issuing Tokens via TIB](/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib). + + +When profiles are loaded from a file, changes made via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) are written back to the mounted file. However, changes made directly to the ConfigMap will not take effect until the pod is restarted. + + +## Starting TIB + +TIB is started from the command line with two configurable options: + +- **Configuration**: via `tib.conf` (pass the path with `-c`, defaults to `tib.conf` in the current directory) or [environment variables](/tyk-configuration-reference/tyk-identity-broker-configuration). See [Configuration](#configuration). +- **Profile location**: from a file (pass the path with `-p`, defaults to `profiles.json` in the current directory) or from MongoDB. See [Profile Storage](#profile-storage). + +For file-based profile storage: + +```bash +./tyk-identity-broker -c /path/to/tib.conf -p /path/to/profiles.json +``` + +For MongoDB profile storage, the `-p` flag is not required: + +```bash +./tyk-identity-broker -c /path/to/tib.conf +``` + +## Configuration + +TIB is configured via the `tib.conf` configuration file. All settings can alternatively be provided as environment variables. One exception: the [Session Cookie Secret](#session-cookie-secret) must always be set as an environment variable and cannot be configured in `tib.conf`. + + +Environment variables are always applied and take precedence over values in `tib.conf`. Set [`TYK_IB_OMITCONFIGFILE=true`](/tyk-configuration-reference/tyk-identity-broker-configuration#omitting-the-configuration-file) if you want to ensure no values from a config file are used at all - useful in containerized deployments where configuration is managed entirely via environment variables. + + +### Management API Secret + +The `Secret` field sets the secret used to authenticate requests to the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api). This is required. + +```json +{ + "Secret": "{tib-api-secret}" +} +``` + +### TLS + +The `HttpServerOptions` section controls how TIB listens for incoming requests. SSL (TLS) is strongly recommended for production deployments. + +```json +{ + "HttpServerOptions": { + "UseSSL": true, + "CertFile": "./certs/server.pem", + "KeyFile": "./certs/server.key" + } +} +``` + +### Profile Storage + +Tyk Identity Broker can load [profiles](/tyk-identity-broker/overview#profile) from either a local file or from a MongoDB instance. After being loaded, profiles are held in memory at runtime regardless of the storage type. After every create, update, or delete operation via the Tyk Identity Broker API, TIB writes the updated profile set back to the source. + +**Local Storage** + +The default behavior is to load profiles from the file specified by the `-p` flag on startup. + +Any updates to the profiles are written back to the same file. + + +Configure [`ProfileDir`](/tyk-configuration-reference/tyk-identity-broker-configuration#profiledir) with a directory path where timestamped backups of the previous `profiles.json` will be created before the file is overwritten. This prevents data loss if a write fails or profiles need to be rolled back. + + +Changes made via the [Tyk Identity Broker API](/tyk-identity-broker/tib-rest-api) take effect immediately; TIB updates its in-memory store and writes the change back to `profiles.json` without requiring a restart. If you edit `profiles.json` directly on disk, TIB must be restarted to pick up the changes. + +**MongoDB Storage** + +To load profiles from MongoDB instead, add a [`Storage`](/tyk-configuration-reference/tyk-identity-broker-configuration#storage) block to the TIB config. Only `mongo_url` and `db_name` are required: + +```json +{ + "Storage": { + "storage_type": "mongo", + "mongo": { + "mongo_url": "mongodb://localhost:27017", + "db_name": "tib" + } + } +} +``` + +The following optional settings are also available: + +| Field | Description | +|---|---| +| `mongo_use_ssl` | Set to `true` to enable TLS for the MongoDB connection. TIB will verify the server certificate against system CAs. | +| `mongo_ssl_insecure_skip_verify` | Skip TLS certificate verification. Not recommended for production. | +| `session_consistency` | MongoDB session consistency level. | +| `driver` | MongoDB driver to use: `mongo-go` (default) or `mgo`. | +| `direct_connection` | Set to `true` to connect directly to a single MongoDB host, bypassing replica set discovery. | + +Any updates to the profiles are written back to the MongoDB collection. + +### Identity Cache + +TIB uses Redis to cache the one-token-per-user mapping for API token generation. The connection is configured via the `BackEnd.IdentityBackendSettings` block and is required regardless of how profiles are stored. + +For a single Redis server, only `Host` and `Port` are required: + +```json +{ + "BackEnd": { + "IdentityBackendSettings": { + "Host": "localhost", + "Port": 6379 + } + } +} +``` + +The following optional settings are also available: + +| Field | Description | +|---|---| +| `Password` | Redis authentication password. | +| `Username` | Redis 6+ ACL username. | +| `Database` | Redis database index. Defaults to `0`. | +| `MaxActive` | Maximum number of connections in the pool per node. Defaults to 500. | +| `Timeout` | Timeout in seconds applied to dial, read, and write operations. Defaults to 5 seconds. | +| `Addrs` | List of `host:port` addresses. Use instead of `Host`/`Port` for Redis Cluster or Sentinel. | +| `EnableCluster` | Set to `true` to enable Redis Cluster mode. | +| `MasterName` | Redis Sentinel master name. | +| `SentinelPassword` | Redis Sentinel authentication password. | +| `UseSSL` | Set to `true` to enable TLS. Server certificate is verified against system CAs by default. | +| `CAFile` | Path to a custom CA certificate file. Use when the Redis server uses a self-signed or private CA certificate. | +| `CertFile` | Path to the client certificate file. Set together with `KeyFile` to enable mutual TLS (mTLS). | +| `KeyFile` | Path to the client key file. Set together with `CertFile` to enable mutual TLS (mTLS). | +| `SSLInsecureSkipVerify` | Skip TLS certificate verification. Not recommended for production. | +| `MinVersion` | Minimum TLS version. Defaults to `1.2`. Valid values: `1.0`, `1.1`, `1.2`, `1.3`. | +| `MaxVersion` | Maximum TLS version. Defaults to `1.3`. Valid values: `1.0`, `1.1`, `1.2`, `1.3`. | + +### Tyk Dashboard Connection + +Required for the [`GenerateTemporaryAuthToken`](/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib) action, which calls the Tyk Dashboard API to generate auth keys. Configure the `DashboardConfig` block within `TykAPISettings`: + +```json +{ + "TykAPISettings": { + "DashboardConfig": { + "Endpoint": "http://{dashboard-host}", + "Port": "3000", + "AdminSecret": "{dashboard-admin-secret}" + } + } +} +``` + +### Tyk Gateway Connection + +Required for the [`GenerateOAuthTokenForClient`](/api-management/access-control/sessions-and-keys/issuing-tokens-via-tib) action, which calls the Tyk Gateway OAuth endpoint directly to issue OAuth 2.0 tokens. Configure the `GatewayConfig` block within `TykAPISettings`: + +```json +{ + "TykAPISettings": { + "GatewayConfig": { + "Endpoint": "http://{gateway-host}", + "Port": "8080", + "AdminSecret": "{gateway-admin-secret}" + } + } +} +``` + +For the full configuration reference including all fields and their environment variable equivalents, see [Tyk Identity Broker Configuration](/tyk-configuration-reference/tyk-identity-broker-configuration). + +### Session Cookie Secret + +When using redirect-based methods (`SocialProvider` or `SAMLProvider`), TIB [signs the session cookie](/tyk-identity-broker/overview#redirect-session-cookie) using a secret set via the `TYK_IB_SESSION_SECRET` environment variable: + +```bash +export TYK_IB_SESSION_SECRET='your-session-secret' +``` + +Use a randomly generated string of 32 or 64 bytes. This should always be set explicitly for standalone deployments. + diff --git a/tyk-identity-broker/tib-rest-api.mdx b/tyk-identity-broker/tib-rest-api.mdx new file mode 100644 index 0000000000..03cc4d73bd --- /dev/null +++ b/tyk-identity-broker/tib-rest-api.mdx @@ -0,0 +1,20 @@ +--- +title: "Tyk Identity Broker API" +description: "Reference documentation for the Tyk Identity Broker (TIB) REST API, covering endpoints to create, update, delete, and list TIB profiles." +keywords: "Tyk Identity Broker, TIB, REST API, profile management, API reference" +sidebarTitle: "Overview" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + + + +The Tyk Identity Broker API allows TIB profiles to be created, updated, deleted, and listed programmatically. All requests require an `Authorization` header whose value matches the `Secret` field in `tib.conf`. + +The interactive endpoint documentation is available in the sidebar below. + +## Embedded TIB + +**Tyk Dashboard (from v3.0):** Profile management endpoints are available via the Tyk Dashboard API on the `/api/tib/` prefix. For example, the standalone TIB endpoint `/api/profiles/{id}` maps to `/api/tib/profiles/{id}` on the Tyk Dashboard API. See the [Tyk Dashboard API reference](/tyk-dashboard-api) for the full Dashboard API documentation. + +**Tyk Developer Portal (from v1.12.0):** Profile management is available through the Portal UI under **Settings > SSO Profiles**. The Portal does not expose TIB profile management via a separate API endpoint. diff --git a/tyk-mdcb-api.mdx b/tyk-mdcb-api.mdx new file mode 100644 index 0000000000..b52e12fbdc --- /dev/null +++ b/tyk-mdcb-api.mdx @@ -0,0 +1,15 @@ +--- +title: "Tyk MDCB API" +description: "Tyk MDCB API documentation. This page provides details on how to use the Tyk Multi Data Center Bridge (MDCB) API for monitoring connected Data Planes and accessing diagnostic data." +keywords: "OpenAPI Spec, OpenAPI Specification, OAS, REST, Tyk MDCB OpenAPI Spec, Tyk MDCB OAS, MDCB API REST" +order: 3 +sidebarTitle: "Overview" +--- + +import { ButtonLeft } from '/snippets/ButtonLeft.mdx'; + + + +This API provides operations for monitoring Data Planes connected to MDCB and accessing diagnostic data. +It includes endpoints for retrieving connected data plane details, performing health checks, +and accessing Go's built-in pprof diagnostics for advanced performance profiling. \ No newline at end of file diff --git a/tyk-multi-data-centre/mdcb-configuration-options.mdx b/tyk-multi-data-centre/mdcb-configuration-options.mdx new file mode 100644 index 0000000000..3879876075 --- /dev/null +++ b/tyk-multi-data-centre/mdcb-configuration-options.mdx @@ -0,0 +1,39 @@ +--- +title: "MDCB Configuration options" +description: "Each of the config options that are available when deploying MDCB." +keywords: "MDCB, configuration options, MDCB configuration options" +order: 3 +sidebarTitle: "Multi Data Center Bridge" +--- + +import MdcbConfig from '/snippets/mdcb-config.mdx'; +import EnvTypeMapping from '/snippets/env-type-mapping.mdx'; + +## Tyk MDCB Configuration + +The Tyk MDCB server is configured primarily via the `tyk_sink.conf` file, this file resides in `/opt/tyk-sink` on most systems, but can also live anywhere and be directly targeted with the `-c` flag. + +### Environment Variables + +Environment variables (env var) can be used to override the settings defined in the configuration file. Where an environment variable is specified, its value will take precedence over the value in the configuration file. + + + +### Default Ports + +| Application | Port | +| :------------------------- | :---------------- | +|MongoDB | 27017 | +|Redis | 6379 | +|**Tyk Dashboard** | | +|Developer Portal | 3000 | +|Admin Dashboard | 3000 | +|Admin Dashboard API | 3000 | +|**Tyk Gateway** | | +|Management API | 8080 | +|**MDCB** | | +|RPC services | 9090 | +|HTTP endpoints | 8181 | + + + diff --git a/tyk-open-source.mdx b/tyk-open-source.mdx new file mode 100644 index 0000000000..3aa22f0fc2 --- /dev/null +++ b/tyk-open-source.mdx @@ -0,0 +1,28 @@ +--- +title: "Tyk Open Source" +description: "This page serves as a comprehensive guide to Tyk Open Source" +keywords: "installation, migration, open source" +sidebarTitle: "Overview" +--- + +## What is Tyk Open Source + +Open source is at the heart of what we do. Anything that is API Gateway-related lives in the Gateway, or is critical for the Gateway to work is open and freely available via our [Github](https://github.com/TykTechnologies/tyk). + +The Tyk Gateway is fully open-source. It's all the same Gateway that's used by you (the community!), by our enterprise products, as well as our SaaS. + +Our commitment to open source also delivers a host of benefits for our users: sign up for free with Tyk, receive securely packaged open source packages, get started guides, access to our community and all of the latest open source information. + + + +Tyk OSS, Tyk Open Source, Tyk Gateway, Tyk CE + + + +OSS-Guide + +## What Does Tyk Open Source Include? + +import OssProductListInclude from '/snippets/oss-product-list-include.mdx'; + + diff --git a/tyk-oss-gateway.mdx b/tyk-oss-gateway.mdx new file mode 100644 index 0000000000..addcbdcc91 --- /dev/null +++ b/tyk-oss-gateway.mdx @@ -0,0 +1,16 @@ +--- +title: "Tyk Gateway Open Source (OSS)" +description: "Overview of Tyk Gateway features and deployment options." +order: 1 +sidebarTitle: "Tyk Gateway" +--- + +import TykGatewayFeaturesInclude from '/snippets/tyk-gateway-features-include.mdx'; + +## What is the Tyk Gateway? + + + +## Deployment Options + +Refer the [deployment options](/apim) page. \ No newline at end of file diff --git a/tyk-oss-gateway/configuration.mdx b/tyk-oss-gateway/configuration.mdx new file mode 100644 index 0000000000..5417ec0ccb --- /dev/null +++ b/tyk-oss-gateway/configuration.mdx @@ -0,0 +1,29 @@ +--- +title: "Tyk Gateway Configuration Options" +description: "Configuration options and environment variables for Tyk Gateway." +order: 1 +sidebarTitle: "Gateway" +--- + +import GatewayConfig from '/snippets/gateway-config.mdx'; +import EnvTypeMapping from '/snippets/env-type-mapping.mdx'; + +You can use environment variables to override the config file for the Tyk Gateway. The Gateway configuration file can be found in the `tyk-gateway` folder and by default is called `tyk.conf`, though it can be renamed and specified using the `--conf` flag. Environment variables are created from the dot notation versions of the JSON objects contained with the config files. +To understand how the environment variables notation works, see [Environment Variables](/tyk-oss-gateway/configuration). + +All the Gateway environment variables have the prefix `TYK_GW_`. The environment variables will take precedence over the values in the configuration file. + + + +### tyk lint + +In **v2.4** we have added a new `tyk lint` command which will validate your `tyk.conf` file and validate it for syntax correctness, misspelled attribute names or format of values. The Syntax can be: + +`tyk lint` or `tyk --conf=path lint` + +If `--conf` is not used, the first of the following paths to exist is used: + +`./tyk.conf` +`/etc/tyk/tyk.conf` + + diff --git a/tyk-overview.mdx b/tyk-overview.mdx new file mode 100644 index 0000000000..c82e304cd6 --- /dev/null +++ b/tyk-overview.mdx @@ -0,0 +1,65 @@ +--- +title: "Tyk Overview" +description: "A high-level overview of Tyk and its capabilities" +keywords: "Tyk API Management, Getting Started, Tutorials" +order: 5 +sidebarTitle: "Tyk Overview" +--- + +APIs are are central to enabling software integration, data exchange, and automation. However, as organizations scale their API ecosystems, they face mounting challenges around security, reliability, and performance. Tyk exists to simplify and strengthen this process. With a focus on efficient, secure, and scalable API management, Tyk provides a powerful solution for companies looking to streamline API operations, enforce robust security standards, and gain deep visibility into their API usage. + +## Why Tyk Exists: The Need for API Management +The demand for APIs has exploded over the last decade, with companies using them to enable everything from mobile apps and IoT devices to microservices architectures and third-party integrations. But with this growth come significant challenges: + +- **Security Risks**: Exposing services through APIs introduces new security vulnerabilities that need constant management and monitoring. +- **Scalability**: As usage grows, APIs need to be resilient, able to handle high traffic, and scalable across global regions. +- **Complexity in Integration**: Integrating various backend services, identity providers, and front-end applications can become an overwhelming task. +- **Monitoring and Performance**: API performance monitoring, traffic management, and analytics are crucial to optimize API usage and provide reliable service. + +Tyk exists to address these challenges by providing an API management platform that’s secure, scalable, flexible, and easy to use. With Tyk, organizations can confidently manage the entire lifecycle of their APIs, from initial design to deployment and ongoing monitoring. + +## What Problem Does Tyk Solve? + +Tyk is designed to solve several critical issues that organizations face with APIs: + +1. **Unified API Management** + Tyk centralizes all aspects of API management, offering tools for routing, load balancing, security, and performance. This unified approach helps teams streamline API operations and reduce operational overhead. + +2. **Enhanced Security and Compliance** + APIs are vulnerable to numerous security threats. Tyk addresses these concerns by supporting a wide array of security protocols, including OAuth2.0, JWT, HMAC, and OpenID Connect. Additionally, Tyk enables organizations to enforce fine-grained access control policies, rate limiting, and quotas to safeguard API access. + +3. **Scalability for High-Volume Traffic** + Tyk provides a high-performance API gateway that can handle substantial traffic loads while maintaining low latency, ensuring that APIs can scale as demand increases. Tyk’s Multi Data Centre Bridge (MDCB) further enhances scalability by distributing traffic across multiple regions, providing high availability and low latency globally. + +4. **Seamless Integration and Flexibility** + Tyk’s open-source architecture and compatibility with Kubernetes, Docker, and cloud platforms make it easy to integrate within existing infrastructures. With Tyk, teams can operate in hybrid or multi-cloud environments, deploy APIs as Kubernetes-native resources, and leverage CI/CD pipelines for seamless updates. + +5. **Developer and Consumer Enablement** + Through the Tyk Developer Portal, developers can discover and access APIs easily, enabling faster adoption and integration. With detailed documentation, developer self-service features, and API analytics, Tyk empowers both API providers and consumers to make the most of their API ecosystem. + +## How Tyk’s Components Work Together + +Tyk offers a comprehensive suite of components designed to address every aspect of the API lifecycle: + +- **[Tyk Gateway](/tyk-oss-gateway)**: The core of Tyk’s platform, providing high-performance API routing, traffic management, and security. +- **[Tyk Dashboard](/api-management/dashboard-configuration)**: A graphical control panel that simplifies API management, configuration, and monitoring. +- **[Tyk Developer Portal](/portal/overview/intro)**: A self-service portal that enables developers to access, understand, and integrate with APIs. +- **[Tyk Multi Data Centre Bridge (MDCB)](/api-management/mdcb)**: Allows centralized control over APIs distributed across multiple data centers or cloud regions. +- **[Tyk Pump](/api-management/tyk-pump)**: Collects and streams analytics from the Tyk Gateway to various storage backends for performance monitoring and reporting. +- **[Tyk Operator](/api-management/automations/operator#what-is-tyk-operator)**: Kubernetes-native API management that allows teams to manage APIs as Kubernetes resources. +- **[Tyk Streams](/api-management/event-driven-apis#)**: Enables real-time data streaming and push-based communication for applications requiring live data. +- **[Tyk Sync](/api-management/automations/sync)**: Synchronizes API configurations across environments, supporting DevOps practices and CI/CD workflows. +- **[Tyk Identity Broker](/tyk-identity-broker/overview)**: Integrates with external identity providers for single sign-on (SSO) and centralized identity management. +- **[Tyk Helm Charts](/product-stack/tyk-charts/overview)**: Simplifies the deployment of Tyk components within Kubernetes environments. +- **[Universal Data Graph](/api-management/data-graph#overview)**: Provides a single GraphQL endpoint that aggregates data from multiple sources, simplifying access to complex data. + +Each component plays a specific role in managing the API lifecycle, from initial deployment and configuration to real-time data streaming and developer access. Together, they create a cohesive API management ecosystem that can handle the unique challenges of production environments. + +You can learn more about the components that make up Tyk, [here](/tyk-components). + +## Why Use Tyk? + +In summary, Tyk offers a complete API management solution designed for modern, production-grade API operations. With its open-source core, robust security options, high performance, and flexible deployment models, Tyk provides everything an organization needs to manage, scale, and secure their APIs. + +Whether you’re a startup looking to build a simple API or a global enterprise deploying complex, multi-region architectures, Tyk has the tools to support your growth at every stage. If you face problems with scaling your solutions, learn more about how Tyk can support you by [getting started with Tyk Cloud](/getting-started/create-account). + diff --git a/tyk-portal-api.mdx b/tyk-portal-api.mdx new file mode 100644 index 0000000000..abcde82dd6 --- /dev/null +++ b/tyk-portal-api.mdx @@ -0,0 +1,20 @@ +--- +title: "Classic Portal API" +description: "Landing page for the Tyk Classic Portal API documentation" +keywords: "Tyk Classic Portal API, Classic Portal API" +robots: "noindex, nofollow" +sidebarTitle: "Overview" +--- + +import LegacyClassicPortalApi from '/snippets/legacy-classic-portal-api.mdx'; + + + + +This section describes the Tyk Classic Portal API endpoints. It includes the following: + +* [Portal Keys](/tyk-apis/tyk-portal-api/portal-keys) +* [Portal Policies](/tyk-apis/tyk-dashboard-api/portal-policies) +* [Portal Developers](/tyk-apis/tyk-portal-api/portal-developers) +* [Portal Configuration](/tyk-apis/tyk-portal-api/portal-configuration) +* [Portal Documentation](/tyk-apis/tyk-portal-api/portal-documentation) \ No newline at end of file diff --git a/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables.mdx b/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables.mdx new file mode 100644 index 0000000000..428202d948 --- /dev/null +++ b/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables.mdx @@ -0,0 +1,19 @@ +--- +title: "Tyk Pump Environment Variables" +description: "Using Environment Variables to configure your Tyk Pump" +keywords: "Tyk Pump, Envoronment Variables, Configuration" +order: 6 +sidebarTitle: "Pump" +--- + +import PumpConfig from '/snippets/pump-config.mdx'; +import EnvTypeMapping from '/snippets/env-type-mapping.mdx'; + +You can use environment variables to override the config file for the Tyk Pump. Environment variables are created from the dot notation versions of the JSON objects contained with the config files. +To understand how the environment variables notation works, see [Environment Variables](/tyk-oss-gateway/configuration). + +All the Pump environment variables have the prefix `TYK_PMP_`. The environment variables will take precedence over the values in the configuration file. + + + + diff --git a/tyk-self-managed.mdx b/tyk-self-managed.mdx new file mode 100644 index 0000000000..2b8c9b0b30 --- /dev/null +++ b/tyk-self-managed.mdx @@ -0,0 +1,26 @@ +--- +title: "Tyk Self Managed" +description: "An overview of Tyk Self-Managed, allowing you to install our Full Lifecycle API Management solution in your own infrastructure" +keywords: "installation, migration, self managed" +sidebarTitle: "Overview" +--- + +## What is Tyk Self-Managed + +Tyk Self-Managed allows you to easily install our Full Lifecycle API Management solution in your own infrastructure. There is no calling home, and there are no usage limits. You have full control. + +## What Does Tyk Self-Managed Include? +The full Tyk Self-Managed system consists of: + +* [Tyk Gateway](/tyk-oss-gateway): Tyk Gateway is provided ‘Batteries-included’, with no feature lockout. It is an open source enterprise API Gateway, supporting REST, GraphQL, TCP and gRPC protocols, that protects, secures and processes your APIs. +* [Tyk Dashboard](/api-management/dashboard-configuration): The management Dashboard and integration API manage a cluster of Tyk Gateways and also show analytics and features of the [Developer portal](/portal/overview/intro). The Dashboard also provides the API Developer Portal, a customizable developer portal for your API documentation, developer auto-enrollment and usage tracking. +* [Developer Portal](/portal/overview/intro): A customizable API portal to securely publish and manage API access for your consumers. +* [Tyk Pump](/api-management/tyk-pump): Tyk Pump handles moving analytics data between your gateways and your Dashboard (amongst other data sinks). The Tyk Pump is an open source analytics purger that moves the data generated by your Tyk nodes to any back-end. +* [Tyk Identity Broker](/tyk-identity-broker/overview) (Optional): Tyk Identity Broker handles integrations with third-party IdPs. It (TIB) is a component providing a bridge between various Identity Management Systems such as LDAP, Social OAuth (e.g. GPlus, Twitter, GitHub) or Basic Authentication providers, to your Tyk installation. +* [Tyk Multi-Data Center Bridge](/api-management/mdcb) (Optional, add-on): Tyk Multi-Data Center Bridge allows for the configuration of a Tyk ecosystem that spans many data centers and clouds. It also (MDCB) acts as a broker between Tyk Gateway Instances that are isolated from one another and typically have their own Redis DB. + +Tyk Self-Managed Architecture + +## Getting Started + +To get started with Tyk Self-Managed, you can follow the [Getting Start Guide](/getting-started/quick-start) which provides a step-by-step walkthrough of setting up Tyk in your environment. diff --git a/tyk-self-managed/install.mdx b/tyk-self-managed/install.mdx new file mode 100644 index 0000000000..4b4025e791 --- /dev/null +++ b/tyk-self-managed/install.mdx @@ -0,0 +1,105 @@ +--- +title: "Installation Options for Tyk Self-Managed" +description: "Explore the various installation options for Tyk Self-Managed, including Docker, Kubernetes, Linux packages, Ansible, and more." +sidebarTitle: "Overview" +--- + +import { ResponsiveGrid } from '/snippets/ResponsiveGrid.mdx'; +import MongodbVersionsInclude from '/snippets/mongodb-versions-include.mdx'; +import SqlVersionsInclude from '/snippets/sql-versions-include.mdx'; +import RedisVersionsInclude from '/snippets/redis-versions-include.mdx'; + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| Enterprise | Self-Managed, Hybrid | + + + +This page is for installing **Tyk Self-Managed**. If you are looking to use Tyk as part of **Tyk Cloud**, please refer to [the Tyk Cloud documentation](/getting-started/create-account). + + + +## Architecture + +Tyk Self-Managed Architecture + +## Requirements + + +**Tyk Dashboard** requires a persistent datastore for its operations. By default MongoDB is used. From Tyk v4.0, we also support PostgreSQL. + +### PostgreSQL + + + +Please check [here](/planning-for-production/database-settings#postgresql) for production configuration. + +### MongoDB + + + +Please check [here](/planning-for-production/database-settings#mongodb) for MongoDB driver and production configuration. + +### Redis + + + +## Recommended Installation: Docker + +For development, testing, and proof of concept purposes, we recommend using our Docker installation, which allows you to quickly spin up a full Tyk stack on your local machine. + + + + + +Install with Docker + + + + +## Alternative Installation Methods + + + + + +Install on K8s + + + + +Install with Ansible + + + + +Install on Red Hat + + + + +Install on Ubuntu + + + + +Install on Amazon AWS + + + + +Install Tyk on Heroku + + + + +Install on Microsoft Azure + + + + +## Air Gapped Installation + +For environments with restricted network access, we provide guidance on how to deploy Tyk using private container registries and local package mirrors. + +Refer to our [Air-Gapped Deployment guide](/api-management/air-gapped-deployment) for detailed instructions on how to set up Tyk in air-gapped or network-restricted environments. \ No newline at end of file diff --git a/tyk-self-managed/install/docker.mdx b/tyk-self-managed/install/docker.mdx new file mode 100644 index 0000000000..550a28bab7 --- /dev/null +++ b/tyk-self-managed/install/docker.mdx @@ -0,0 +1,199 @@ +--- +title: "Install Tyk Self-Managed on Docker" +description: "Install the full Tyk Self-Managed stack (Gateway, Dashboard, Portal, Pump, Redis, and PostgreSQL) using Docker Compose." +sidebarTitle: "Docker" +--- + +| Edition | Deployment Type | +| :------ | :-------------- | +| Enterprise | Self-Managed, Hybrid | + + +Running on Podman, containerd, or another container runtime? See [Container Runtimes](/deployment-and-operations/container-runtimes). + + + +From v5.5.0 onwards, Docker images are based on [distroless](https://github.com/GoogleContainerTools/distroless). You cannot obtain a shell with `docker run --rm -it tykio/tyk-gateway:v5.5.0 sh`. Use [dive](https://github.com/wagoodman/dive) or [Docker Desktop](https://www.docker.com/products/docker-desktop/) to inspect images. + + +## Prerequisites + +- [Docker Engine](https://docs.docker.com/engine/install/) 24.0 or later +- [Docker Compose](https://docs.docker.com/compose/install/) v2.20 or later +- Tyk license keys — Dashboard license and Portal license (optional). Get a free trial at [tyk.io/self-managed-trial](https://tyk.io/self-managed-trial/). +- 4GB RAM or more available + +## Instructions + +### Step 1: Clone and configure + +1. Clone the [tyk-install](https://github.com/TykTechnologies/tyk-install) repository and navigate to the Docker self-managed directory: + + ```bash + git clone https://github.com/TykTechnologies/tyk-install + cd tyk-install/docker/self-managed + ``` + +2. Copy the example environment file and add your license keys: + + ```bash + cp .env.example .env + ``` + +3. Open `.env` and set your license keys: + + ```bash + TYK_LICENSE_KEY= + TYK_PORTAL_LICENSE= + ``` + + + Ensure there are **no spaces** around the `=` sign. Spaces will cause the configuration to fail silently. + + ```bash + # Correct + TYK_LICENSE_KEY=eyJhbGciOiJSUzI1NiIsInR5cCI6... + + # Wrong — will fail + TYK_LICENSE_KEY = eyJhbGciOiJSUzI1NiIsInR5cCI6... + ``` + + +### Step 2: Start services + +```bash +docker compose up -d +``` + +Wait for all health checks to pass (approximately 30–60 seconds), then verify all containers are running: + +```bash +docker compose ps +``` + +Expected containers: + +| Container | Port | Status | +| --------- | ---- | ------ | +| `tyk-dashboard` | 3000 | Running | +| `tyk-gateway` | 8080 | Running | +| `tyk-pump` | N/A | Running | +| `tyk-portal` | 3001 | Running | +| `tyk-redis` | 6379 | Running | +| `tyk-postgres` | 5432 | Running | + +If any container is not running, check the logs: + +```bash +docker compose logs -f +``` + +### Step 3: Bootstrap the stack + +Choose one of the following options to complete the initial setup. + +#### Option A: Automated bootstrap (recommended) + +Use the Bootstrap Utility from the repository to automatically create an organization, admin user, test API, policy, API key, and Portal configuration. + +1. Navigate to the bootstrap utility directory and create an `.env` file with your license key: + + ```bash + cd tyk-install/utils/bootstrap + echo "TYK_LICENSE_KEY=" > .env + ``` + +2. Run the bootstrap: + + ```bash + docker compose --profile tools run --rm tyk-bootstrap + ``` + +Credentials are printed to the terminal on completion. + +#### Option B: Manual bootstrap + +1. Open `http://localhost:3000` in your browser. + +2. Fill in the bootstrap form: + - Organization Name + - Admin Email + - Admin Password + + + Use a combination of alphanumeric characters with both upper and lower case letters for your password. + + +3. Click **Bootstrap** to save. + +4. Log in to the Tyk Dashboard at `http://localhost:3000` with the credentials you just created. + +5. Bootstrap the Developer Portal by calling its API: + + ```bash + curl "http://localhost:3001/portal-api/bootstrap" \ + -H 'Content-Type: application/json' \ + -d '{ + "username": "portal-admin@example.com", + "password": "portalpass123", + "first_name": "Portal", + "last_name": "Admin" + }' + ``` + + Save the `api_token` from the response — you will need it to configure the Portal provider. + +### Step 4: Verify the installation + +Test that all components are responding: + +```bash +# Gateway +curl http://localhost:8080/hello + +# Dashboard +curl http://localhost:3000/hello + +# Portal +curl http://localhost:3001/ready +``` + +## Access URLs + +| Service | URL | Description | +| ------- | --- | ----------- | +| Dashboard | `http://localhost:3000` | Admin UI | +| Gateway | `http://localhost:8080` | API Gateway | +| Developer Portal | `http://localhost:3001` | Developer Portal | +| Redis | `localhost:6379` | Cache and session storage | +| PostgreSQL | `localhost:5432` | Analytics and config database | + +## Configuration + +The following environment files control each component's settings: + +| File | Purpose | +| ---- | ------- | +| `.env` | License keys and component versions | +| `confs/tyk.env` | Gateway configuration | +| `confs/tyk_analytics.env` | Dashboard configuration | +| `confs/pump.env` | Pump configuration | +| `confs/portal.env` | Developer Portal configuration | + +For production deployments, review the [Planning for Production](/planning-for-production) guide and apply the recommendations noted in the config file comments. + +## Cleanup + +Stop all services and remove volumes: + +```bash +docker compose down -v +``` + +To also remove orphaned networks: + +```bash +docker compose down -v --remove-orphans +docker network prune -f +``` + diff --git a/tyk-self-managed/install/kubernetes.mdx b/tyk-self-managed/install/kubernetes.mdx new file mode 100644 index 0000000000..14de59d46d --- /dev/null +++ b/tyk-self-managed/install/kubernetes.mdx @@ -0,0 +1,663 @@ +--- +title: "Install Tyk Self-Managed on Kubernetes" +description: "Install the full Tyk Self-Managed stack (Gateway, Dashboard, Portal, Pump, Operator, Redis, and PostgreSQL) on Kubernetes using Helm." +sidebarTitle: "Kubernetes" +--- + +| Edition | Deployment Type | +| :------ | :-------------- | +| Enterprise | Self-Managed, Hybrid | + + +Running on Podman, containerd, or another container runtime? See [Container Runtimes](/deployment-and-operations/container-runtimes). + + +## Compatible Kubernetes Versions + +1.33.x, 1.34.x, 1.35.x + +## Prerequisites + +- A running Kubernetes cluster. This can be local (minikube, kind, or Docker Desktop) or a managed cloud cluster (EKS, GKE, or AKS). +- [kubectl](https://kubernetes.io/docs/tasks/tools/) installed and connected to your cluster. +- [Helm](https://helm.sh/docs/intro/install/) 3.12 or later. +- Tyk license keys. The Dashboard license is required; the Operator and Portal licenses are optional. Get a free trial at [tyk.io/self-managed-trial](https://tyk.io/self-managed-trial/). +- 4GB RAM or more available to the cluster. + +## Instructions + +This guide deploys the full Tyk stack with the opinionated, trial-ready configuration from the [tyk-install](https://github.com/TykTechnologies/tyk-install) repository. The bundled `values.yaml` enables Pump analytics to PostgreSQL, audit logging, hashed-key listing, OPA, and Dashboard security defaults out of the box. + +### Step 1: Clone and Configure + +1. Clone the [tyk-install](https://github.com/TykTechnologies/tyk-install) repository and navigate to the Kubernetes self-managed directory: + + ```bash + git clone https://github.com/TykTechnologies/tyk-install + cd tyk-install/kubernetes/helm-self-managed + ``` + +2. Copy the example environment file and add your license keys: + + ```bash + cp .env.example .env + ``` + +3. Open `.env` and set your license key: + + ```bash + TYK_LICENSE_KEY= + TYK_OPERATOR_LICENSE= + TYK_PORTAL_LICENSE= + ``` + +4. Load the environment variables into your shell: + + ```bash + source .env + ``` + +### Step 2: Create the Namespace and Secrets + +The secrets store your licenses, the shared API secret, database connection strings, and the bootstrap admin user. The values come from the `.env` file you loaded in Step 1. + +```bash +# Create the namespace +kubectl create namespace tyk + +# Create the main Tyk secret +kubectl create secret generic tyk-conf \ + --namespace tyk \ + --from-literal=APISecret=$TYK_API_SECRET \ + --from-literal=AdminSecret=$TYK_ADMIN_SECRET \ + --from-literal=DashLicense=$TYK_LICENSE_KEY \ + --from-literal=OperatorLicense=$TYK_OPERATOR_LICENSE \ + --from-literal=DevPortalLicense=$TYK_PORTAL_LICENSE \ + --from-literal=adminUserFirstName=$ADMIN_FIRST_NAME \ + --from-literal=adminUserLastName=$ADMIN_LAST_NAME \ + --from-literal=adminUserEmail=$ADMIN_EMAIL \ + --from-literal=adminUserPassword=$ADMIN_PASSWORD \ + --from-literal=DashDatabaseConnectionString="$DashDatabaseConnectionString" \ + --from-literal=DevPortalDatabaseConnectionString="$DevPortalDatabaseConnectionString" + +# Create the Developer Portal secret +kubectl create secret generic secrets-tyk-tyk-dev-portal \ + --namespace tyk \ + --from-literal=adminUserPassword=$ADMIN_PASSWORD \ + --from-literal=adminUserEmail=$ADMIN_EMAIL +``` + +### Step 3: Install Dependencies + +Install PostgreSQL and Redis from Bitnami, wait for them to become ready, then install cert-manager (required by the Tyk Operator). + +```bash +# Add the Bitnami repository +helm repo add bitnami https://charts.bitnami.com/bitnami +helm repo update + +# Install PostgreSQL (also creates the portal database) +helm install tyk-postgres bitnami/postgresql \ + --namespace tyk \ + --set image.repository=bitnamilegacy/postgresql \ + --set auth.username=$POSTGRES_USER \ + --set auth.password=$POSTGRES_PASSWORD \ + --set auth.database=$POSTGRES_DB \ + --set primary.initdb.scripts."init\.sql"="CREATE DATABASE portal;" \ + --set primary.persistence.size=20Gi \ + --version 12.12.10 + +# Install Redis +helm install tyk-redis oci://registry-1.docker.io/bitnamicharts/redis \ + --namespace tyk \ + --set image.repository=bitnamilegacy/redis \ + --set auth.enabled=false \ + --version 19.0.2 + +# Wait for the databases to be ready (about 2 minutes) +kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=postgresql -n tyk +kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=redis -n tyk + +# Install cert-manager (required for the Tyk Operator) +helm install cert-manager oci://quay.io/jetstack/charts/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.17.4 \ + --set crds.enabled=true +``` + + +Waiting for PostgreSQL and Redis to report ready before installing Tyk prevents the Gateway, Dashboard, and Pump pods from entering `CrashLoopBackOff` while they wait for a database connection. + + +### Step 4: Install the Tyk Stack + +```bash +# Add the Tyk Helm repository +helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ +helm repo update + +# Install Tyk with the bundled values file +helm install tyk tyk-helm/tyk-stack \ + --namespace tyk \ + --values values.yaml + +# Watch the pods come up (press Ctrl+C once all are Running or Completed) +kubectl get pods -n tyk -w +``` + +Expected pods: + +| Pod | Status | +| --- | ------ | +| `gateway-xxx` | Running | +| `dashboard-xxx` | Running | +| `tyk-pump-xxx` | Running | +| `tyk-portal-xxx` | Running | +| `tyk-postgres-xxx` | Running | +| `tyk-redis-xxx` | Running | +| `tyk-tyk-operator-xxx` | Running | + +### Step 5: Access the Services + +The bundled `values.yaml` sets the Gateway, Dashboard, and Portal services to `LoadBalancer`. Choose the access method that matches your environment. + +**Local cluster (port-forward):** the quickest way to reach the services on a local cluster. Run each command in a separate terminal: + +```bash +kubectl port-forward -n tyk svc/dashboard-svc-tyk-tyk-dashboard 3000:3000 +kubectl port-forward -n tyk svc/gateway-svc-tyk-tyk-gateway 8080:8080 +kubectl port-forward -n tyk svc/dev-portal-svc-tyk-tyk-dev-portal 3001:3001 +``` + +**Cloud cluster (LoadBalancer):** EKS, GKE, and AKS provision an external address for each service. List them and wait for `EXTERNAL-IP` to be assigned: + +```bash +kubectl get svc -n tyk +``` + +For production setups with custom domains and TLS, configure Ingress (AWS ALB, GKE GCE, AKS Application Gateway, or NGINX). See the [helm-self-managed README](https://github.com/TykTechnologies/tyk-install/tree/main/kubernetes/helm-self-managed) for per-provider Ingress configuration. + +### Step 6: Get Admin Credentials + +The chart bootstraps the admin user from the `tyk-conf` secret. Retrieve the credentials to log in to the Tyk Dashboard: + +```bash +# Admin email +kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.adminUserEmail}' | base64 -d && echo + +# Admin password +kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.adminUserPassword}' | base64 -d && echo +``` + +### Step 7: Verify the Installation + +Test that all components are responding (adjust the host if you are not using port-forward): + +```bash +# Gateway +curl http://localhost:8080/hello + +# Dashboard +curl http://localhost:3000/hello + +# Portal +curl http://localhost:3001/ready +``` + +You are now ready to [create an API](/api-management/gateway-config-managing-classic#create-an-api), or manage APIs declaratively with [Tyk Operator](/api-management/automations/operator), which is installed as part of this stack. + +## Configuration + +Two files control the deployment: + +| File | Purpose | +| ---- | ------- | +| `.env` | License keys, the shared API secret, database credentials, and the bootstrap admin user. | +| `values.yaml` | Tyk stack configuration for the Gateway, Dashboard, Pump, Developer Portal, and Operator. | + +The bundled `values.yaml` is tuned for trials and evaluation, with Pump analytics to PostgreSQL, audit logging, hashed-key listing, OPA, and Dashboard security settings enabled. Inline comments mark the options to change for production and performance. For production deployments, also review the [Planning for Production](/planning-for-production) guide and the [helm-self-managed README](https://github.com/TykTechnologies/tyk-install/tree/main/kubernetes/helm-self-managed) for Ingress, TLS, autoscaling, and troubleshooting. + +### Hybrid Control Plane and Data Plane + +The installation above deploys a single, self-contained Tyk stack. To distribute API traffic across multiple data centers or regions, you can instead run a hybrid topology: a central Control Plane that hosts the management components and one or more remote Data Planes whose Gateways serve traffic locally and sync configuration from the Control Plane over [Tyk MDCB](/api-management/mdcb). + +To set up a hybrid deployment with Helm: + +- Install the [Control Plane](/api-management/mdcb#installing-in-a-kubernetes-cluster-with-our-helm-chart) first to provision the Dashboard, MDCB, and supporting services. +- Install each [Data Plane](/api-management/mdcb#installing-in-a-kubernetes-cluster-with-our-helm-chart-1) using the connection details produced by the Control Plane installation. + + +## Cleanup + +```bash +# Remove the Tyk stack +helm uninstall tyk -n tyk + +# Remove the databases (this deletes all data) +helm uninstall tyk-postgres -n tyk +helm uninstall tyk-redis -n tyk + +# Remove secrets and persistent volume claims +kubectl delete secrets -n tyk --all +kubectl delete pvc -n tyk --all + +# Delete the namespace +kubectl delete namespace tyk +``` + +To remove cert-manager (only if you installed it specifically for Tyk): + +```bash +kubectl delete namespace cert-manager +``` + +## Troubleshooting + + + + + +Pods fail to initialize or remain in a pending state. + +```bash +# Check pod details +kubectl describe pod -n tyk + +# Check recent events +kubectl get events -n tyk --sort-by='.lastTimestamp' | tail -20 + +# Check pod logs +kubectl logs -n tyk +``` + + + + + +Missing or incorrectly configured Kubernetes secrets prevent proper authentication and configuration. + +```bash +# Verify the secret exists and has all required keys +kubectl get secret tyk-conf -n tyk -oyaml + +# Decode and verify specific values +kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.APISecret}' | base64 -d && echo +kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.DashLicense}' | base64 -d && echo + +# If the secret is missing or incorrect, recreate it +kubectl delete secret tyk-conf -n tyk +# Re-export the environment variables and run Step 3 again +source .env +``` + + + + + +Developer Portal authentication fails with a bad request error. + +Set `PORTAL_DISABLECSRFCHECK=true` in `values.yaml` under `tyk-dev-portal.extraEnvs`. This is needed when accessing the Developer Portal via HTTP or a LoadBalancer IP. Set it to `false` when using a proper domain with TLS and Ingress. + + + + + +The Developer Portal or Tyk Dashboard cannot connect to the PostgreSQL database. + +```bash +# Check PostgreSQL is running +kubectl get pods -n tyk -l app.kubernetes.io/name=postgresql + +# Test database connectivity from a pod +kubectl exec -it -n tyk deployment/dashboard-tyk-tyk-dashboard -- /bin/sh +# Inside the pod: +env | grep DATABASE + +# Verify the connection string format in the secret +kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.DashDatabaseConnectionString}' | base64 -d && echo +# Expected format: postgresql://user:password@host:5432/database + +# Check PostgreSQL logs +kubectl logs -n tyk -l app.kubernetes.io/name=postgresql +``` + + + + + +The Tyk Dashboard or other components fail due to a missing or invalid license key. + +```bash +# Verify the license key is set +kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.DashLicense}' | base64 -d && echo + +# Check Dashboard logs for license errors +kubectl logs -n tyk -l app=dashboard-tyk-tyk-dashboard --tail=100 | grep -i license + +# Check license expiration in the Tyk Dashboard: +# Settings > License +``` + + + + + +Tyk Gateway fails to retrieve or display API definitions from the Tyk Dashboard. + +```bash +# Check the Gateway is connected to the Dashboard +kubectl logs -n tyk -l app=gateway-tyk-tyk-gateway --tail=100 + +# Force a Gateway reload (using APISecret for auth) +API_SECRET=$(kubectl get secret tyk-conf -n tyk -o jsonpath='{.data.APISecret}' | base64 -d) +curl -X GET http://localhost:8080/tyk/reload \ + -H "X-Tyk-Authorization: $API_SECRET" + +# Confirm the APIs are published in the Dashboard: +# APIs > check the "Published" status +``` + + + + + +The Tyk Dashboard web interface is unavailable or unreachable. + +```bash +# Check Dashboard pod status +kubectl get pods -n tyk -l app=dashboard-tyk-tyk-dashboard + +# Check the Dashboard service +kubectl get svc -n tyk | grep dashboard + +# Check Dashboard logs +kubectl logs -n tyk -l app=dashboard-tyk-tyk-dashboard --tail=100 + +# If using a LoadBalancer, verify an external IP is assigned +kubectl get svc -n tyk dashboard-svc-tyk-tyk-dashboard + +# If using Ingress, verify the ingress is created +kubectl get ingress -n tyk +kubectl describe ingress -n tyk +``` + + + + + +Tyk Operator fails to start or manage custom resources. + +```bash +# Check the operator pod is running +kubectl get pods -n tyk -l control-plane=tyk-operator-controller-manager + +# Check operator logs +kubectl logs -n tyk -l control-plane=tyk-operator-controller-manager --tail=100 + +# Verify the operator secret exists +kubectl get secret tyk-operator-conf -n tyk + +# Check the CRDs are installed +kubectl get crds | grep tyk +``` + + + + + +Ingress routes fail to direct traffic to the services. + +```bash +# Verify the ingress controller is installed +kubectl get pods -n kube-system | grep ingress # For NGINX +kubectl get pods -n kube-system | grep aws-load-balancer # For AWS LB Controller + +# Check the ingress resource +kubectl get ingress -n tyk +kubectl describe ingress -n tyk + +# For AWS ALB, check the AWS console for ALB creation +# For GKE, check the Google Cloud console for the Load Balancer + +# Verify DNS is pointing to the ingress address +nslookup gateway.yourdomain.com +``` + + + + + +## Legacy Helm Chart + + +`tyk-pro` chart is deprecated. Please use our [Tyk Stack helm chart](/product-stack/tyk-charts/tyk-stack-chart) instead. + +We recommend all users migrate to the `tyk-stack` Chart. Please review the [Configuration](/product-stack/tyk-charts/tyk-stack-chart) section of the new helm chart and cross-check with your existing configurations while planning for migration. + + +Tyk Helm chart is the preferred (and easiest) way to install **Tyk Self-Managed** on Kubernetes. +The helm chart `tyk-helm/tyk-pro` will install full Tyk platform with **Tyk Manager**, **Tyk Gateways** and **Tyk Pump** into your Kubernetes cluster. You can also choose to enable the installation of **Tyk Operator** (to manage your APIs in a declarative way). + +### Prerequisites + +1. **Tyk License** + + If you are evaluating Tyk on Kubernetes, [contact us](https://tyk.io/about/contact/) to obtain a temporary license. + +2. **Data stores** + + The following are required for a Tyk Self-Managed installation: + - Redis - Should be installed in the cluster or reachable from inside the cluster (for SaaS option). + You can find instructions for a simple Redis installation bellow. + - MongoDB or SQL - Should be installed in the cluster or be reachable by the **Tyk Manager** (for SaaS option). + + You can find supported MongoDB and SQL versions [here](/planning-for-production/database-settings). + + Installation instructions for Redis and MongoDB/SQL are detailed below. + +3. **Helm** + + Installed [Helm 3](https://helm.sh/) + Tyk Helm Chart is using Helm v3 version (i.e. not Helm v2). + +### Installing the data stores + +For Redis, MongoDB or SQL you can use these rather excellent charts provided by Bitnami + + + +
+ +```bash +helm install tyk-redis bitnami/redis -n tyk --version 19.0.2 +``` + + +Please make sure you are installing Redis versions that are supported by Tyk. Please refer to Tyk docs to get list of [supported versions](/planning-for-production/database-settings#redis). + + +Follow the notes from the installation output to get connection details and password. + +```console + Redis(TM) can be accessed on the following DNS names from within your cluster: + + tyk-redis-master.tyk.svc.cluster.local for read/write operations (port 6379) + tyk-redis-replicas.tyk.svc.cluster.local for read-only operations (port 6379) + + export REDIS_PASSWORD=$(kubectl get secret --namespace tyk tyk-redis -o jsonpath="{.data.redis-password}" | base64 --decode) +``` + +The DNS name of your Redis as set by Bitnami is `tyk-redis-master.tyk.svc.cluster.local:6379` (Tyk needs the name including the port) +You can update them in your local `values.yaml` file under `redis.addrs` and `redis.pass` +Alternatively, you can use `--set` flag to set it in Tyk installation. For example `--set redis.pass=$REDIS_PASSWORD` +
+ +
+ +```bash +helm install tyk-mongo bitnami/mongodb --set "replicaSet.enabled=true" -n tyk --version 15.1.3 +``` + + +Bitnami MongoDB images is not supported on darwin/arm64 architecture. + + +Follow the notes from the installation output to get connection details and password. The DNS name of your MongoDB as set with Bitnami is `tyk-mongo-mongodb.tyk.svc.cluster.local` and you also need to set the `authSource` parameter to `admin`. The full `mongoURL` should be similar to `mongoURL: mongodb://root:pass@tyk-mongo-mongodb.tyk.svc.cluster.local:27017/tyk_analytics?authSource=admin`. You can update them in your local `values.yaml` file under `mongo.mongoURL` Alternatively, you can use `--set` flag to set it in your Tyk installation. + + +**Important Note regarding MongoDB** + +This Helm chart enables the *PodDisruptionBudget* for MongoDB with an arbiter replica-count of 1. If you intend to perform +system maintenance on the node where the MongoDB pod is running and this maintenance requires for the node to be drained, +this action will be prevented due the replica count being 1. Increase the replica count in the helm chart deployment to +a minimum of 2 to remedy this issue. + + +
+ +
+ +```bash +helm install tyk-postgres bitnami/postgresql --set "auth.database=tyk_analytics" -n tyk --version 12.12.10 +``` + + +Please make sure you are installing PostgreSQL versions that are supported by Tyk. Please refer to Tyk docs to get list of [supported versions](/tyk-self-managed/install#requirements). + + +Follow the notes from the installation output to get connection details and password. The DNS name of your Postgres service as set by Bitnami is `tyk-postgres-postgresql.tyk.svc.cluster.local`. +You can update connection details in `values.yaml` file under `postgres`. +
+
+ +--- + +**Quick Redis and MongoDB PoC installation** + + +Another option for Redis and MongoDB, to get started quickly, is to use our **simple-redis** and **simple-mongodb** charts. +Please note that these provided charts must not ever be used in production and for anything +but a quick start evaluation only. Use external redis or Official Redis Helm chart in any other case. +We provide this chart, so you can quickly get up and running, however it is not meant for long term storage of data for example. + +```bash +helm install redis tyk-helm/simple-redis -n tyk +helm install mongo tyk-helm/simple-mongodb -n tyk +``` + + +### Instructions + +As well as our official Helm repo, you can also find it in [ArtifactHub](https://artifacthub.io/packages/helm/tyk-helm/tyk-pro). +[Open in ArtifactHub](https://artifacthub.io/packages/helm/tyk-helm/tyk-pro) + +If you are interested in contributing to our charts, suggesting changes, creating PRs or any other way, +please use [GitHub Tyk-helm-chart repo](https://github.com/TykTechnologies/tyk-helm-chart/tree/master/tyk-pro) +or contact us in [Tyk Community forum](https://community.tyk.io/) or through our sales team. + + +1. **Add Tyk official Helm repo to your local Helm repository** + + ```bash + helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ + helm repo update + ``` + +2. **Create namespace for your Tyk deployment** + + ```bash + kubectl create namespace tyk + ``` + +3. **Getting the values.yaml of the chart** + + Before we proceed with installation of the chart you need to set some custom values. + To see what options are configurable on a chart and save that options to a custom values.yaml file run: + + ```bash + helm show values tyk-helm/tyk-pro > values.yaml + ``` + +4. **License setting** + + For the **Tyk Self-Managed** chart we need to set the license key in your custom `values.yaml` file under `dash.license` field + or use `--set dash.license={YOUR-LICENSE_KEY}` with the `helm install` command. + + + Tyk Self-Managed licensing allow for different numbers of Gateway nodes to connect to a single Dashboard instance. + To ensure that your Gateway pods will not scale beyond your license allowance, please ensure that the Gateway's resource kind is `Deployment` + and the replica count to your license node limit. By default, the chart is configured to work with a single node license: `gateway.kind=Deployment` and `gateway.replicaCount=1`. + + + + **Please Note** + + There may be intermittent issues on the new pods during the rolling update process, when the total number of online + gateway pods is more than the license limit with lower amounts of Licensed nodes. + + + +5. **Installing Tyk Self managed** + + Now we can install the chart using our custom values: + + ```bash + helm install tyk-pro tyk-helm/tyk-pro -f ./values.yaml -n tyk --wait + ``` + + + + **Important Note regarding MongoDB** + + The `--wait` argument is important to successfully complete the bootstrap of your **Tyk Manager**. + + +### Pump Installation + +By default pump installation is disabled. You can enable it by setting `pump.enabled` to `true` in `values.yaml` file. +Alternatively, you can use `--set pump.enabled=true` while doing helm install. + +**Quick Pump configuration(Supported from tyk helm v0.10.0)** + +1. **Mongo Pump** + + To configure mongo pump, do following changings in `values.yaml` file: + + 1. Set `backend` to `mongo`. + 2. Set connection string in `mongo.mongoURL`. + +2. **Postgres Pump** + + To configure postgres pump, do following changings in `values.yaml` file: + + 1. Set `backend` to `postgres`. + 2. Set connection string parameters in `postgres` section. + +### Tyk Developer Portal + +You can disable the bootstrapping of the Developer Portal by the `portal.bootstrap: false` in your local `values.yaml` file. + +### Using TLS + +You can turn on the TLS option under the gateway section in your local `values.yaml` file which will make your Gateway +listen on port 443 and load up a dummy certificate. You can set your own default certificate by replacing the file in the `certs/` folder. + +### Mounting Files + +To mount files to any of the Tyk stack components, add the following to the mounts array in the section of that component. +For example: + ```bash + - name: aws-mongo-ssl-cert + filename: rds-combined-ca-bundle.pem + mountPath: /etc/certs +``` + +### Sharding APIs + +Sharding is the ability for you to decide which of your APIs are loaded on which of your Tyk Gateways. This option is +turned off by default, however, you can turn it on by updating the `gateway.sharding.enabled` option. Once you do that you +will also need to set the `gateway.sharding.tags` field with the tags that you want that particular Gateway to load. (ex. tags: "external,ingress".) +You can then add those tags to your APIs in the API Designer, under the **Advanced Options** tab, and +the **Segment Tags (Node Segmentation)** section in your Tyk Dashboard. +Check [Tyk Gateway Sharding](/api-management/api-sharding#what-is-api-sharding-) for more details. diff --git a/tyk-self-managed/install/linux.mdx b/tyk-self-managed/install/linux.mdx new file mode 100644 index 0000000000..b4e5905401 --- /dev/null +++ b/tyk-self-managed/install/linux.mdx @@ -0,0 +1,1002 @@ +--- +title: "Install Tyk Self-Managed on Linux" +description: "Installation guide for the Tyk Self-Managed on on Linux distributions using Ubuntu, Debian, Red Hat, and CentOS" +sidebarTitle: "Linux" +--- + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| Enterprise | Self-Managed, Hybrid | + +## Compatible Operating Systems + +| Operating System | Version | +|-----------------|---------| +| Ubuntu | 20.04 (focal), 22.04 (jammy), 24.04 (noble) | +| Red Hat Enterprise Linux | 7.9, 8.9, 9.3 | +| CentOS Stream | Stream 9, Stream 10 | +| Debian | 11 (Bullseye), 12 (Bookworm) | +| Amazon Linux | 2023.7 | + +### Binaries + +Installation packages for supported Linux distributions are available on [packagecloud.io](https://packagecloud.io/tyk). + +## Prerequisites + +- [Enterprise Edition License](/apim#licensing) + +## Red Hat (RHEL / CentOS) + + +There are 4 components which needs to be installed. + +### Install Database + +#### Redis + +Tyk Gateway has a [dependency](/planning-for-production/database-settings#redis) on Redis. Follow the steps provided by Red Hat to make the installation of Redis, conducting a [search](https://access.redhat.com/search/?q=redis) for the correct version and distribution. + +#### Storage Database + +Tyk Dashboard has a dependency on a storage database that can be [PostgreSQL](/planning-for-production/database-settings#postgresql) or [MongoDB](/planning-for-production/database-settings#mongodb-sizing-guidelines). + + + + + +Check the PostgreSQL supported [versions](/planning-for-production/database-settings#postgresql). Follow the steps provided by [PostgreSQL](https://www.postgresql.org/download/linux/redhat/) to install it. + +Configure PostgreSQL + +Create a new role/user +```console +sudo -u postgres createuser --interactive +``` +The name of the role can be "tyk" and say yes to make it a superuser + +Create a matching DB with the same name. Postgres authentication system assumes by default that for any role used to log in, that role will have a database with the same name which it can access. +```console +sudo -u postgres createdb tyk +``` +Add another user to be used to log into your operating system + +```console +sudo adduser tyk +``` +Log in to your Database +```console +sudo -u tyk psql +``` +Update the user “tyk” to have a password +```console +ALTER ROLE tyk with PASSWORD '123456'; +``` +Create a DB (my example is tyk_analytics) +```console +sudo -u tyk createdb tyk_analytics +``` + + + + + +Check the MongoDB supported [versions](/planning-for-production/database-settings#mongodb-sizing-guidelines). Follow the steps provided by [MongoDB](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-red-hat/) to install it. + +Optionally initialize the database and enable automatic start: +```console +# Optionally ensure that MongoDB will start following a system reboot +sudo systemctl enable mongod +# start MongoDB server +sudo systemctl start mongod +``` + + + + + +### Install Dashboard + +Tyk has its own signed RPMs in a YUM repository hosted by the kind folks at [packagecloud.io](https://packagecloud.io/tyk/tyk-dashboard/install#manual-rpm), which makes it easy, safe and secure to install a trusted distribution of the Tyk Gateway stack. + +This configuration should also work (with some tweaks) for CentOS. + +**Prerequisites** + +* Ensure port `3000` is open: This is used by the Dashboard to provide the GUI and the Classic Developer Portal. +* Follow the steps provided in this link [Getting started on Red Hat (RHEL / CentOS)](#install-tyk-on-redhat-rhel-centos) to install and configure Tyk dependencies. + +1. **Set up YUM Repositories** + + First, install two package management utilities `yum-utils` and a file downloading tool `wget`: + ```bash + sudo yum install yum-utils wget + ``` + Then install Python: + ```bash + sudo yum install python3 + ``` + +2. **Configure and Install the Tyk Dashboard** + + Create a file named `/etc/yum.repos.d/tyk_tyk-dashboard.repo` that contains the repository configuration settings for YUM repositories `tyk_tyk-dashboard` and `tyk_tyk-dashboard-source` used to download packages from the specified URLs, including GPG key verification and SSL settings, on a Linux system. + + Make sure to replace `el` and `8` in the config below with your Linux distribution and version: + ```bash + [tyk_tyk-dashboard] + name=tyk_tyk-dashboard + baseurl=https://packagecloud.io/tyk/tyk-dashboard/el/8/$basearch + repo_gpgcheck=1 + gpgcheck=0 + enabled=1 + gpgkey=https://packagecloud.io/tyk/tyk-dashboard/gpgkey + sslverify=1 + sslcacert=/etc/pki/tls/certs/ca-bundle.crt + metadata_expire=300 + + [tyk_tyk-dashboard-source] + name=tyk_tyk-dashboard-source + baseurl=https://packagecloud.io/tyk/tyk-dashboard/el/8/SRPMS + repo_gpgcheck=1 + gpgcheck=0 + enabled=1 + gpgkey=https://packagecloud.io/tyk/tyk-dashboard/gpgkey + sslverify=1 + sslcacert=/etc/pki/tls/certs/ca-bundle.crt + metadata_expire=300 + ``` + + We'll need to update the YUM package manager's local cache, enabling only the `tyk_tyk-dashboard` repository while disabling all other repositories `--disablerepo='*' --enablerepo='tyk_tyk-dashboard'`, and confirm all prompts `-y`. + ```bash + sudo yum -q makecache -y --disablerepo='*' --enablerepo='tyk_tyk-dashboard' + ``` + + Install Tyk dashboard: + ```bash + sudo yum install -y tyk-dashboard + ``` + +3. **Confirm Redis and MongoDB or PostgreSQL are running** + + Start Redis since it is always required by the Dashboard. + ```bash + sudo service redis start + ``` + Then start either MongoDB or PostgreSQL depending on which one you are using. + ```bash + sudo systemctl start mongod + ``` + ```bash + sudo systemctl start postgresql-13 + ``` + +4. **Configure Tyk Dashboard** + +We can set the Dashboard up with a similar setup command, the script below will get the Dashboard set up for the local instance. +Make sure to use the actual DNS hostname or the public IP of your instance as the last parameter. + + + + +```bash +sudo /opt/tyk-dashboard/install/setup.sh --listenport=3000 --redishost= --redisport=6379 --mongo=mongodb://:/tyk_analytics --tyk_api_hostname=$HOSTNAME --tyk_node_hostname=http://localhost --tyk_node_port=8080 --portal_root=/portal --domain="XXX.XXX.XXX.XXX" +``` + +Replace ``, `` and `` with your own values to run this script. + + + + +```bash +sudo /opt/tyk-dashboard/install/setup.sh --listenport=3000 --redishost= --redisport=6379 --storage=postgres --connection_string=postgresql://:@:/ --tyk_api_hostname=$HOSTNAME --tyk_node_hostname=http://localhost --tyk_node_port=8080 --portal_root=/portal --domain="XXX.XXX.XXX.XXX" +``` + +Replace ``,``,``, ``, `` and `` with your own values to run the script. + + + + +With these values your are configuring the following: + +* `--listenport=3000`: Tyk Dashboard (and Portal) to listen on port `3000`. +* `--redishost=`: Tyk Dashboard should use the local Redis instance. +* `--redisport=6379`: The Tyk Dashboard should use the default port. +* `--domain="XXX.XXX.XXX.XXX"`: Bind the Dashboard to the IP or DNS hostname of this instance (required). +* `--mongo=mongodb://:/tyk_analytics`: Use the local MongoDB (should always be the same as the Gateway). +* `--storage=postgres`: In case, your preferred storage Database is PostgreSQL, use storage type "postgres" and specify connection string. +* `--connection_string=postgresql://:@:/`: Use the PostgreSQL instance provided in the connection string (should always be the same as the gateway). +* `--tyk_api_hostname=$HOSTNAME`: The Tyk Dashboard has no idea what hostname has been given to Tyk, so we need to tell it, in this instance we are just using the local HOSTNAME env variable, but you could set this to the public-hostname/IP of the instance. +* `--tyk_node_hostname=http://localhost`: The Tyk Dashboard needs to see a Tyk node in order to create new tokens, so we need to tell it where we can find one, in this case, use the one installed locally. +* `--tyk_node_port=8080`: Tell the Dashboard that the Tyk node it should communicate with is on port 8080. +* `--portal_root=/portal`: We want the Portal to be shown on /portal of whichever domain we set for the Portal. + +5. **Start Tyk Dashboard** + + ```bash + sudo service tyk-dashboard start + ``` + + + + To check the logs from the deployment run: + ```bash + sudo journalctl -u tyk-dashboard + ``` + + + + Notice how we haven't actually started the gateway yet, because this is a Dashboard install, we need to enter a license first. + + + +When using PostgreSQL you may receive the error: `"failed SASL auth (FATAL: password authentication failed for user...)"`, follow these steps to address the issue: +1. Open the terminal or command prompt on your PostgreSQL server. +2. Navigate to the location of the `pg_hba.conf` file. This file is typically located at `/var/lib/pgsql/13/data/pg_hba.conf`. +3. Open the `pg_hba.conf` file using a text manipulation tool. +4. In the `pg_hba.conf` file, locate the entry corresponding to the user encountering the authentication error. This entry might resemble the following: +```bash +host all all / scram-sha-256 +``` +5. In the entry, find the METHOD column. It currently has the value scram-sha-256. +6. Replace scram-sha-256 with md5, so the modified entry looks like this: +```bash +host all all / md5 +``` +7. Save the changes you made to the `pg_hba.conf` file. +8. Restart the PostgreSQL service to apply the modifications: +```bash +sudo systemctl restart postgresql-13 +``` + + + +6. **Enter Dashboard license** + + Add your license in `/var/opt/tyk-dashboard/tyk_analytics.conf` in the `license` field. + + If all is going well, you will be taken to a Dashboard setup screen - we'll get to that soon. + +7. **Restart the Dashboard process** + + Because we've just entered a license via the UI, we need to make sure that these changes get picked up, so to make sure things run smoothly, we restart the Dashboard process (you only need to do this once) and (if you have it installed) then start the gateway: + ```bash + sudo service tyk-dashboard restart + ``` + +8. **Go to the Tyk Dashboard URL** + + Go to the following URL to access to the Tyk Dashboard: + + ```bash + 127.0.0.1:3000 + ``` + + You should get to the Tyk Dashboard Setup screen: + + Tyk Dashboard Bootstrap Screen + +9. **Create your Organization and Default User** + + You need to enter the following: + + * Your **Organization Name** + * Your **Organization Slug** + * Your User **Email Address** + * Your User **First and Last Name** + * A **Password** for your User + * **Re-enter** your user **Password** + + + + + + For a password, we recommend a combination of alphanumeric characters, with both upper and lower case letters. + + + + + Click **Bootstrap** to save the details. + +10. **Login to the Dashboard** + + You can now log in to the Tyk Dashboard from `127.0.0.1:3000`, using the username and password created in the Dashboard Setup screen. + + **Configure your Developer Portal** + + To set up your [Developer Portal](/portal/overview/intro) follow our Self-Managed [tutorial on publishing an API to the Portal Catalog](/getting-started/tutorials/publish-api). + +### Install Pump + + +Tyk has it's own signed RPMs in a YUM repository hosted by the kind folks at [packagecloud.io](https://packagecloud.io), which makes it easy, safe and secure to install a trusted distribution of the Tyk Gateway stack. + +This tutorial will run on an [Amazon AWS](http://aws.amazon.com) *Red Hat Enterprise Linux 7.1* instance. We will install Tyk Pump with all dependencies stored locally. + +We're installing on a `t2.micro` because this is a tutorial, you'll need more RAM and more cores for better performance. + +This configuration should also work (with some tweaks) for CentOS. + +**Prerequisites** + +We are assuming that Redis and either MongoDB or SQL are installed (these are installed as part of the Tyk Gateway and Dashboard installation guides) + +**Step 1: Set up YUM Repositories** + +First, we need to install some software that allows us to use signed packages: +```bash +sudo yum install pygpgme yum-utils wget +``` + +Next, we need to set up the various repository configurations for Tyk and MongoDB: + +Create a file named `/etc/yum.repos.d/tyk_tyk-pump.repo` that contains the repository configuration below: + +Make sure to replace `el` and `7` in the config below with your Linux distribution and version: +```bash +[tyk_tyk-pump] +name=tyk_tyk-pump +baseurl=https://packagecloud.io/tyk/tyk-pump/el/7/$basearch +repo_gpgcheck=1 +gpgcheck=1 +enabled=1 +gpgkey=https://keyserver.tyk.io/tyk.io.rpm.signing.key.2020 + https://packagecloud.io/tyk/tyk-pump/gpgkey +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +metadata_expire=300 +``` + +Finally we'll need to update our local cache, so run: +```bash +sudo yum -q makecache -y --disablerepo='*' --enablerepo='tyk_tyk-pump' +``` + +**Step 2: Install Packages** + +We're ready to go, you can now install the relevant packages using yum: +```bash +sudo yum install -y tyk-pump +``` + +**(You may be asked to accept the GPG key for our repos and when the package installs, hit yes to continue.)** +
+ +**Step 3: Configure Tyk Pump** + +If you don't complete this step, you won't see any analytics in your Dashboard, so to enable the analytics service, we need to ensure Tyk Pump is running and configured properly. + + +**Configure Tyk Pump for MongoDB** +
+ + +You need to replace `` for `--redishost=`, and ``, `` for `--mongo=mongodb://:/` with your own values to run this script. + + + +```bash +sudo /opt/tyk-pump/install/setup.sh --redishost= --redisport=6379 --mongo=mongodb://:/tyk_analytics +``` +**Configure Tyk Pump for SQL** +
+ + +You need to replace `` for `--redishost=`, and ``,``, ``, ``, `` for `--postgres="host= port= user= password= dbname="` with your own values to run this script. + + +```bash +sudo /opt/tyk-pump/install/setup.sh --redishost= --redisport=6379 --postgres="host= port= user= password= dbname=" +``` + + +**Step 4: Start Tyk Pump** + +```bash +sudo service tyk-pump start +``` + +That's it, the Pump should now be up and running. + +You can verify if Tyk Pump is running and working by accessing the logs: +```bash +sudo journalctl -u tyk-pump +``` +### Install Gateway + + +Tyk has it's own signed RPMs in a YUM repository hosted by the kind folks at [packagecloud.io](https://packagecloud.io/tyk/tyk-dashboard/install#manual-rpm), which makes it easy, safe and secure to install a trusted distribution of the Tyk Gateway stack. + +This tutorial will run on an [Amazon AWS](http://aws.amazon.com) *Red Hat Enterprise Linux 7.1* instance. We will install Tyk Gateway with all dependencies stored locally. + +We're installing on a `t2.micro` because this is a tutorial, you'll need more RAM and more cores for better performance. + +This configuration should also work (with some tweaks) for CentOS. + +**Prerequisites** + +* Ensure port `8080` is open: this is used in this guide for Gateway traffic (API traffic to be proxied) +* EPEL (Extra Packages for Enterprise Linux) is a free, community based repository project from Fedora which provides high quality add-on software packages for Linux distribution including RHEL, CentOS, and Scientific Linux. EPEL isn’t a part of RHEL/CentOS but it is designed for major Linux distributions. In our case we need it for Redis. Install EPEL using the instructions here. + +**Step 1: Set up YUM Repositories** + +First, we need to install some software that allows us to use signed packages: +```bash +sudo yum install pygpgme yum-utils wget +``` + +Next, we need to set up the various repository configurations for Tyk and MongoDB: + +**Step 2: Create Tyk Gateway Repository Configuration** + +Create a file named `/etc/yum.repos.d/tyk_tyk-gateway.repo` that contains the repository configuration below https://packagecloud.io/tyk/tyk-gateway/install#manual-rpm: +```bash +[tyk_tyk-gateway] +name=tyk_tyk-gateway +baseurl=https://packagecloud.io/tyk/tyk-gateway/el/7/$basearch +repo_gpgcheck=1 +gpgcheck=1 +enabled=1 +gpgkey=https://keyserver.tyk.io/tyk.io.rpm.signing.key.2020 + https://packagecloud.io/tyk/tyk-gateway/gpgkey +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +metadata_expire=300 +``` + +**Step 3: Install Packages** + +We're ready to go, you can now install the relevant packages using yum: +```bash +sudo yum install -y redis tyk-gateway +``` + +*(you may be asked to accept the GPG key for our two repos and when the package installs, hit yes to continue)* + +**Step 4: Start Redis** + +In many cases Redis will not be running, so let's start those: +```bash +sudo service redis start +``` + +When Tyk is finished installing, it will have installed some init scripts, but it will not be running yet. The next step will be to setup the Gateway – thankfully this can be done with three very simple commands. + +## Install Tyk on Debian or Ubuntu + +### Install Database + + +**Requirements** + +Before installing the Tyk components in the order below, you need to first install Redis and MongoDB/SQL. + +**Getting Started** + + + +**Install MongoDB 4.0** + +You should follow the [online tutorial for installing MongoDb](https://docs.mongodb.com/v4.0/tutorial/install-mongodb-on-ubuntu/). We will be using version 4.0. As part of the Mongo installation you need to perform the following: + +1. Import the public key +2. Create a list file +3. Reload the package database +4. Install the MongoDB packages +5. Start MongoDB +6. Check the `mongod` service is running + + + + +**Install SQL** + +You should follow the [online tutorial for installing PostgreSQL](https://www.postgresql.org/download/linux/ubuntu/). We will be using version 13. As part of the PostgreSQL installation you need to perform the following: + +1. Create the file repository configuration +2. Import the repository signing key +3. Update the package lists +4. Install the PostgreSQL packages +5. Start PostgreSQL +6. Check the `postgresql` service is running + +See [SQL configuration](/planning-for-production/database-settings#postgresql) for details on installing SQL in a production environment. + + + +**Install Redis** + +```console +$ sudo apt-get install -y redis-server +``` + + +For a production environment, we recommend that the Gateway, Dashboard and Pump are installed on separate machines. If installing multiple Gateways, you should install each on a separate machine. See [Planning for Production](/planning-for-production) For more details. + + +### Install Dashboard + + + +Tyk has its own APT repositories hosted by the kind folks at [packagecloud.io](https://packagecloud.io/tyk), which makes it easy, safe and secure to install a trusted distribution of the Tyk Gateway stack. + +This tutorial has been tested on Ubuntu 16.04 & 18.04 with few if any modifications. We will install the Tyk Dashboard with all dependencies locally. + +**Prerequisites** +- Have MongoDB/SQL and Redis installed - follow the guide for [installing databases on Debian/Ubuntu](#install-tyk-on-debian-or-ubuntu). +- Ensure port `3000` is available. This is used by the Tyk Dashboard to provide the GUI and the Developer Portal. + +**Step 1: Set up our APT Repositories** + +First, add our GPG key which signs our binaries: + +```bash +curl -L https://packagecloud.io/tyk/tyk-dashboard/gpgkey | sudo apt-key add - +``` + +Run update: + +```bash +sudo apt-get update +``` + +Since our repositories are installed via HTTPS, you will need to make sure APT supports this: + +```bash +sudo apt-get install -y apt-transport-https +``` + +Now lets add the required repos and update again (notice the `-a` flag in the second Tyk commands - this is important!): + +```bash +echo "deb https://packagecloud.io/tyk/tyk-dashboard/ubuntu/ bionic main" | sudo tee /etc/apt/sources.list.d/tyk_tyk-dashboard.list + +echo "deb-src https://packagecloud.io/tyk/tyk-dashboard/ubuntu/ bionic main" | sudo tee -a /etc/apt/sources.list.d/tyk_tyk-dashboard.list + +sudo apt-get update +``` + + + +`bionic` is the code name for Ubuntu 18.04. Please substitute it with your particular [ubuntu release](https://releases.ubuntu.com/), e.g. `focal`. + + + +**What we've done here is:** + +- Added the Tyk Dashboard repository +- Updated our package list + +**Step 2: Install the Tyk Dashboard** + +We're now ready to install the Tyk Dashboard. To install run: + +```bash +sudo apt-get install -y tyk-dashboard +``` + +What we've done here is instructed `apt-get` to install the Tyk Dashboard without prompting. Wait for the downloads to complete. + +When the Tyk Dashboard has finished installing, it will have installed some `init` scripts, but it will not be running yet. The next step will be to setup each application - thankfully this can be done with three very simple commands. + +**Verify the origin key (optional)** + +Debian packages are signed with the repository keys. These keys are verified at the time of fetching the package and is taken care of by the `apt` infrastructure. These keys are controlled by PackageCloud, our repository provider. For an additional guarantee, it is possible to verify that the package was indeed created by Tyk by verifying the `origin` certificate that is attached to the package. + +First, you have to fetch Tyk's signing key and import it. + +```bash +wget https://keyserver.tyk.io/tyk.io.deb.signing.key +gpg --import tyk.io.deb.signing.key +``` + +Then, you have to either, +- sign the key with your ultimately trusted key +- trust this key ultimately + +The downloaded package will be available in `/var/cache/apt/archives`. Assuming you found the file `tyk-gateway-2.9.4_amd64.deb` there, you can verify the origin signature. + +```bash +gpg --verify d.deb +gpg: Signature made Wed 04 Mar 2020 03:05:00 IST +gpg: using RSA key F3781522A858A2C43D3BC997CA041CD1466FA2F8 +gpg: Good signature from "Team Tyk (package signing) " [ultimate] +``` + +##### **Configure Tyk Dashboard** + +**Prerequisites for MongoDB** + +You need to ensure the MongoDB and Redis services are running before proceeding. + + + +You need to replace `` for `--redishost=`, and `` for `--mongo=mongodb:///` with your own values to run this script. + + + + +You can set your Tyk Dashboard up with a helper setup command script. This will get the Dashboard set up for the local instance: + +```bash +sudo /opt/tyk-dashboard/install/setup.sh --listenport=3000 --redishost= --redisport=6379 --mongo=mongodb:///tyk_analytics --tyk_api_hostname=$HOSTNAME --tyk_node_hostname=http://localhost --tyk_node_port=8080 --portal_root=/portal --domain="XXX.XXX.XXX.XXX" +``` + + + +Make sure to use the actual DNS hostname or the public IP of your instance as the last parameter. + + + + +What we have done here is: + +- `--listenport=3000`: Told the Tyk Dashboard (and Portal) to listen on port 3000. +- `--redishost=`: The Tyk Dashboard should use the local Redis instance. +- `--redisport=6379`: The Tyk Dashboard should use the default port. +- `--domain="XXX.XXX.XXX.XXX"`: Bind the Tyk Dashboard to the IP or DNS hostname of this instance (required). +- `--mongo=mongodb:///tyk_analytics`: Use the local MongoDB (should always be the same as the gateway). +- `--tyk_api_hostname=$HOSTNAME`: The Tyk Dashboard has no idea what hostname has been given to Tyk, so we need to tell it, in this instance we are just using the local HOSTNAME env variable, but you could set this to the public-hostname/IP of the instance. +- `--tyk_node_hostname=http://localhost`: The Tyk Dashboard needs to see a Tyk node in order to create new tokens, so we need to tell it where we can find one, in this case, use the one installed locally. +- `--tyk_node_port=8080`: Tell the Tyk Dashboard that the Tyk node it should communicate with is on port 8080. +- `--portal_root=/portal`: We want the portal to be shown on `/portal` of whichever domain we set for the portal. + +**Prerequisites for SQL** + +You need to ensure the PostgreSQL and Redis services are running before proceeding. + + + +You need to replace `` for `--redishost=`, and ``, ``, ``, ``, `` for `--connection_string="host= port= user= password= dbname="` with your own values to run this script. + + + + +You can set the Tyk Dashboard up with a helper setup command script. This will get the Dashboard set up for the local instance: + +```bash +sudo /opt/tyk-dashboard/install/setup.sh --listenport=3000 --redishost= --redisport=6379 --storage=postgres --connection_string="host= port= user= password= dbname=" --tyk_api_hostname=$HOSTNAME --tyk_node_hostname=http://localhost --tyk_node_port=8080 --portal_root=/portal --domain="XXX.XXX.XXX.XXX" +``` + + + +Make sure to use the actual DNS hostname or the public IP of your instance as the last parameter. + + + + +What we have done here is: + +- `--listenport=3000`: Told the Tyk Dashboard (and Portal) to listen on port 3000. +- `--redishost=`: The Tyk Dashboard should use the local Redis instance. +- `--redisport=6379`: The Tyk Dashboard should use the default port. +- `--domain="XXX.XXX.XXX.XXX"`: Bind the dashboard to the IP or DNS hostname of this instance (required). +- `--storage=postgres`: Use storage type postgres. +- `--connection_string="host= port= user= password= dbname="`: Use the postgres instance provided in the connection string(should always be the same as the gateway). +- `--tyk_api_hostname=$HOSTNAME`: The Tyk Dashboard has no idea what hostname has been given to Tyk, so we need to tell it, in this instance we are just using the local HOSTNAME env variable, but you could set this to the public-hostname/IP of the instance. +- `--tyk_node_hostname=http://localhost`: The Tyk Dashboard needs to see a Tyk node in order to create new tokens, so we need to tell it where we can find one, in this case, use the one installed locally. +- `--tyk_node_port=8080`: Tell the dashboard that the Tyk node it should communicate with is on port 8080. +- `--portal_root=/portal`: We want the portal to be shown on `/portal` of whichever domain we set for the portal. + + +**Step 1: Enter your Tyk Dashboard License** + +Add your license in `/opt/tyk-dashboard/tyk_analytics.conf` in the `license` field. + +**Step 2: Start the Tyk Dashboard** + +Start the dashboard service, and ensure it will start automatically on system boot. + +```bash +sudo systemctl start tyk-dashboard +sudo systemctl enable tyk-dashboard +``` + +**Step 3: Install your Tyk Gateway** + +Follow the [Gateway installation instructions](#using-shell-7) to connect to your Dashboard instance before you continue on to step 4. + +**Step 4: Bootstrap the Tyk Dashboard with an initial User and Organization** + +Go to: + +```bash +127.0.0.1:3000 +``` + +You should get to the Tyk Dashboard Setup screen: + +Tyk Dashboard Bootstrap Screen + +**Step 5 - Create your Organization and Default User** + +You need to enter the following: + +- Your **Organization Name** +- Your **Organization Slug** +- Your User **Email Address** +- Your User **First and Last Name** +- A **Password** for your User +- **Re-enter** your user **Password** + + + + + For a password, we recommend a combination of alphanumeric characters, with both upper and lower case + letters. + + + +Click **Bootstrap** to save the details. + +**Step 6 - Login to the Tyk Dashboard** + +You can now log in to the Tyk Dashboard from `127.0.0.1:3000`, using the username and password created in the Dashboard Setup screen. + +##### **Configure your Developer Portal** + +To set up your [Developer Portal](/portal/overview/intro) follow our Self-Managed [tutorial on publishing an API to the Portal Catalog](/getting-started/tutorials/publish-api). + +### Install Pump + + + +This tutorial has been tested Ubuntu 16.04 & 18.04 with few if any modifications. + +**Prerequisites** + +- You have installed Redis and either MongoDB or SQL. +- You have installed the Tyk Dashboard. + +**Step 1: Set up our APT repositories** + +First, add our GPG key which signs our binaries: + +```bash +curl -L https://packagecloud.io/tyk/tyk-pump/gpgkey | sudo apt-key add - +``` + +Run update: + +```bash +sudo apt-get update +``` + +Since our repositories are installed via HTTPS, you will need to make sure APT supports this: + +```bash +sudo apt-get install -y apt-transport-https +``` + +Now lets add the required repos and update again (notice the `-a` flag in the second Tyk commands - this is important!): + +```bash +echo "deb https://packagecloud.io/tyk/tyk-pump/ubuntu/ bionic main" | sudo tee /etc/apt/sources.list.d/tyk_tyk-pump.list + +echo "deb-src https://packagecloud.io/tyk/tyk-pump/ubuntu/ bionic main" | sudo tee -a /etc/apt/sources.list.d/tyk_tyk-pump.list + +sudo apt-get update +``` + + + +`bionic` is the code name for Ubuntu 18.04. Please substitute it with your particular [ubuntu release](https://releases.ubuntu.com/), e.g. `focal`. + + + +**What you've done here is:** + +- Added the Tyk Pump repository +- Updated our package list + +**Step 2: Install the Tyk Pump** + +You're now ready to install the Tyk Pump. To install it, run: + +```bash +sudo apt-get install -y tyk-pump +``` + +What you've done here is instructed `apt-get` to install Tyk Pump without prompting. Wait for the downloads to complete. + +When Tyk Pump has finished installing, it will have installed some `init` scripts, but it will not be running yet. The next step will be to setup each application using three very simple commands. + +**Verify the origin key (optional)** + +Debian packages are signed with the repository keys. These keys are verified at the time of fetching the package and is taken care of by the `apt` infrastructure. These keys are controlled by PackageCloud, our repository provider. For an additional guarantee, it is possible to verify that the package was indeed created by Tyk by verifying the `origin` certificate that is attached to the package. + +First, you have to fetch Tyk's signing key and import it. + +```bash +wget https://keyserver.tyk.io/tyk.io.deb.signing.key +gpg --import tyk.io.deb.signing.key +``` + +Then, you have to either, +- sign the key with your ultimately trusted key +- trust this key ultimately + +The downloaded package will be available in `/var/cache/apt/archives`. Assuming you found the file `tyk-gateway-2.9.3_amd64.deb` there, you can verify the origin signature. + +```bash +gpg --verify d.deb +gpg: Signature made Wed 04 Mar 2020 03:05:00 IST +gpg: using RSA key F3781522A858A2C43D3BC997CA041CD1466FA2F8 +gpg: Good signature from "Team Tyk (package signing) " [ultimate] +``` + +**Step 3: Configure Tyk Pump** + +If you don't complete this step, you won't see any analytics in your Dashboard, so to enable the analytics service, we need to ensure Tyk Pump is running and configured properly. + +**Option 1: Configure Tyk Pump for MongoDB** +
+ + +You need to replace `` for `--redishost=`, and `` for `--mongo=mongodb:///` with your own values to run this script. + + + +```bash +sudo /opt/tyk-pump/install/setup.sh --redishost= --redisport=6379 --mongo=mongodb:///tyk_analytics +``` + +**Option 2: Configure Tyk Pump for SQL** +
+ + +You need to replace `` for `--redishost=`, and ``,``, ``, ``, `` for `--postgres="host= port= user= password= dbname="` with your own values to run this script. + + + +```bash +sudo /opt/tyk-pump/install/setup.sh --redishost= --redisport=6379 --postgres="host= port= user= password= dbname=" +``` + +**Step 4: Start Tyk Pump** + +```bash +sudo service tyk-pump start +sudo service tyk-pump enable +``` + +You can verify if Tyk Pump is running and working by tailing the log file: + +```bash +sudo tail -f /var/log/upstart/tyk-pump.log +``` +### Install Gateway + + + +Tyk has it's own APT repositories hosted by the kind folks at [packagecloud.io][1], which makes it easy, safe and secure to install a trusted distribution of the Tyk Gateway stack. + +This tutorial has been tested on Ubuntu 16.04 & 18.04 with few if any modifications. + +Please note however, that should you wish to write your own plugins in Python, we currently have a Python version dependency of 3.4. Python-3.4 ships with Ubuntu 14.04, however you may need to explicitly install it on newer Ubuntu Operating System releases. + +**Prerequisites** + +* Ensure port `8080` is available. This is used in this guide for Gateway traffic (API traffic to be proxied). +* You have MongoDB and Redis installed. +* You have installed firstly the Tyk Dashboard, then the Tyk Pump. + +**Step 1: Set up our APT Repositories** + +First, add our GPG key which signs our binaries: + +```bash +curl -L https://packagecloud.io/tyk/tyk-gateway/gpgkey | sudo apt-key add - +``` + +Run update: +```bash +sudo apt-get update +``` + +Since our repositories are installed via HTTPS, you will need to make sure APT supports this: +```bash +sudo apt-get install -y apt-transport-https +``` + +Create a file `/etc/apt/sources.list.d/tyk_tyk-gateway.list` with the following contents: +```bash +deb https://packagecloud.io/tyk/tyk-gateway/ubuntu/ bionic main +deb-src https://packagecloud.io/tyk/tyk-gateway/ubuntu/ bionic main +``` + + +`bionic` is the code name for Ubuntu 18.04. Please substitute it with your particular [ubuntu release](https://releases.ubuntu.com/), e.g. `focal`. + + + +Now you can refresh the list of packages with: +```bash +sudo apt-get update +``` + +**What we've done here is:** + +* Added the Tyk Gateway repository +* Updated our package list + +**Step 2: Install the Tyk Gateway** + +We're now ready to install the Tyk Gateway. To install it, run: + +```bash +sudo apt-get install -y tyk-gateway +``` +What we've done here is instructed apt-get to install the Tyk Gateway without prompting, wait for the downloads to complete. + +When Tyk has finished installing, it will have installed some init scripts, but will not be running yet. The next step will be to set up the Gateway - thankfully this can be done with three very simple commands, however it does depend on whether you are configuring Tyk Gateway for use with the Dashboard or without (the Community Edition). + +**Verify the origin key (optional)** + +Debian packages are signed with the repository keys. These keys are verified at the time of fetching the package and is taken care of by the `apt` infrastructure. These keys are controlled by PackageCloud, our repository provider. For an additional guarantee, it is possible to verify that the package was indeed created by Tyk by verifying the `origin` certificate that is attached to the package. + +First, you have to fetch Tyk's signing key and import it. + +```bash +wget https://keyserver.tyk.io/tyk.io.deb.signing.key +gpg --import tyk.io.deb.signing.key +``` + +Then, you have to either, +- sign the key with your ultimately trusted key +- trust this key ultimately + +The downloaded package will be available in `/var/cache/apt/archives`. Assuming you found the file `tyk-gateway-2.9.4_amd64.deb` there, you can verify the origin signature. + +```bash +gpg --verify d.deb +gpg: Signature made Wed 04 Mar 2020 03:05:00 IST +gpg: using RSA key F3781522A858A2C43D3BC997CA041CD1466FA2F8 +gpg: Good signature from "Team Tyk (package signing) " [ultimate] +``` + +**Configure Tyk Gateway with Dashboard** + +**Prerequisites** + +This configuration assumes that you have already installed the Tyk Dashboard, and have decided on the domain names for your Dashboard and your Portal. **They must be different**. For testing purposes, it is easiest to add hosts entries to your (and your servers) `/etc/hosts` file. + +**Set up Tyk** + +You can set up the core settings for Tyk Gateway with a single setup script, however for more involved deployments, you will want to provide your own configuration file. + + + +You need to replace `` for `--redishost=`with your own value to run this script. + + + + +```bash +sudo /opt/tyk-gateway/install/setup.sh --dashboard=1 --listenport=8080 --redishost= --redisport=6379 +``` + +What we've done here is told the setup script that: + +* `--dashboard=1`: We want to use the Dashboard, since Tyk Gateway gets all it's API Definitions from the Dashboard service, as of v2.3 Tyk will auto-detect the location of the dashboard, we only need to specify that we should use this mode. +* `--listenport=8080`: Tyk should listen on port 8080 for API traffic. +* `--redishost=`: Use Redis on your hostname. +* `--redisport=6379`: Use the default Redis port. + +**Starting Tyk** + +The Tyk Gateway can be started now that it is configured. Use this command to start the Tyk Gateway: +```bash +sudo service tyk-gateway start +sudo service tyk-gateway enable +``` + +**Pro Tip: Domains with Tyk Gateway** + +Tyk Gateway has full domain support built-in, you can: + +* Set Tyk to listen only on a specific domain for all API traffic. +* Set an API to listen on a specific domain (e.g. api1.com, api2.com). +* Split APIs over a domain using a path (e.g. api.com/api1, api.com/api2, moreapis.com/api1, moreapis.com/api2 etc). +* If you have set a hostname for the Gateway, then all non-domain-bound APIs will be on this hostname + the `listen_path`. + +[1]: https://packagecloud.io/tyk \ No newline at end of file diff --git a/tyk-self-managed/install/methods.mdx b/tyk-self-managed/install/methods.mdx new file mode 100644 index 0000000000..66e56e45f3 --- /dev/null +++ b/tyk-self-managed/install/methods.mdx @@ -0,0 +1,638 @@ +--- +title: "Install Tyk Self-Managed on Ansible, AWS, GCP, Heroku, and more" +description: "Explore the various installation options for Tyk Self-Managed, including AWS Marketplace, Ansible, Heroku, Google Cloud, Azure, and more." +sidebarTitle: "Alternative Methods" +--- + +| Edition | Deployment Type | +| :------------- | :---------------------- | +| Enterprise | Self-Managed, Hybrid | + +## Install on AWS Marketplace + +Tyk offers a flexible and powerful API management solution through **Tyk Cloud** on the [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-pboluroscnqro). Tyk Cloud is an end-to-end managed API platform where both the control plane and gateways are installed on AWS for a seamless, fully cloud-hosted experience. + +For those who need more deployment flexibility, Tyk Cloud also supports a [Hybrid Gateway](/tyk-cloud/environments-deployments/hybrid-gateways) option. In this setup, the control plane remains hosted and managed by Tyk on AWS, while the gateways can be deployed on your preferred cloud provider or on-premises environment—allowing you to meet data locality and compliance needs without sacrificing control. + +### Available AWS Deployment Regions + +You can deploy Tyk Cloud in the following AWS regions: + +- **Singapore**: `aws-ap-southeast-1` +- **Frankfurt, Germany**: `aws-eu-central-1` +- **London, UK**: `aws-eu-west-2` +- **N. Virginia, USA**: `aws-us-east-1` +- **Oregon, USA**: `aws-us-west-2` +- **Australia**: `aws-ap-southeast-2` + +Getting started with Tyk Cloud via the AWS Marketplace is quick and easy. Sign up today to access Tyk’s comprehensive API management tools designed to scale with your needs. + +### Install Tyk on AWS EC2 + + +1. Spin up an [EC2 instance](https://aws.amazon.com/ec2/instance-types/), AWS Linux2 preferably, T2.Medium is fine + - add a public IP + - open up SG access to: + - 3000 for the Tyk Dashboard + - 8080 for the Tyk Gateway + - 22 TCP for SSH + +2. SSH into the instance + + `ssh -i mykey.pem ec2-user@public-ec2-ip` + +3. Install Git, Docker, & Docker Compose + + Feel free to copy paste these + ```.sh + sudo yum update -y + sudo yum install git -y + sudo yum install -y docker + sudo service docker start + sudo usermod -aG docker ec2-user + sudo su + sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose + docker ps + ``` + +4. Now follow our Docker installation [guide](/tyk-self-managed/install/docker) + +5. Visit + + ``` + http://:3000 + ``` + and fill out the Bootstrap form! + **If you see any page besides the Bootstrap page, you have pasted the license key incorrectly** + +#### Enable SSL for the Gateway & Dashboard + +1. Add the following to `confs/tyk.env` + +```env +TYK_GW_POLICIES_POLICYCONNECTIONSTRING=https://tyk-dashboard:3000 +TYK_GW_DBAPPCONFOPTIONS_CONNECTIONSTRING=https://tyk-dashboard:3000 +TYK_GW_HTTPSERVEROPTIONS_USESSL=true +TYK_GW_HTTPSERVEROPTIONS_CERTIFICATES=[{"domain_name":"*.yoursite.com","cert_file":"/opt/tyk-gateway/certs/new.cert.cert","key_file":"/opt/tyk-gateway/certs/new.cert.key"}] +TYK_GW_HTTPSERVEROPTIONS_SSLINSECURESKIPVERIFY=true +``` + +2. Add the following to `confs/tyk_analytics.env` + +```env +TYK_DB_TYKAPI_HOST=https://tyk-gateway +TYK_DB_HTTPSERVEROPTIONS_USESSL=true +TYK_DB_HTTPSERVEROPTIONS_CERTIFICATES=[{"domain_name":"*.yoursite.com","cert_file":"/opt/tyk-dashboard/certs/new.cert.cert","key_file":"/opt/tyk-dashboard/certs/new.cert.key"}] +``` + +3. Generate self-signed Certs: (Or bring your own CA signed) + +``` +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes +``` + +4. Mount your certs to containers through `docker-compose.yml` + +```.yaml + tyk-dashboard: + ... + volumes: + - ./cert.pem:/opt/tyk-dashboard/certs/new.cert.cert + - ./key.pem:/opt/tyk-dashboard/certs/new.cert.key + tyk-gateway: + ... + volumes: + - ./cert.pem:/opt/tyk-gateway/certs/new.cert.cert + - ./key.pem:/opt/tyk-gateway/certs/new.cert.key +``` + +5. Restart your containers with the mounted files + +``` +docker compose up -d tyk-dashboard tyk-gateway +``` + +6. Download the bootstrap script onto EC2 machine + +``` +wget https://raw.githubusercontent.com/sedkis/tyk/master/scripts/bootstrap-ssl.sh +``` + +7. Apply execute permissions to file: + +```chmod +x bootstrap.sh``` + +8. Run the bootstrap script + +```./bootstrap.sh localhost``` + +9. Done! use the generated user and password to log into The Tyk Dashboard + + +## Install with Ansible + + +**Requirements** + +[Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) is required to run the following commands. + + +For a list of compatible operating systems and versions, please refer to the [Linux documentation](/tyk-self-managed/install/linux#compatible-operating-systems). + +### Instructions + +1. clone the [tyk-ansible](https://github.com/TykTechnologies/tyk-ansible) repositry + + ```bash + $ git clone https://github.com/TykTechnologies/tyk-ansible + ``` + +2. `cd` into the directory + + ```.bash + $ cd tyk-ansible + ``` + +3. Run initialisation script to initialise environment + + ```bash + $ sh scripts/init.sh + ``` + +4. Modify `hosts.yml` file to update ssh variables to your server(s). You can learn more about the hosts file [here](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html) + +5. Run ansible-playbook to install the following: + + - Redis + - MongoDB or PostgreSQL + - Tyk Dashboard + - Tyk Gateway + - Tyk Pump + + ```bash + $ ansible-playbook playbook.yaml -t tyk-pro -t redis -t `mongodb` or `pgsql` + ``` + + You can choose to not install Redis, MongoDB or PostgreSQL by removing the `-t redis` or `-t mongodb` or `-t pgsql` However Redis and MongoDB or PostgreSQL are a requirement and need to be installed for the Tyk Pro installation to run. + + + + + For a production environment, we recommend that the Gateway, Dashboard and Pump are installed on separate machines. If installing multiple Gateways, you should install each on a separate machine. See [Planning for Production](/planning-for-production) For more details. + + +For a list available ansible variables and their descriptions, please refer to the [ansible documentation](https://github.com/TykTechnologies/tyk-ansible/tree/main/vars) + +## Install on Heroku + +A full Tyk Self-Managed installation can be deployed to Heroku dynos and workers using [Heroku Container Registry and Runtime](https://devcenter.heroku.com/articles/) functionality. This guide will utilize [Tyk Docker images](https://hub.docker.com/u/tykio/) with a small amount of customization as well as an external MongoDB service. + +### Prerequisites + +1. Docker daemon installed and running locally +2. [Heroku account](https://www.heroku.com/), the free plan is sufficient for a basic PoC but not recommended for production usage +3. [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) installed +4. MongoDB service (such as [Atlas](https://www.mongodb.com/cloud/atlas), [mLab](https://elements.heroku.com/addons/mongolab), or your own deployment), this guide is based on MongoDB Atlas but others should work as well +5. [Tyk License](https://tyk.io/pricing/on-premise/) (note that in case of running multiple gateway dynos, license type must match) +6. Checkout the [Tyk quickstart repository](https://github.com/TykTechnologies/tyk-pro-heroku) from GitHub +7. Python 2 or 3 in order to execute the bootstrap script + +### Creating Heroku Apps + +We will create two Heroku apps, one for the Tyk Gateway (with [Redis add-on](https://devcenter.heroku.com/articles/heroku-redis) attached to it) and another for the Dashboard and Pump. + +Given Heroku CLI is installed and your Heroku account is available, log into it: +```bash +heroku login +``` + +Now create the Gateway app and note down its name: +```bash +heroku create +``` +``` +Creating app... done, ⬢ infinite-plains-14949 +https://infinite-plains-14949.herokuapp.com/ | https://git.heroku.com/infinite-plains-14949.git +``` + + +`--space` flag must be added to the command if the app is being created in a private space, see more details in the section on Heroku private spaces (below). + + + +Provision a Redis add-on (we'll use a `hobby-dev` plan for demonstration purposes but that's not suitable for production), replacing the app name with your own: +```bash +heroku addons:create heroku-redis:hobby-dev -a infinite-plains-14949 +``` +``` +Creating heroku-redis:hobby-dev on ⬢ infinite-plains-14949... free +Your add-on should be available in a few minutes. +! WARNING: Data stored in hobby plans on Heroku Redis are not persisted. +redis-infinite-35445 is being created in the background. The app will restart when complete... +Use heroku addons:info redis-infinite-35445 to check creation progress +Use heroku addons:docs heroku-redis to view documentation +``` + +Once add-on provisioning is done, the info command (replacing the add-on name with your own) will show the following output: +```bash +heroku addons:info redis-infinite-35445 +``` +``` +=== redis-infinite-35445 +Attachments: infinite-plains-14949::REDIS +Installed at: Sun May 18 2018 14:23:21 GMT+0300 (EEST) +Owning app: infinite-plains-14949 +Plan: heroku-redis:hobby-dev +Price: free +State: created +``` + +Time to create the Dashboard app and note down its name as well: +```bash +heroku create +``` +``` +Creating app... done, ⬢ evening-beach-40625 +https://evening-beach-40625.herokuapp.com/ | https://git.heroku.com/evening-beach-40625.git +``` + +Since the Dashboard and Pump need access to the same Redis instance as the gateway, we'll need to share the Gateway app's add-on with this new app: +```bash +heroku addons:attach infinite-plains-14949::REDIS -a evening-beach-40625 +``` +``` +Attaching redis-infinite-35445 to ⬢ evening-beach-40625... done +Setting REDIS config vars and restarting ⬢ evening-beach-40625... done, v3 +``` + +To check that both apps have access to the same Redis add-on, we can utilize the `heroku config` command and check for the Redis endpoint: +```bash +heroku config -a infinite-plains-14949 | grep REDIS_URL +heroku config -a evening-beach-40625 | grep REDIS_URL +``` + +Their outputs should match. + +### Deploy the Dashboard + +It's recommended to start with the Dashboard so in your Heroku quickstart clone run: +```bash +cd analytics +ls dashboard +``` +``` +bootstrap.sh Dockerfile.web entrypoint.sh tyk_analytics.conf +``` + +You will find it contains a `Dockerfile.web` for the web dyno, a config file for the Dashboard, entrypoint script for the Docker container and a bootstrap script for seeding the dashboard instance with sample data. All these files are editable for your purposes but have sane defaults for a PoC. + + + +You can use the `FROM` statement in `Dockerfile.web` to use specific dashboard version and upgrade when needed instead of relying on the `latest` tag. + + +The [Dashboard configuration](/tyk-dashboard/configuration) can be changed by either editing the `tyk_analytics.conf` file or injecting them as [environment variables](/tyk-oss-gateway/configuration) via `heroku config`. In this guide we'll use the latter for simplicity of demonstration but there is merit to both methods. + +First let's set the license key: +```bash +heroku config:set TYK_DB_LICENSEKEY="your license key here" -a evening-beach-40625 +``` +``` +Setting TYK_DB_LICENSEKEY and restarting ⬢ evening-beach-40625... done, v4 +TYK_DB_LICENSEKEY: should show your license key here +``` + +Now the MongoDB endpoint (replacing with your actual endpoint): +```bash +heroku config:set TYK_DB_MONGOURL="mongodb://user:pass@mongoprimary.net:27017,mongosecondary.net:27017,mongotertiary.net:27017" -a evening-beach-40625 +``` +``` +Setting TYK_DB_MONGOURL and restarting ⬢ evening-beach-40625... done, v5 +TYK_DB_MONGOURL: mongodb://user:pass@mongoprimary.net:27017,mongosecondary.net:27017,mongotertiary.net:27017 +``` + +And enable SSL for it if your service supports/requires this: +```bash +heroku config:set TYK_DB_MONGOUSESSL="true" -a evening-beach-40625 +``` +``` +Setting TYK_DB_MONGOUSESSL and restarting ⬢ evening-beach-40625... done, v6 +TYK_DB_MONGOUSESSL: true +``` + +Since the Tyk Dashboard needs to access gateways sometimes, we'll need to specify the Gateway endpoint too, which is the Gateway app's URL: +```bash +heroku config:set TYK_DB_TYKAPI_HOST="https://infinite-plains-14949.herokuapp.com" -a evening-beach-40625 +heroku config:set TYK_DB_TYKAPI_PORT="443" -a evening-beach-40625 +``` +``` +Setting TYK_DB_TYKAPI_HOST and restarting ⬢ evening-beach-40625... done, v7 +TYK_DB_TYKAPI_HOST: https://infinite-plains-14949.herokuapp.com +Setting TYK_DB_TYKAPI_PORT and restarting ⬢ evening-beach-40625... done, v8 +TYK_DB_TYKAPI_PORT: 443 +``` + +This is enough for a basic Dashboard setup but we recommend also changing at least node and admin secrets with strong random values, as well as exploring other config options. + +Since the Tyk Pump is also a part of this application (as a worker process), we'll need to configure it too. + +```bash +ls pump +``` +``` +Dockerfile.pump entrypoint.sh pump.conf +``` + +Same principles apply here as well. Here we'll need to configure MongoDB endpoints for all the Pumps (this can also be done in the `pump.conf` file): +```bash +heroku config:set PMP_MONGO_MONGOURL="mongodb://user:pass@mongoprimary.net:27017,mongosecondary.net:27017,mongotertiary.net:27017" -a evening-beach-40625 +heroku config:set PMP_MONGO_MONGOUSESSL="true" + +heroku config:set PMP_MONGOAGG_MONGOURL="mongodb://user:pass@mongoprimary.net:27017,mongosecondary.net:27017,mongotertiary.net:27017" -a evening-beach-40625 +heroku config:set PMP_MONGOAGG_MONGOUSESSL="true" +``` + +With the configuration in place it's finally time to deploy our app to Heroku. + +First, make sure CLI is logged in to Heroku containers registry: +```bash +heroku container:login +``` +``` +Login Succeeded +``` + +Provided you're currently in `analytics` directory of the quickstart repo: +```bash +heroku container:push --recursive -a evening-beach-40625 +``` +``` +=== Building web (/tyk-heroku-docker/analytics/dashboard/Dockerfile.web) +Sending build context to Docker daemon 8.192kB +Step 1/5 : FROM tykio/tyk-dashboard:v1.6.1 + ---> fdbc67b43139 +Step 2/5 : COPY tyk_analytics.conf /opt/tyk-dashboard/tyk_analytics.conf + ---> 89be9913798b +Step 3/5 : COPY entrypoint.sh /opt/tyk-dashboard/entrypoint.sh + ---> c256152bff29 +Step 4/5 : ENTRYPOINT ["/bin/sh", "-c"] + ---> Running in bc9fe7a569c0 +Removing intermediate container bc9fe7a569c0 + ---> f40e6b259230 +Step 5/5 : CMD ["/opt/tyk-dashboard/entrypoint.sh"] + ---> Running in 705273810eea +Removing intermediate container 705273810eea + ---> abe9f10e8b21 +Successfully built abe9f10e8b21 +Successfully tagged registry.heroku.com/evening-beach-40625/web:latest +=== Building pump (/tyk-heroku-docker/analytics/pump/Dockerfile.pump) +Sending build context to Docker daemon 5.12kB +Step 1/5 : FROM tykio/tyk-pump-docker-pub:v0.5.2 + ---> 247c6b5795a9 +Step 2/5 : COPY pump.conf /opt/tyk-pump/pump.conf + ---> 1befeab8f092 +Step 3/5 : COPY entrypoint.sh /opt/tyk-pump/entrypoint.sh + ---> f8ad0681aa70 +Step 4/5 : ENTRYPOINT ["/bin/sh", "-c"] + ---> Running in 0c30d35b9e2b +Removing intermediate container 0c30d35b9e2b + ---> b17bd6a8ed44 +Step 5/5 : CMD ["/opt/tyk-pump/entrypoint.sh"] + ---> Running in a16acb453b62 +Removing intermediate container a16acb453b62 + ---> 47ac9f221d8d +Successfully built 47ac9f221d8d +Successfully tagged registry.heroku.com/evening-beach-40625/pump:latest +=== Pushing web (/tyk-heroku-docker/analytics/dashboard/Dockerfile.web) +The push refers to repository [registry.heroku.com/evening-beach-40625/web] +c60cf00e6e9b: Pushed +11d074829795: Pushed +8b72aa2b2acc: Pushed +ca2feecf234c: Pushed +803aafd71223: Pushed +43efe85a991c: Pushed +latest: digest: sha256:b857afaa69154597558afb2462896275ab667b729072fac224487f140427fa73 size: 1574 +=== Pushing pump (/tyk-heroku-docker/analytics/pump/Dockerfile.pump) +The push refers to repository [registry.heroku.com/evening-beach-40625/pump] +eeddc94b8282: Pushed +37f3b3ce56ab: Pushed +4b61531ec7dc: Pushed +eca9efd615d9: Pushed +0f700064c5a1: Pushed +43efe85a991c: Mounted from evening-beach-40625/web +latest: digest: sha256:f45acaefa3b47a126dd784a888c89e420814ad3031d3d4d4885e340a59aec31c size: 1573 +``` + +This has built Docker images for both dashboard and pump, as well as pushed them to Heroku registry and automatically deployed to the application. + +Provided everything went well (and if not, inspect the application logs), you should be seeing the Dashboard login page at your app URL (e.g "https://evening-beach-40625.herokuapp.com/"). + +However, it doesn't yet have any accounts. It order to populate it please run the `dashboard/bootstrap.sh` script: +```bash +dashboard/bootstrap.sh evening-beach-40625.herokuapp.com +``` +``` +Creating Organization +ORGID: 5b016ca530867500050b9e90 +Adding new user +USER AUTH: a0f7c1e878634a60599dc037489a880f +NEW ID: 5b016ca6dcd0056d702dc40e +Setting password + +DONE +==== +Login at https://evening-beach-40625.herokuapp.com/ +User: c7ze82m8k3@default.com +Pass: test123 +``` + +It will generate a default organization with random admin username and a specified password. The bootstrap script can be edited to suit your needs as well as just editing the user info in the dashboard. + +If this was successful, you should be able to log into your dashboard now. + +The last step in this app is to start the Pump worker dyno since by default only the web dyno is enabled: +```bash +heroku dyno:scale pump=1 -a evening-beach-40625 +``` +``` +Scaling dynos... done, now running pump at 1:Free +``` + +At that point the dyno formation should look like this: +```bash +heroku dyno:scale -a evening-beach-40625 +``` +``` +pump=1:Free web=1:Free +``` + +### Deploy the Gateway + +The process is very similar for the Tyk Gateway, except it doesn't have a worker process and doesn't need access to MongoDB. + +```bash +cd ../gateway +ls +``` +``` +Dockerfile.web entrypoint.sh tyk.conf +``` + +All these files serve the same purpose as with the Dasboard and the Pump. [Configuration](/tyk-oss-gateway/configuration) can either be edited in `tyk.conf` or [injected](/tyk-oss-gateway/configuration) with `heroku config`. + +To get things going we'll need to set following options for the Dashboard endpoint (substituting the actual endpoint and the app name, now for the gateway app): +```bash +heroku config:set TYK_GW_DBAPPCONFOPTIONS_CONNECTIONSTRING="https://evening-beach-40625.herokuapp.com" -a infinite-plains-14949 +heroku config:set TYK_GW_POLICIES_POLICYCONNECTIONSTRING="https://evening-beach-40625.herokuapp.com" -a infinite-plains-14949 +``` +``` +Setting TYK_GW_DBAPPCONFOPTIONS_CONNECTIONSTRING and restarting ⬢ infinite-plains-14949... done, v4 +TYK_GW_DBAPPCONFOPTIONS_CONNECTIONSTRING: https://evening-beach-40625.herokuapp.com +Setting TYK_GW_POLICIES_POLICYCONNECTIONSTRING and restarting ⬢ infinite-plains-14949... done, v5 +TYK_GW_POLICIES_POLICYCONNECTIONSTRING: https://evening-beach-40625.herokuapp.com +``` + +Since the Redis configuration will be automatically discovered (it's already injected by Heroku), we're ready to deploy: +```bash +heroku container:push --recursive -a infinite-plains-14949 +``` +``` +=== Building web (/tyk-heroku-docker/gateway/Dockerfile.web) +Sending build context to Docker daemon 6.144kB +Step 1/5 : FROM tykio/tyk-gateway:v2.6.1 + ---> f1201002e0b7 +Step 2/5 : COPY tyk.conf /opt/tyk-gateway/tyk.conf + ---> b118611dc36b +Step 3/5 : COPY entrypoint.sh /opt/tyk-gateway/entrypoint.sh + ---> 68ad364030cd +Step 4/5 : ENTRYPOINT ["/bin/sh", "-c"] + ---> Running in 859f4c15a0d2 +Removing intermediate container 859f4c15a0d2 + ---> 5f8c0d1b378a +Step 5/5 : CMD ["/opt/tyk-gateway/entrypoint.sh"] + ---> Running in 44c5e4c87708 +Removing intermediate container 44c5e4c87708 + ---> 86a9eb509968 +Successfully built 86a9eb509968 +Successfully tagged registry.heroku.com/infinite-plains-14949/web:latest +=== Pushing web (/tyk-heroku-docker/gateway/Dockerfile.web) +The push refers to repository [registry.heroku.com/infinite-plains-14949/web] +b8a4c3e3f93c: Pushed +0b7bae5497cd: Pushed +e8964f363bf4: Pushed +379aae48d347: Pushed +ab2b28b92877: Pushed +021ee50b0983: Pushed +43efe85a991c: Mounted from evening-beach-40625/pump +latest: digest: sha256:d67b8f55d729bb56e06fe38e17c2016a36f2edcd4f01760c0e62a13bb3c9ed38 size: 1781 +``` + +Inspect the logs (`heroku logs -a infinite-plains-14949`) to check that deployment was successful, also the node should be registered by the Dashboard in "System Management" -> "Nodes and Licenses" section. + +You're ready to follow the guide on [creating and managing your APIs](/api-management/gateway-config-managing-classic#create-an-api) with this Heroku deployment. + + +To use the [geographic log distribution](/api-management/dashboard-analytics#activity-by-location) feature in the Dashboard please supply the GeoLite2 DB in the `gateway` directory, uncomment the marked line in `Dockerfile.web` and set the `analytics_config.enable_geo_ip` setting (or `TYK_GW_ANALYTICSCONFIG_ENABLEGEOIP` env var) to `true`. + + +### Heroku Private Spaces + +Most instructions are valid for [Heroku Private Spaces runtime](https://devcenter.heroku.com/articles/private-spaces). However there are several differences to keep in mind. + +Heroku app creation commands must include the private space name in the `--space` flag, e.g.: +```bash +heroku create --space test-space-virginia +``` + +When deploying to the app, the container must be released manually after pushing the image to the app: +```bash +heroku container:push --recursive -a analytics-app-name +heroku container:release web -a analytics-app-name +heroku container:release pump -a analytics-app-name +``` + +Similarly, the Gateway: +```bash +heroku container:push --recursive -a gateway-app-name +heroku container:release web -a gateway-app-name +``` + +Please allow several minutes for the first deployment to start as additional infrastructure is being created for it. Next deployments are faster. + +Private spaces maintain stable set of IPs that can be used for allowing fixed set of IPs on your upstream side (e.g. on an external database service). Find them using the following command: +```bash +heroku spaces:info --space test-space-virginia +``` + +Alternatively VPC peering can be used with the private spaces if external service supports it. This way exposure to external network can be avoided. For instance, see [MongoDB Atlas guide](https://www.mongodb.com/blog/post/integrating-mongodb-atlas-with-heroku-private-spaces) for setting this up. + +The minimal Heroku Redis add-on plan that installs into your private space is currently `private-7`. Please refer to [Heroku's Redis with private spaces guide](https://devcenter.heroku.com/articles/heroku-redis-and-private-spaces) for more information. + +Apps in private spaces don't enable SSL/TLS by default. It needs to be configured in the app settings along with the domain name for it. If it's not enabled, please make sure that configs that refer to corresponding hosts are using HTTP instead of HTTPS and related ports (80 for HTTP). + +### Gateway Plugins + +In order to enable [rich plugins](/api-management/plugins/rich-plugins#) for the Gateway, please set the following Heroku config option to either `python` or `lua` depending on the type of plugins used: +```bash +heroku config:set TYK_PLUGINS="python" -a infinite-plains-14949 +``` +``` +Setting TYK_PLUGINS and restarting ⬢ infinite-plains-14949... done, v9 +TYK_PLUGINS: python +``` + +After re-starting the Gateway, the logs should be showing something similar to this: +``` +2018-05-18T13:13:50.272511+00:00 app[web.1]: Tyk will be using python plugins +2018-05-18T13:13:50.311510+00:00 app[web.1]: time="May 18 13:13:50" level=info msg="Setting PYTHONPATH to 'coprocess/python:middleware/python:event_handlers:coprocess/python/proto'" +2018-05-18T13:13:50.311544+00:00 app[web.1]: time="May 18 13:13:50" level=info msg="Initializing interpreter, Py_Initialize()" +2018-05-18T13:13:50.497815+00:00 app[web.1]: time="May 18 13:13:50" level=info msg="Initializing dispatcher" +``` + +Set this variable back to an empty value in order to revert back to the default behavior. + +### Upgrading or Customizing Tyk + +Since this deployment is based on Docker images and containers, upgrading or making changes to the deployment is as easy as building a new image and pushing it to the registry. + +Specifically, upgrading version of any Tyk components is done by editing the corresponding `Dockerfile` and replacing the base image version tag. E.g. changing `FROM tykio/tyk-gateway:v2.5.4` to `FROM tykio/tyk-gateway:v2.6.1` will pull the Tyk gateway 2.6.1. We highly recommend specifying concrete version tags instead of `latest` for better house keeping. + +Once these changes have been made just run `heroku container:push --recursive -a app_name` on the corresponding directory as shown previously in this guide. This will do all the building and pushing as well as gracefully deploying on your Heroku app. + +Please refer to [Heroku documentation on containers and registry](https://devcenter.heroku.com/articles/container-registry-and-runtime) for more information. + +## Install on Microsoft Azure + +Azure allows you to install Tyk in the following ways: + +**On-Premises** + +1. Via our [Ubuntu Setup](/tyk-self-managed/install/linux#install-tyk-on-debian-or-ubuntu) on an installed Ubuntu Server on Azure. +2. Via our [Docker Installation](/tyk-self-managed/install/docker) using Azure's Docker support. + +See our video for installing Tyk on Ubuntu via Azure: + + + +We also have a [blog post](https://tyk.io/blog/getting-started-with-tyk-on-microsoft-azure-and-ubuntu/) that walks you through installing Tyk on Azure. + + +## Install to Google Cloud + +Google Cloud allows you to install Tyk in the following ways: + +**On-Premises** + +1. Via our [Ubuntu Setup](/tyk-self-managed/install/linux#install-tyk-on-debian-or-ubuntu) on an installed Ubuntu Server within Google Cloud. +2. Via our [Docker Installation](/tyk-self-managed/install/docker) using Google Cloud's Docker support. + +**Tyk Pump on GCP** + +When running Tyk Pump in GCP using [Cloud Run](https://cloud.google.com/run/docs/overview/what-is-cloud-run) it is available 24/7. However, since it is serverless you also need to ensure that the _CPU always allocated_ option is configured to ensure availability of the analytics. Otherwise, for each request there will be a lag between the Tyk Pump container starting up and having the CPU allocated. Subsequently, the analytics would only be available during this time. + +1. Configure Cloud Run to have the [CPU always allocated](https://cloud.google.com/run/docs/configuring/cpu-allocation#setting) option enabled. Otherwise, the Tyk Pump container needs to warm up, which takes approximately 1 min. Subsequently, by this time the stats are removed from Redis. + +2. Update the Tyk Gateway [configuration](/tyk-oss-gateway/configuration#analytics_config-storage_expiration_time) to keep the stats for 3 mins to allow Tyk Pump to process them. This value should be greater than the Pump [purge delay](/tyk-pump/tyk-pump-configuration/tyk-pump-environment-variables#purge_delay) to ensure the analytics data exists long enough in Redis to be processed by the Pump. + diff --git a/tyk-stack.mdx b/tyk-stack.mdx new file mode 100644 index 0000000000..b69cc862b3 --- /dev/null +++ b/tyk-stack.mdx @@ -0,0 +1,25 @@ +--- +title: "Tyk Stack" +description: "Overview of Tyk Stack components, both open-source and closed-source." +order: 7 +sidebarTitle: "Tyk Stack" +--- + +import OssProductListInclude from '/snippets/oss-product-list-include.mdx'; + +## Tyk Open Source + + + +## Closed Source + +The following Tyk components, created and maintained by the Tyk Team, are proprietary and closed-source: + +* [Tyk Dashboard](/api-management/dashboard-configuration) +* [Tyk Developer Portal](/portal/overview/intro) +* [Tyk Multi Data Center Bridge](/api-management/mdcb#managing-geographically-distributed-gateways-to-minimize-latency-and-protect-data-sovereignty) +* [Universal Data Graph](/api-management/data-graph#overview) +* [Tyk Operator](/api-management/automations/operator#what-is-tyk-operator) +* [Tyk Sync](/api-management/automations/sync) + +If you plan to deploy and use the above components On-premise, license keys are required. diff --git a/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/approve-requests.mdx b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/approve-requests.mdx new file mode 100644 index 0000000000..67a38553bc --- /dev/null +++ b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/approve-requests.mdx @@ -0,0 +1,145 @@ +--- +title: "Managing API Access Requests" +description: "How to provision API Access Requests in Tyk Developer Portal" +keywords: "Developer Portal, Tyk, Dynamic Client Registration" +sidebarTitle: "Manage Access Requests" +--- + +## Introduction + +API Access Requests are formal requests from API Consumers to access specific API Products and Plans through the Developer Portal. These requests initiate the workflow for granting, or provisioning, API access to users. + +### Understanding the Provisioning Request Workflow + +When API Consumers discover APIs in your Catalog that they need access to, they initiate a API Access Request through the Live Portal. This request: + +- Identifies the specific API Product and subscriptionPlan they want to access +- Specifies which Developer App should receive the access credentials +- Creates an auditable record of the access request + +Depending on your configuration, these requests can be processed [automatically]() or require [manual approval](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/approve-requests#manual-approval-workflow). + + + + +## Requesting Access to an API Product + +API Consumers can request access to API Products through the Live Portal using one of two configured flows. +To learn how to configure this flow visit [Access Flow Types](https://tyk.io/docs/portal/overview/concepts#Access-Flow-Types) + +### Initial Steps (Both Flows): +1. From the **Catalogues** page, choose the API Product of interest and select **More info** +2. On the API Product detail page, decide which of the available Plans to subscribe to and select **Access with this Plan** +3. **The next steps depend on your portal's configured access flow**: + +#### Direct Access Flow +When Direct Access Flow is enabled, you can request access to API Products immediately without using a shopping cart. + +**Steps:** +- You'll be taken directly to the access request page with your selected product and plan pre-populated +- Select or create a Developer App to store your credentials +- Configure credentials (new or extend existing compatible credentials) +- Select **Continue** to submit your request + +**Credential Compatibility Rules** + +For existing credentials to be compatible, they must: +- Have the same Plan as the selected product +- Not already include the selected product +- Have a matching authentication type with the product + +For more information, refer to this [guide](/tyk-developer-portal/direct-access-flow) + +#### Cart-Based Flow +When Cart-Based Flow is enabled, you can add multiple API Products to a shopping cart before submitting a single access request. + +**Steps:** +- The API Product and Plan combination will be added to your cart +- Repeat steps 1-2 for additional API Products you want to access +- Go to the **Cart** using the icon in the top right of the screen +- Review your selections and select a Developer App +- Select **Submit request** + +### After Submission +- Your access request will be submitted for approval (if required) +- Track request status in **My Apps** +- Once approved, credentials will be available in your selected Developer App + +## Manual Approval Workflow + +The manual approval workflow provides API Owners with oversight of all API access. When an API Consumer completes an [access request](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/approve-requests#requesting-access-to-an-api-product), API Owners receive notification of pending request via [email](/product-stack/tyk-enterprise-developer-portal/getting-started/setup-email-notifications) and should then: + +1. Navigate to the **API Consumers > Access Requests** page in the Admin Portal +2. Review the request + - User name + - Developer App + - Requested API Products + - Selected subscription Plan +3. Approve or reject the request from the three dot menu + - If approved, access is provisioned with credentials issued to the specified Developer App + - If rejected, access will not be granted + - API Consumer receives notification of the decision via email + + +## Automatic Approval Workflow + +For trusted users or specific API Products, you can enable automatic approval in the [subscription Plan](/portal/api-plans#auto-approve-provisioning-requests). + +To configure automatic approval, the API Owner should: + +1. Navigate to the **Plans** page in the Admin Portal +2. Select or create the API Plan that should be automatically approved +3. Set the **Auto approve access request** checkbox + Auto Approve API provisioning requests +4. Select **Save changes** + +When an API Consumer requests access using this plan, the request will be approved immediately and access credentials provisioned to the Developer App. + + + +Despite automatic approval, a record of the request is maintained in the **API Consumers > Access requests** page in the Admin Portal. + + + + +## Notification of Decision + +The Dev Portal sends notification to the API Consumer when their request is approved or rejected. + +If the [email service](/product-stack/tyk-enterprise-developer-portal/getting-started/setup-email-notifications) is configured, then: + +- When a request is approved: + - The system sends an approval notification email to the user + - The email uses the template "approve" with a configurable subject + - The notification includes details about the approved access +- When a request is rejected: + - The system sends a rejection notification email to the user + - The email uses the template "reject" with a configurable subject + +## Update Products and Plans of an Access Request + +Starting from v1.16.0, the Tyk Developer Portal enables users to efficiently manage their access requests by adding or removing API Products and modifying subscription plans for existing credentials. This eliminates credential sprawl and provides a streamlined experience. + +The following features can be accessed via "App" page, under each access credential. + +#### Adding Products to Existing Access Request +Users can extend their existing credentials with additional API Products when compatibility requirements are met, avoiding the need to creating new credentials for additional API products. + +**Compatibility Requirements** +Products can be added to existing credentials when they share: +- Same authentication method +- Same subscription plan +- Products not already included in the credential + +When requesting access to an API Product, developers can choose to create a new credential or use one of the compatible existing credential. + +For more information, refer to this [guide](/tyk-developer-portal/single-credentials-multiple-api-products) +#### Removing Products from Access Requests +Users can remove individual API Products from credentials that contain multiple products, maintaining access to remaining products while revoking access to unwanted ones. This provides granular control over application permissions without affecting the entire credential. + +#### Plan Management +Users can update subscription plans for existing access requests without generating new credentials. Plan modifications maintain existing product associations where compatibility allows. + +Currently, Plan management is available to OAuth 2.0 access requests only. + + diff --git a/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/configuring-custom-rate-limit-keys.mdx b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/configuring-custom-rate-limit-keys.mdx new file mode 100644 index 0000000000..6f0e4a9e29 --- /dev/null +++ b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/configuring-custom-rate-limit-keys.mdx @@ -0,0 +1,55 @@ +--- +title: "Configuring Custom Rate Limit Keys in Developer Portal" +description: "How to configure custom rate limit keys in Tyk Developer Portal" +keywords: "Developer Portal, Tyk, Rate Limit" +sidebarTitle: "Advanced Rate Limits" +--- + +## Introduction + +The Tyk Enterprise Developer Portal supports custom rate limiting patterns that allow you to apply rate limits based on entities other than just credentials, such as per application, per developer, or per organization. This is particularly useful for B2B scenarios where API quotas need to be shared across multiple developers and applications within an organization. + +For detailed information about custom rate limiting concepts and configuration, see the [Custom Rate Limiting](/api-management/rate-limit#custom-rate-limiting) section in the main Rate Limiting documentation. + +**Prerequisites** + +This capability works with [Tyk 5.3.0](/developer-support/release-notes/dashboard#5-3-0-release-notes) or higher. + +## Configuring Custom Rate Limit Keys in the Portal + + + +If you are using Tyk Developer Portal version 1.13.0 or later, you can configure the custom rate limit keys directly from the Developer Portal in the Advanced settings (optional) collapsible section of the Plan's view (by Credentials metadata). +Add Plan Advanced Settings + + + +For general configuration of custom rate limit keys in policies, refer to the [Custom Rate Limiting](/api-management/rate-limit#custom-rate-limiting) documentation. + +## Using Custom Rate Limit Keys with the Portal + +The Tyk Enterprise Developer Portal facilitates the configuration of various rate limiting options based on a business model for API Products published in the portal. + +To achieve this, the portal, by default, populates the following attributes in the credential metadata, which can be used as part of a custom rate limit key: +- **ApplicationID**: The ID of the application to which the credential belongs. +- **DeveloperID**: The ID of the developer who created the credential. +- **OrganisationID**: The ID of the organization to which the developer belongs. + +Additionally, it's possible to attach [custom attribute values](/portal/customization/user-model#add-custom-attributes-to-the-user-model) defined in a developer profile as metadata fields to credentials. + +When a credential is provisioned by the portal, all the fields described above are added as metadata values to the credential, making them valid options for configuring the rate limit key: + +Credential's metadata + +This approach allows the portal to seamlessly apply rate limits based on any combination of the aforementioned fields and other custom metadata objects defined in policies used for plans or products. This is in addition to credentials. + +--- + + + +**Tyk Enterprise Developer Portal** + +If you are interested in getting access contact us at [support@tyk.io]() + + + diff --git a/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration.mdx b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration.mdx new file mode 100644 index 0000000000..56105081c3 --- /dev/null +++ b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/api-access/dynamic-client-registration.mdx @@ -0,0 +1,370 @@ +--- +title: "Dynamic Client Registration" +description: "Learn how to use Dynamic Client Registration to grant access to API Products" +keywords: "Developer Portal, Tyk, Dynamic Client Registration" +sidebarTitle: "OAuth 2.0 Dynamic Client Registration" +--- + +## Introduction + +Dynamic Client Registration (DCR) is an [IETF protocol](https://datatracker.ietf.org/doc/html/rfc7591) that automates the registration of OAuth 2.0 clients with an authorization server. The Tyk Developer Portal uses DCR so that when an API Consumer is approved for access to an API Product, they receive OAuth 2.0 credentials automatically. The API Consumer has no direct interaction with the IdP during this process; the Portal handles client registration on their behalf. + +DCR requires an Identity Provider (IdP) that acts as an OAuth 2.0 authorization server, supports the DCR protocol, and exposes an OIDC well-known configuration endpoint. [OpenID Connect (OIDC)](https://openid.net/connect/) is an identity layer built on top of OAuth 2.0 that standardizes the discovery endpoint used by the Portal to locate the IdP's DCR and token endpoints. + +This page covers JWT-based flows, where the IdP issues access tokens as JSON Web Tokens (JWTs). When an API Consumer's access request is approved, the Portal registers a new client with the IdP and writes the resulting IdP configuration into Tyk. + +At runtime, the API Consumer uses their credentials to obtain a JWT from the IdP, then presents it to Tyk Gateway when calling an API. The Gateway [validates the token's signature](/api-management/authentication/jwt-signature-validation) using the public keys published by the IdP at its JWKS endpoint, and maps the token's [scopes](#oauth-2-0-scopes) to Tyk policies to enforce access control. + +```mermaid +sequenceDiagram + participant Dev as Developer / App + participant Portal as Tyk Developer Portal + participant IdP as Identity Provider (IdP) + participant GW as Tyk Gateway + + rect rgb(235, 245, 255) + Note over Dev,GW: Setup: one time, on access request approval + Dev->>Portal: Submit access request + Portal->>IdP: Register OAuth 2.0 client (DCR) + IdP-->>Portal: Client credentials + Portal->>GW: Write JWKS URI + scope mappings + Note over Portal,GW: via Identity Provider Registry or API definition + Portal-->>Dev: Approval notification + credentials + end + + rect rgb(240, 255, 240) + Note over Dev,GW: Runtime: every API request + Dev->>IdP: Request JWT (client credentials) + IdP-->>Dev: JWT (signed access token) + Dev->>GW: API call with JWT + GW-->>IdP: Fetch JWKS (cached) + GW->>GW: Validate signature + map scopes to policies + GW-->>Dev: API response + end +``` + +### Developer Apps and OAuth Clients + +A Developer App in the Portal corresponds 1:1 to an OAuth 2.0 client registered in the IdP. When a DCR access request is approved, the Portal registers an OAuth client in the IdP on the API Consumer's behalf and stores the resulting credentials against the Developer App. The API Consumer uses these credentials to request access tokens from the IdP. + +A single Developer App can hold access to multiple API Products and Plans. Each subsequent approval adds the new scopes to the same OAuth client in the IdP rather than creating a new one. The API Consumer's credentials remain unchanged; their scope set grows. + +### OAuth 2.0 Scopes + +OAuth 2.0 scopes are central to how Tyk enforces access control in the DCR flow. Understanding their role makes the configuration steps that follow easier to understand. + +In Tyk's DCR flow, scopes serve as the link between the IdP, the API Consumer's access token, and Tyk's access control policies. Each API Product and each Plan has a unique scope name assigned to it. That scope name is what gets embedded in the access token by the IdP, and it is what Tyk Gateway uses to look up which policy to apply. + +Tyk's scope-to-policy mapping resolves one scope name to exactly one Tyk policy. No two API Products or Plans may share a scope; a duplicate would cause both to resolve to the same policy. Tyk Gateway combines the policies resolved from all scopes present in the token to authorize the request. + +The full sequence is: + +1. When an access request is approved, the Portal sends the scope names for the approved Product and Plan to the IdP as part of registering the OAuth 2.0 client. The IdP associates those scopes with that client, constraining what it can request. +2. When the API Consumer requests an access token, they must explicitly include those scope names. The IdP will only return scopes that are both registered with the client and present in the token request. +3. Tyk Gateway reads the scopes in the incoming JWT and maps each one to its corresponding Tyk policy, enforcing the access control and rate limits defined for that Product and Plan. + +Every API Product and Plan used with DCR must therefore have a unique scope assigned, and that scope must exist in the IdP before the Product or Plan is published. Publishing makes it available for access requests; if a scope is missing when a request is approved, the Portal's attempt to register the OAuth client will fail. Scope names must exactly match between the IdP and the Portal. + +## How Portal Manages Identity Providers + +Identity Providers are configured in the Portal under the **OAuth 2.0 Providers** menu. This is the Portal's term for an IdP. The two are the same concept: an OAuth 2.0 Provider in the Portal represents an external IdP that the Portal will register DCR clients with. + +There are two approaches to how the Portal stores IdP configuration in Tyk. + +| Approach | Available From | Where Scope Mappings Are Written | +|:---------|:---------------|:---------------------------------| +| Identity Provider Registry (recommended) | Portal 1.18.0, Tyk 5.14.0 | Identity Provider Registry | +| API Definition | All versions | API definitions | + +The **Identity Provider Registry** approach is recommended for all new deployments. It stores IdP configuration centrally in the Tyk Dashboard, decoupled from API definitions, eliminating write conflicts between the Portal and Tyk Dashboard and ensuring that IdP configuration is kept up to date as API Products change and as OAuth 2.0 Providers are created, updated, or deleted. Refer to the [Identity Provider Registry](/api-management/client-idp-registry) page for more detail. + +The **API definition** approach is available for installations running Tyk prior to 5.14.0, or for deployments where the Registry cannot be used. The Portal writes the JWKS URI and scope-to-policy mappings directly into the relevant API definitions when an access request is approved. + + +Refer to [Migrating to the Identity Provider Registry](#migrating-to-the-identity-provider-registry) if you have an existing installation using DCR and wish to use the IdP Registry. + + +## Configure Your Identity Provider + +Before configuring Tyk Developer Portal, you need to prepare the IdP in two ways: authorize the Portal to register OAuth 2.0 clients on behalf of API Consumers, and define the OAuth 2.0 scopes that will be included in access tokens. + +These steps are required regardless of which approach you use. + +### Authorize the Portal + +Most IdPs use an [initial access token](https://openid.net/specs/openid-connect-registration-1_0.html#Terminology) to authorize a trusted client to register new OAuth 2.0 clients via the DCR protocol. The Portal presents this token when registering a client for an API Consumer, proving it has permission to do so. + +Some IdPs use a different authorization mechanism and do not require an initial access token, for example: + +- Gluu uses a `dynamicRegistrationEnabled` flag on each scope instead. +- Auth0 uses a separate authorization model and does not issue initial access tokens. + +### Define OAuth 2.0 Scopes + +Create scopes in the IdP for each API Product and Plan you intend to use with DCR, following the naming and uniqueness requirements in the [OAuth 2.0 Scopes](#oauth-2-0-scopes) section above. Scopes must exist before the Product or Plan is published; see that section for details. + +The provider-specific tabs below show how to create scopes in each supported IdP. + +### Provider-Specific Instructions + + + + +Follow the [Keycloak client registration guide](https://www.keycloak.org/securing-apps/client-registration) to obtain the initial access token. + +Create scopes for each API Product and Plan from the **Client scopes** menu item. Set the scope type to **Optional**. Default scopes are applied automatically to all clients, but optional scopes can be requested on a case-by-case basis. The Portal requests specific scopes during client registration; using optional scopes ensures those scopes are included only when explicitly requested. + +Navigate to the Client scopes menu item + +Client Scope Assigned Type + + + + + +To obtain a Registration Access Token for Okta, go to **Okta Admin Console > Security > API > Tokens** and click **Create New Token**. Copy the token value; you will need it when configuring the OAuth 2.0 Provider in the Portal. For more details, refer to the [Okta Dynamic Client Registration guide](https://developer.okta.com/docs/reference/api/oauth-clients/). + +Create scopes for each API Product and Plan from the **Scopes** tab on the **Security > API** screen. All scopes must be associated with an authorization server. If you do not have a custom authorization server, use the **Default** one. Note the authorization server's issuer URL; you will need it when setting the OIDC well-known configuration URL in the Portal. The URL takes the form `https://{your-domain}/oauth2/default/.well-known/openid-configuration`. + +Add or Edit OAuth servers in Okta + + + + + +Auth0 does not require an initial access token. Follow the [Auth0 Dynamic Client Registration guide](https://auth0.com/docs/get-started/applications/dynamic-client-registration) to configure DCR. + +Create scopes for each API Product and Plan by navigating to **Dashboard > Applications > APIs**, selecting your API, and opening the **Permissions** tab. Enter the permission name and description for each scope, then click **Add**. + + + + + +When using [Curity](https://curity.io) as the Identity Provider, you must configure the DCR endpoint to use `no-authentication`. By default, Curity requires a nonce token with a `dcr` scope to authenticate the DCR endpoint, but nonce tokens are one-time-use and cannot be stored as a reusable credential in the Portal. Setting the authentication method to `no-authentication` allows the Portal to register clients without presenting a token. + +Ensure that network access to the Curity DCR endpoint is restricted to Tyk only, since it will be unauthenticated. + +To configure this: go to **Profiles > Token Service > Dynamic Registration**, scroll to the **Non-templatized** section, and set **Authentication Method** to `no-authentication`. + +Create scopes for each API Product and Plan from **Profiles > Token Service > Scopes**. + +Navigate to the Scopes menu + +For more information, see the [Curity DCR documentation](https://curity.io/docs/identity-server/profiles/token-profile/clients/dcr). + + + + + +[Gluu](https://gluu.org/) does not use an initial access token for DCR. Instead, DCR is authorized per-scope via the **Dynamic Registration** toggle. + +Create scopes for each API Product and Plan by navigating to **Configuration > OpenID Connect > Scopes** and clicking **Add Scope**. For each scope, enable the **Dynamic Registration** toggle so that it can be included in DCR client registration requests. + +For more information, see the [Gluu Server documentation](https://docs.gluu.org). + + + + + +## Configure Tyk Developer Portal + +### Enable the Identity Provider Registry (Recommended) + +The Identity Provider Registry is available from Tyk Developer Portal 1.18.0 when using Tyk Dashboard 5.14.0 or later. + +It must be explicitly enabled in Tyk Developer Portal by setting [`TYK_PORTAL_ENABLEIDPREGISTRY=true`](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#tyk_portal_enableidpregistry) (or `EnableIDPRegistry = true` in the config file) and restarting the Portal. + +Ensure that Tyk Gateway and Tyk Dashboard are running on version 5.14.0 or later before starting Tyk Developer Portal with this flag enabled. + + +If you are running Tyk Dashboard prior to 5.14.0, skip this step as the IdP Registry is not available. + + +### Configure OAuth 2.0 Providers + +In the Admin Portal, navigate to **OAuth 2.0 Providers** (the Developer Portal's name for Identity Providers) and create an entry for each IdP you want to use with DCR. + +#### Connection Settings + +Configuring the connection to the IdP in OAuth 2.0 Providers + +| Field | Description | +|:------|:------------| +| **Name** | A label to identify this OAuth 2.0 Provider in the Portal UI. This is also stored as the name of the corresponding entry in the Identity Provider Registry. | +| **Identity provider type** | Select your IdP from the dropdown. If it is not listed, select **Other** to use a standard RFC 7591-compliant client registration flow. | +| **OIDC well-known configuration URL** (required) | The OIDC discovery endpoint for your IdP. | +| **Scope claim name** (optional) | The JWT claim that contains the token's scopes. Different IdPs use different claim names: `scope` is the most common, but some use `scp` or another custom claim. Defaults to `scope`. Used by Tyk Gateway when [mapping token scopes to policies](/api-management/authentication/jwt-authorization#scope-policies). | +| **Registration access token** (optional) | The token obtained in the [Authorize the Portal](#authorize-the-portal) section. | +| **SSL insecure skip verify** (optional) | Enable only if your IdP uses a self-signed or privately issued certificate that Tyk cannot verify. This disables TLS certificate verification for connections to the IdP and should not be used in production. | + +#### Client Profiles + +When the Portal registers an OAuth 2.0 client in the IdP on behalf of an API Consumer, it must specify certain parameters that describe how access tokens will be obtained. These include the OAuth [grant type](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3) and the authentication method used by the token endpoint. A **Client Profile** is a named template for these parameters (referred to as a **client type** in the Portal UI). + +You can define multiple Client Profiles to support different use cases. For example, you might define one Client Profile using the client credentials grant for server-to-server integrations and another using the authorization code grant for user-facing applications. + +When requesting access to an API Product in the Portal, API Consumers select from the list of Client Profiles defined for the IdP. The Portal uses that template when registering the OAuth 2.0 client that the IdP will use to issue tokens to that API Consumer. + +To add a Client Profile, scroll to **Client Types** and click **Add client type**. + +Configuring a Client Profile for OAuth 2.0 client creation + +| Field | Description | +|:------|:------------| +| **Client type display name** | The name shown to API Consumers at checkout. Keep it short and descriptive, for example "Server-to-server" or "Web application". | +| **Description** | Additional context to help API Consumers choose the right Client Profile. Not shown by default but configurable via templates. | +| **Allowed response types** | Controls what the IdP returns to the API Consumer's application after authorization. Note: when using Okta with the client credentials grant, set this to `token`.
  • `code`: authorization code to exchange for tokens
  • `token`: access token returned directly
  • `id_token`: identity token (OIDC)
See the [OIDC specification](https://openid.net/specs/openid-connect-core-1_0.html#Authentication) for details. | +| **Allowed grant types** | The OAuth 2.0 grant flow the API Consumer's application will use to obtain tokens.
  • `client_credentials`: server-to-server, no user involved
  • `authorization_code`: user-facing applications
  • `refresh_token`: obtain new access tokens without re-authenticating
See the [OAuth 2.0 specification](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3) for details. | +| **Token endpoint auth methods** | How the API Consumer's application authenticates to the IdP's token endpoint.
  • `client_secret_basic`: credentials as a Base64-encoded Authorization header
  • `client_secret_post`: credentials in the request body
See the [OIDC specification](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication) for details. | +| **Okta application type** (Okta only) | The Okta application type to create: `web` for server-side apps, `native` for mobile or desktop, `browser` for single-page apps, `service` for machine-to-machine. | + + +Your IdP may override some of these settings based on its own configuration. + + +When you have finished, click **Save Changes**. + + +When an OAuth 2.0 Provider is created, updated, or deleted in Portal, the corresponding entry in the Identity Provider Registry is updated immediately on every connected Tyk Dashboard. No approval is required to propagate the change. + + +### API Products and Plans + +Each API Product and each Plan used with DCR must have a unique OAuth 2.0 scope assigned, matching a scope you created in the IdP. See the [OAuth 2.0 Scopes](#oauth-2-0-scopes) section for the full requirements. + +See [API Products](/portal/api-products#dynamic-client-registration) and [API Plans](/portal/api-plans#dynamic-client-registration) for configuration instructions. + + +When APIs are added to or removed from a DCR-enabled API Product, or when its DCR scopes are changed, the scope-to-policy mappings in the Identity Provider Registry are updated immediately. Existing tokens gain or lose access without requiring a new approval. + +This applies only when at least one access request for the API Product has already been approved. If no approval has ever been made, the Registry has no entry for that Product yet and there is nothing to update; the first approval will write the full mapping. + + +## End-to-End DCR Flow + +This section walks through the complete DCR flow from the perspective of each actor: an API Consumer requesting access, an API Owner approving it, and the API Consumer using their credentials to call an API. It covers what happens at each stage and what to expect as output. + +Before proceeding, confirm that the following are in place: + +- At least one [OAuth 2.0 Provider](#configure-your-identity-provider) is configured in the Portal, with at least one [Client Profile](#client-profiles) defined. +- At least one API in Tyk Dashboard has JWT authentication enabled. +- An API Product includes that API and has DCR enabled, with a scope assigned. +- A Plan has a scope assigned. +- Both scopes [exist in the IdP](#oauth-2-0-scopes). + +### Request Access to the API Product + +The API Consumer discovers the DCR-enabled API Product in the catalog and submits an access request. As part of checkout, they select how their application will obtain tokens. + +As an API Consumer, log in and navigate to the catalog page. Select the DCR-enabled API Product, proceed to checkout, and complete the following: + +- Select a Plan. +- Select an existing Developer App or create a new one. +- Select a Client Profile (**client type** in the Portal UI). +- If your Client Profile uses the authorization code grant, enter your application's redirect URI in the **Redirect URLs** field. This is the URL the IdP will redirect the user to after authentication, carrying the authorization code your application exchanges for a token. Separate multiple URIs with commas. +- Click **Submit request**. + +Request access to the DCR-enabled product + +### Approve the Access Request + +An API Owner reviews and approves the request. + +The Portal then registers a new OAuth 2.0 client with the IdP on the API Consumer's behalf, using the selected Client Profile to determine the grant type and token endpoint authentication method. The scopes from the API Product and Plan are associated with that client in the IdP. + +As part of the same process, the Portal retrieves the JWKS URI from the IdP and writes it, together with the scope-to-policy mappings, into Tyk. With `EnableIDPRegistry = true`, these are written to the Identity Provider Registry. Otherwise, they are written into the API definition. + +As an API Owner, navigate to **Access Requests**, select the request, and click **Approve**. + +Approve DCR access request + +### Obtain an Access Token + +Once the request is approved, the API Consumer can retrieve their OAuth 2.0 credentials from **My Dashboard**. Navigate to the Developer App and copy the **client ID** and **secret**. + +Copy the OAuth 2.0 credentials + +Use these credentials to request an access token from the IdP's token endpoint. You must include the scopes for the API Product and Plan in the request. Tyk Gateway uses these to identify which policies to apply, and will reject requests where the token does not contain the expected scopes. + +The example below uses the `client_credentials` grant with the `client_secret_basic` authentication method, where credentials are passed as a Base64-encoded `{client_id}:{client_secret}` string in the `Authorization` header. + +```bash +curl --location --request POST '' \ +--header 'Authorization: Basic N2M2NGM2ZTQtM2I0Ny00NTMyLWFlMWEtODM1ZTMyMWY2ZjlkOjNwZGlJSXVxd004Ykp0M0toV0tLZHFIRkZMWkN3THQ0' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'scope=product_payments free_plan' \ +--data-urlencode 'grant_type=client_credentials' +``` + +A successful response includes a JWT access token. Decode it to confirm it contains the expected scopes before making an API call. + +An example of a JWT + +### Make an API Call + +With a valid JWT access token, the API Consumer can call the API. Tyk Gateway validates the token signature and maps the scopes to policies before forwarding the request. + +Use the access token to call the API: + +```bash +curl --location --request GET '/payment-api/get' \ +--header 'Authorization: Bearer ' +``` + +## Migrating to the Identity Provider Registry + +DCR support in Tyk Developer Portal has evolved across three eras. The table below summarizes each to help you identify your current state before migrating. + +| Era | Portal Version | How IdP Config Is Stored | Limitations | +|:----|:--------------|:------------------------|:------------| +| Legacy | Before 1.13.0 | Manually in each API definition | No Portal management of IdP configuration; scope mappings require manual setup per API in Tyk Dashboard | +| API Definition | 1.13.0 to 1.17.x | Portal writes to API definitions at approval | Write conflicts possible between Portal and Tyk Dashboard; configuration not updated automatically when API Products change | +| Identity Provider Registry | 1.18.0+ | Centralized Registry in Tyk Dashboard | Recommended for all deployments | + +### Legacy Setup (Before Portal 1.13.0) + +Before Portal 1.13.0, DCR required fully manual configuration of scope-to-policy mappings in each Tyk Dashboard API definition. The Portal did not write anything into API definitions when an access request was approved. For each JWT-authenticated API, this involved: + +- Creating Tyk policies for the API Product and Plan +- Creating a No Operation API and policy to satisfy the default policy requirement without granting real access. On Gateway versions prior to 5.11.0, Tyk required a default policy on APIs using scope-to-policy mapping; the No Operation API satisfied this without overriding the Product and Plan policies. From Gateway 5.11.0 onwards this workaround is no longer needed. +- Manually enabling scope-to-policy mapping on each API definition and configuring the JWKS URI, scope-to-policy mappings, and default policy directly + +To migrate to the Identity Provider Registry, first upgrade to Portal 1.18.0, then follow the [Migration Steps](#migration-steps) below. Note that the automatic backfill does not cover manually configured API definitions. You will need to create OAuth 2.0 Providers in the Portal for each IdP, then populate the Registry manually via `POST /api/clientidps`. + +### API Definition Era (Portal 1.13.0 to 1.17.x) + +From Portal 1.13.0, the Portal began managing IdP configuration directly. OAuth 2.0 Providers and Client Profiles are configured in the Portal, and when an access request is approved, the Portal automatically writes the JWKS URI and scope-to-policy mappings into the relevant API definitions. + +For each JWT-authenticated API, the setup required leaving the **Public key** and **default policy** fields blank. The Portal populated these when an access request was approved. On Gateway versions prior to 5.11.0, a No Operation API and policy were also required to satisfy the default policy requirement; from Gateway 5.11.0 this is no longer needed. + +The limitation of this approach is that API definition configuration takes precedence over any changes made in Tyk Dashboard, and write conflicts can occur if the Portal and Tyk Dashboard both manage the same API definition. Configuration is also not updated automatically when API Products change outside of an approval event. + +To migrate to the Identity Provider Registry, first upgrade to Portal 1.18.0, then follow the [Migration Steps](#migration-steps) below. + +### Migration Steps + +1. Set [`TYK_PORTAL_ENABLEIDPREGISTRY=true`](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#tyk_portal_enableidpregistry) (or `EnableIDPRegistry = true` in the config file) and restart the Portal. + + On startup, the Portal runs a backfill that creates Registry entries in Tyk Dashboard for any existing OAuth 2.0 Providers. Any new, or changes to existing, OAuth 2.0 Providers and API Products will be stored in the Registry going forward. + + If moving from a [legacy version](#legacy-setup-before-portal-1-13-0) there will be nothing for the backfill to process. + +2. **Audit your API definitions.** + + Call `GET /api/clientidps` against the Tyk Dashboard API to inspect the Registry entries created by the backfill. + + For each JWT-authenticated API, compare the JWKS URI and scope-to-policy mappings in the API definition against what the Registry now contains. + + Pay particular attention to any IdP configuration that was added directly in Tyk Dashboard rather than through the Portal. The backfill only covers OAuth 2.0 Providers managed by the Portal; configuration added manually outside the Portal will not appear in the Registry after the backfill. Any such configuration must be added to the Registry manually via `POST /api/clientidps` before proceeding to the next step. + + + There is no Tyk Dashboard UI for the Identity Provider Registry. All inspection and manual management must be done via the [Tyk Dashboard API](/tyk-dashboard-api). + + +3. **Remove IdP configuration from API definitions.** + + Once the Registry is verified as complete and correct, you can remove the JWKS URIs and scope-to-policy mappings from each API definition. The configuration in the API definition [takes precedence over the Registry](/api-management/client-idp-registry#what-is-the-identity-provider-registry)) so if an issuer and scope match is found in the API definition, this will be used rather than the configuration in the Registry. + + + Do not remove configuration from an API definition until you have confirmed that the corresponding Registry entry exists and is correct. Removing configuration that has no Registry counterpart will break JWT validation for that API. + diff --git a/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso.mdx b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso.mdx new file mode 100644 index 0000000000..ca55a11c64 --- /dev/null +++ b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/enable-sso.mdx @@ -0,0 +1,353 @@ +--- +title: "Single Sign-On" +description: "Learn how to configure Single Sign-On (SSO) for Tyk Developer Portal, allowing API Owners and API Consumers to log in with their existing identity provider credentials." +keywords: "Tyk Developer Portal, SSO, Single Sign-On, Tyk Identity Broker, TIB, Authentication, Identity Provider" +sidebarTitle: "Single Sign On" +--- + +Tyk Identity Broker (TIB) enables Single Sign-On (SSO) for Tyk Developer Portal, allowing users to log in using their existing identity provider (IdP) credentials. + +The Tyk Developer Portal has two distinct audiences, each requiring a different TIB configuration: + +- **API Owners** - Portal administrators who manage APIs, plans, and developer access from the Admin Portal. +- **API Consumers** - External developers who browse the Live Portal and request API access. + + +We recommend that you read the [Tyk Identity Broker overview](/tyk-identity-broker/overview) before configuring SSO for Developer Portal. + + +## How It Works + +When a user logs in via SSO, TIB authenticates them against the configured IdP and then calls the Tyk Developer Portal API to obtain a one-time nonce. TIB redirects the user's browser to the Portal API's `/sso` endpoint with the nonce appended. Tyk Developer Portal validates the nonce and creates a session automatically. + +```mermaid +sequenceDiagram + actor User + participant IDP as Identity Provider (IdP) + participant TIB as Tyk Identity Broker + participant Portal as Tyk Developer Portal + + User->>TIB: Log in + TIB->>IDP: Verify identity + IDP->>TIB: Identity confirmed + TIB->>Portal: Request SSO nonce + Portal->>TIB: One-time nonce + TIB-->>User: Browser redirected to /sso?nonce=... + Note over User,Portal: Browser follows redirect automatically + Portal->>User: Session created, logged in +``` + +The outcome of a login depends entirely on which [TIB profile](/tyk-identity-broker/overview#profile) is used. + +- You will typically configure two separate TIB profiles - one for each audience - and publish their respective login URLs to the appropriate users. +- The login URL contains the profile ID: + +``` + http://{portal-host}/tib/auth/{profile-id}/{provider} +``` + +- The `ActionType` configured in that profile determines the result: + + | `ActionType` | Audience | Portal Access | + |---|---|---| + | `GenerateOrLoginUserProfile` | API Owners | Admin Portal | + | `GenerateOrLoginDeveloperProfile` | API Consumers | Live Portal | + + + +## Enabling Single Sign-On + +To enable Single Sign-On with Portal you must set the following configuration: + +- set `PORTAL_TIB_ENABLED=true` in [the portal configuration](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#sample-env-file) +- set the `TYK_IB_SESSION_SECRET` environment variable with a secret that will be used to sign the [redirect session cookie](/tyk-identity-broker/overview#redirect-session-cookie) if you are using an IdP that implements the redirect flow, such as those using OpenID Connect. + + +From Portal v1.12.0, TIB is embedded in the portal and no separate TIB installation is required. If you are running an earlier version, or have a specific infrastructure requirement, see [Using Standalone TIB](#using-standalone-tib). + + + +## API Owner Login + +Tyk Developer Portal maintains user accounts for all API Owners, identified by email address. + +When a user is authenticated by the IdP and is directed to a TIB profile configured for action `GenerateOrLoginUserProfile`, the following decision tree is followed: + +```mermaid +flowchart TD + A[API Owner authenticates via TIB] --> B{Account found with matching email address?} + B -- Yes --> C{Account active?} + B -- No --> D{Is AdminRegistrationAllow set to true?} + C -- Yes --> E[Logged in] + C -- No --> F[Login refused] + D -- Yes --> G[New account created with Provider Admin role] + G --> E + D -- No --> F +``` + + +By default, Tyk will automatically create a new account for any successful login where there is no existing admin account for the user's email address. Set `AdminRegistrationAllow` to `false` in the [Portal configuration](/product-stack/tyk-enterprise-developer-portal/deploy/configuration) to require that an admin account must already exist before SSO login is permitted. + + +### API Owner Profile Configuration + +The following [TIB profile](/tyk-identity-broker/overview#profile) fields are required for API Owner SSO: + +| Field | Value | +|---|---| +| `ID` | Unique identifier for this profile. Forms part of the TIB authentication URL. | +| `ActionType` | `GenerateOrLoginUserProfile` | +| `OrgID` | Must be `"0"` | +| `ReturnURL` | `http://{portal-host}/sso` | +| `IdentityHandlerConfig.DashboardCredential` | Must match `PORTAL_API_SECRET` in the Portal configuration | +| `ProviderName` | Authentication method. See [IdP-specific guides](#set-up-sso-with-your-identity-provider). | +| `ProviderConfig` | IdP-specific connection settings. See [IdP-specific guides](#set-up-sso-with-your-identity-provider). | +| `Type` | `redirect` for OIDC/Social; `passthrough` for LDAP/Proxy. | + +The following optional fields are also available: + +| Field | Description | +|---|---| +| `CustomEmailField` | The IdP claim to use as the user's email address. If not set, TIB uses the standard email claim. | +| `CustomUserIDField` | The IdP claim to use as the user's unique identifier. If not set, TIB uses the standard subject claim. | + +## API Consumer Login + +When a user authenticates, the IdP returns a set of attributes about them, such as their name, email address, and group membership. TIB receives these attributes as a key-value map. + +When configured with an API Consumer SSO profile, TIB derives an **SSO key** from the user's IdP identity. The SSO key is a combination of the user's unique IdP user ID and the provider name (for example, `abc123@okta`). It is stored on the Portal developer account and used to recognize the same user on subsequent logins, independently of their email address. + +### Login Flow + +TIB uses the SSO key to determine whether to create or update a Portal developer account: + +```mermaid +flowchart TD + A[User authenticates with IdP] --> B[TIB: look up account by SSO key] + B --> C{SSO key matches existing account?} + C -- Yes --> D{Account has existing Team allocation?} + C -- No --> E{Email matches existing account?} + D -- Yes --> F[Logged in] + D -- No --> G[Assign to Team based on TIB profile and IdP claim] + G --> F + E -- Yes --> H[Link SSO key to account] + H --> F + E -- No --> I[Create new account with API Consumer Admin role] + I --> G +``` + + +There is no configuration option to restrict API Consumer SSO login to pre-existing accounts only. A new user who successfully authenticates via SSO will always have a new [API Consumer Admin](/portal/api-consumer#api-consumer-admin) account created for them. + + +### Team Assignment + +When a user logs into an account which is not assigned to any Teams - either because it is newly created, or because all Teams were removed after the first login (as shown in the diagram above) - Team assignment takes place based on the TIB profile and claims in the attributes returned by the IdP. + +This assignment involves two stages: TIB resolves the user's IdP group claim to a Portal Team ID, then the Portal looks up that Team and assigns the user. + +**Stage 1: TIB resolves the Team ID** + +```mermaid +flowchart TD + A[TIB reads IdP attributes] --> B{Is CustomUserGroupField claim found?} + B -- Yes --> C{Is the Value from CustomUserGroupField found in UserGroupMapping?} + B -- No --> E{Is DefaultUserGroupID configured?} + C -- Yes --> D[Use mapped Portal Team ID] + C -- No --> E + E -- Yes --> F[Use DefaultUserGroupID] + E -- No --> G[No Team ID sent] +``` + +**Stage 2: Portal assigns the Team and Organisation** + +```mermaid +flowchart TD + A[Portal receives Team ID from TIB] --> B{Team ID provided?} + B -- Yes --> C{Valid Team ID?} + B -- No --> D[Account has no Team or Organisation] + C -- Yes --> E[Assign Team, set Organisation from Team] + C -- No --> F[Error: login fails] +``` + + +If `DefaultUserGroupID` is set to a Team ID that does not exist in the Portal, login will fail. Always ensure `DefaultUserGroupID` refers to a valid, existing Team. + + + +Once a user has been assigned to a Team - whether via SSO on first login or manually by an admin - subsequent SSO logins will never change their Team or Organisation. Any manual changes made in the Portal are preserved. + + +### User Group Mapping Configuration + +The following TIB profile fields control how IdP group claims are resolved to Portal Team IDs (Stage 1 above): + +| Profile Field | Description | +|---|---| +| `CustomUserGroupField` | The key in the IdP attributes map that contains the user's group membership. | +| `UserGroupMapping` | Maps IdP group values to Portal Team IDs. If multiple values match, the first match is used. | +| `DefaultUserGroupID` | The Portal Team ID to use when no mapping matches. | + +User group mapping + +### API Consumer Profile Configuration + +The following [TIB profile](/tyk-identity-broker/overview#profile) fields are required for API Consumer SSO: + +| Field | Value | +|---|---| +| `ID` | Unique identifier for this profile. Forms part of the TIB authentication URL. | +| `ActionType` | `GenerateOrLoginDeveloperProfile` | +| `OrgID` | Must be `"0"` | +| `ReturnURL` | `http://{portal-host}/sso` | +| `IdentityHandlerConfig.DashboardCredential` | Must match `PORTAL_API_SECRET` in the Portal configuration | +| `ProviderName` | Authentication method. See [IdP-specific guides](#set-up-sso-with-your-identity-provider). | +| `ProviderConfig` | IdP-specific connection settings. See [IdP-specific guides](#set-up-sso-with-your-identity-provider). | +| `Type` | `redirect` for OIDC/Social; `passthrough` for LDAP/Proxy. | + +The following optional fields are also available: + +| Field | Description | +|---|---| +| `CustomUserGroupField` | The IdP claim that contains the user's group membership. Required for [User Group Mapping](#user-group-mapping-configuration). | +| `UserGroupMapping` | Maps IdP group values to Portal Team IDs. See [User Group Mapping](#user-group-mapping-configuration). | +| `DefaultUserGroupID` | The Portal Team ID to use when no group mapping matches. See [User Group Mapping](#user-group-mapping-configuration). | +| `CustomEmailField` | The IdP claim to use as the user's email address. If not set, TIB uses the standard email claim. | +| `CustomUserIDField` | The IdP claim to use as the user's unique identifier. If not set, TIB uses the standard subject claim. | + + +## Creating a TIB Profile + +TIB profiles are managed in the Portal UI under **Settings > SSO Profiles**. + +1. Select **Add new SSO Profile**. +2. Complete the **Profile action** step. Choose a **Name** (this becomes the profile `ID`), select the **Profile type** - **Profile for admin users** for API Owner login or **Profile for developers** for API Consumer login - and set the failure redirect URL. + + SSO Profiles Wizard - Profile action step + +3. Select the **Provider type** for your IdP. + + SSO Profiles Wizard - Provider type selection + +4. Complete the **Profile configuration** step with your IdP connection details. + + SSO Profiles Wizard - Profile configuration + +5. For developer profiles, configure the **Group mapping**. Set **Custom user group claim name** to the IdP claim that contains the user's group membership. For admin profiles, skip this step. + + SSO Profiles Wizard - Group mapping + +6. Click **Continue** to create the profile. + +You can view and edit the profile JSON directly from the **Raw editor** view. + +SSO Profiles Raw Editor + + +The SSO profile wizard supports OIDC, LDAP, and Social provider types. SAML is not available as a selectable option so you would need to create the TIB profile in the **Raw editor** view. + + +## Initiating SSO Login + +The TIB authentication URL for each profile is shown in the profile's **Provider configuration** section in **Settings > SSO Profiles**. + +SSO Profile Details - Login URL + +How users initiate login depends on the flow type used by their IdP. + +### Redirect Flow (OIDC, Social) + +For redirect-based IdPs, the IdP hosts the login page. Users need to be directed to the TIB authentication URL, which then redirects them to the IdP. + +From Portal v1.16.0, you can configure `PORTAL_SSO_CUSTOM_LOGIN_URL` to automatically redirect users from the Portal login page to your IdP's login flow. When set, all login requests - including the Portal's **Log in** button - are redirected to the specified URL instead of displaying the built-in login form. + +Set it to the TIB authentication URL for the profile: + + + +```ini +PORTAL_SSO_CUSTOM_LOGIN_URL=http://{portal-host}:{portal-port}/tib/auth/{profile-id}/openid-connect +``` + + +```json +{ + "SSOCustomLoginURL": "http://{portal-host}:{portal-port}/tib/auth/{profile-id}/openid-connect" +} +``` + + + +For the full reference, see [PORTAL_SSO_CUSTOM_LOGIN_URL](/product-stack/tyk-enterprise-developer-portal/deploy/configuration#portal_sso_custom_login_url) in the Portal configuration reference. + + +This setting only redirects the login entry point. User registration and password reset pages are not affected. + + +For earlier Portal versions, share the TIB authentication URL directly with users. + +### Passthrough Flow (LDAP, Proxy) + +For passthrough-based IdPs, there is no IdP-hosted login page. Users submit their credentials directly to TIB via a form `POST`. You must provide a custom HTML login page, for example: + +```html expandable + + + Developer Portal Login + + + Login to the Developer Portal +
+ Username:
+ Password:
+ +
+ + +``` + +Replace `{portal-host}`, `{portal-port}`, and `{profile-id}` with the values for your installation. + +## Using Standalone TIB + +By default, Tyk Developer Portal uses its embedded TIB for SSO from v1.12.0 onwards. If you need to use a standalone TIB instance instead - for example, if you are running an earlier Portal version - install and configure TIB separately and point it at the Portal. + +The TIB configuration for standalone Portal SSO requires the `TykAPISettings.DashboardConfig` block to reference the Portal host and `PortalAPISecret`: + +```json +{ + "TykAPISettings": { + "DashboardConfig": { + "Endpoint": "http://{portal-host}", + "Port": "{portal-port}", + "AdminSecret": "{portal-api-secret}" + } + } +} +``` + +| Setting | Description | +|---|---| +| `Endpoint` | URL of the Tyk Developer Portal. | +| `Port` | Port on which the Portal is running. | +| `AdminSecret` | Must match `PORTAL_API_SECRET` in the Portal configuration. | + +For full installation and configuration instructions, see [Install Standalone TIB](/tyk-identity-broker/standalone-tib). + + +TIB uses the same `DashboardConfig` block to connect to both Tyk Dashboard and Tyk Developer Portal - a single standalone TIB instance can only point at one. If you need standalone TIB for both Dashboard SSO and Portal SSO in the same deployment, you must run a separate TIB instance for each. + + +## Set Up SSO with Your Identity Provider + +Select your identity provider to get started: + +| Identity Provider | Guide | +|---|---| +| Microsoft Entra ID (OIDC), ADFS | [SSO with Microsoft Entra ID](/tyk-identity-broker/sso-entra-id) | +| Okta (OIDC) | [SSO with Okta](/tyk-identity-broker/sso-okta) | +| Auth0 | [SSO with Auth0](/tyk-identity-broker/sso-auth0) | +| Keycloak | [SSO with Keycloak](/tyk-identity-broker/sso-keycloak) | +| Active Directory, OpenLDAP | [SSO with LDAP](/api-management/single-sign-on-ldap) | +| Google, GitHub, LinkedIn, and other OAuth providers | [SSO with Social Providers](/api-management/single-sign-on-social-idp) | +| Custom or legacy authentication endpoints | [SSO with Proxy Provider](/api-management/custom-auth-with-proxy-identity-provider) | diff --git a/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations.mdx b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations.mdx new file mode 100644 index 0000000000..2e35a69a26 --- /dev/null +++ b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations.mdx @@ -0,0 +1,334 @@ +--- +title: "Organisations and Teams" +description: "How to manage Organisations in Tyk Developer Portal" +keywords: "Developer Portal, Tyk, API Consumer, Organisation, Organization, Team" +sidebarTitle: "Organisations and Teams" +--- + +## Introduction + +The Tyk Developer Portal uses Organisations and Teams to provide flexible, hierarchical access control for your API ecosystem. This structure allows you to manage API Consumers at both the organizational level and in smaller functional groups, reflecting real-world business relationships and access requirements. + +Unlike individual developer accounts, Organisations represent entire companies or business entities with sophisticated requirements: + +- **Team-based access:** Companies typically have multiple developers who need access to your APIs. Tyk Developer Portal's Organisation and Team structure ensures communication and access don't depend on a single individual who might leave the company. +- **Secure credential sharing**: Organizations need secure ways to share API credentials within their teams. Without proper tooling, developers resort to sharing credentials through insecure channels, creating security risks. +- **Hierarchical permissions**: Within organizations, some users need administrative capabilities while others require more limited access. The Tyk Developer Portal supports this through API Consumer Admin and Team Member roles. +- **Self-service team management**: Organizations can maintain their own teams by inviting new members or removing departed ones, reducing administrative overhead for API providers. + +This organizational approach allows you to manage API Consumers at both the company level and in smaller functional groups, supporting complex business relationships while maintaining security and governance. + +
+ + +**A note on spelling** + +Throughout this documentation, we use specific spelling conventions to help distinguish between product features and general concepts: +- Organisation (with an 's') refers specifically to the entity within the Tyk Developer Portal (sometimes abbreviated to Org) +- organization (with a 'z') refers to real-world businesses or the general concept of organizing + +This British/American English distinction helps clarify when we're discussing the Tyk Developer Portal feature versus general organizational concepts. + + + +### Understanding the Organizational Hierarchy + +Organisations and Teams create a two-level hierarchy that provides granular control over API access. This allows API Owners to manage access at multiple levels, supporting complex business relationships while maintaining security and governance. Note that users can belong to multiple Teams within an Organisation, allowing for flexible resource allocation based on project needs or job responsibilities. + +For example, consider a Partner (Acme Bank) that wishes to consume your APIs. They have an *Accounts* team that requires access to a specific set of APIs and a *Development* team that requires access to those plus additional APIs. + +- You create an Organisation for the client (Acme Bank) +- You create separate Teams for their *Accounts* and *Development* users +- You construct two [Catalogs](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-catalogues) of [API Products](/portal/api-products) and [Plans](/portal/api-plans) + - Catalog 1 contains the Accounts APIs and subscription Plans + - Catalog 2 contains the Developer APIs and subscription Plans +- You configure Catalog visibility as follows: + - Catalog 1 is made visible to both Teams + - Catalog 2 is made visible only to the Developer Team +- You create an [API Consumer Admin](/portal/api-consumer#api-consumer-admin) user for each Team + - these users can invite colleagues into their Team as [API Consumer Team Member](/portal/api-consumer#team-member) users + +Diagram showing an Organisation with two Teams of API Consumers + +With this configuration, the Admin and Team Members in each team are unaware of the other Team or its members. The members of the Accounts team have access to discover and consume the API Products in Catalog 1, whilst the members of the Development team have access to both Catalogs. + +### Default Organisation + +The system automatically creates a pre-configured "Default Organisation" during the [bootstrap](/portal/install#bootstrapping-developer-portal) process that serves as the initial home for: + +- Self-registered users without an [invite code](/portal/api-consumer#invite-codes) +- API Consumer users created by API Owners without a specific Organisation assignment +- API Consumer users whose Organisation has been [deleted](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#deleting-organisations) + +While the Default Organisation cannot be deleted, you can: + +- [Rename](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#editing-organisation-details) it to better reflect your business needs +- Move users from it to other Organisations as needed +- Use it as a holding area for users awaiting proper Organisation assignment + +#### Developer App visibility + +[Team and Organisation level app visibility](/portal/developer-app#visibility) is not applied within the Default Organisation. This behavior has been implemented to prevent accidental exposure of Developer Apps if a user is removed from a custom Organisation and automatically reverts to the Default Org. + +
+ + +We do not recommend using the Default Org for publication of API Products and Plans. + + + +## Managing Organisations + +Organisations represent companies or business units that consume your APIs. + +- **Purpose**: Group related teams and developers under a single entity +- **Hierarchy**: Each API Consumer belongs to exactly one Organisation +- **Default Organisation**: A system-provided Organisation where users are placed if not assigned elsewhere +- **Creation**: Organisations can only be created by API Owners (or by self-registered developers if [Organisation requests](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#requesting-a-new-organisation) are enabled) +- **Management**: + - API Owners can create, modify, and delete any Organisation + - API Consumer Admins can manage users within their Organisation + +### Creating Organisations + +As an API Owner, you can create new Organisations to represent partner companies or business units: + +1. Navigate to **API Consumers > Organisations** in the Admin Portal +2. Select **Add new Organisation** + Click on Add to create a new Organisation +3. Provide a **Name** for the new Organisation + Giving the new Organisation a name +4. Select **Save changes** to create the Organisation + +Once created, you can begin adding Teams and users to the Organisation. Note that a [default Team](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#default-team) is automatically created with the Organisation. + +### Editing Organisation Details + +As an API Owner, you can change the name of an Organisations to represent changes in partner companies or business units: + +1. Navigate to **API Consumers > Organisations** in the Admin Portal +2. Select the Organisation you want to rename +3. Update the **Name** +4. Select **Save changes** + +### Deleting Organisations + +As an API Owner, you can delete an Organisation to represent changes in partner companies or business units: + +1. Navigate to **API Consumers > Organisations** in the Admin Portal +2. Select the three dot menu next to the Organisation you want to delete +3. Select **Delete** +4. Confirm the deletion + +The Organisation and any Teams created within it will be deleted immediately. + +All users (both API Consumer Admins and Team Members) will be moved to the [default Team](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#default-team) in the [default Organisation](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#default-organisation) where any Developer Apps they own will have their visibility set to [Personal](/portal/developer-app#visibility) + +### Best Practices for Organisation Management + +- **Naming conventions**: Establish a consistent naming pattern for Organisations +- **Regular audits**: Periodically review Organisation membership and activity +- **Documentation**: Maintain records of which real-world entities each Organisation represents +- **Onboarding process**: Create a standardized workflow for adding new Organisations + +## Working With Teams + +Teams are groups of developers who collaborate on related projects. + +- **Purpose**: Enable collaboration and shared access to API resources +- **Hierarchy**: + - Teams exist within a specific Organisation + - API Consumers can belong to multiple Teams within their Organisation +- **Default Team**: Each Organisation has a default Team where users are placed if not assigned to any other team +- **Creation**: Teams can only be created by API Owners +- **Management**: + - **API Owners** can create, modify, and delete any Team + - **API Consumer Admins** can manage team membership within their Organisation + +### Creating Teams + +Teams allow you to organize API Consumers into functional groups with specific API access: + +1. As an API Owner, navigate to **API Consumers > Teams** in the Admin Portal +2. Select **Add new Team** +3. Complete the team details: + - **Name**: A descriptive name for the team (required) + - **Organisation**: Select the Organisation this team belongs to +4. Select **Save changes** + +Teams can represent departments, project groups, or any logical grouping that helps organize API access within an Organisation. + +### Managing Team Membership + +Once a team is created, an API Owner can add members from the Organisation containing the Team: + +1. Navigate to **API Consumers > Users** in the Admin Portal +2. Find and select the user you wish to add or remove +3. If they are not in the Organisation containing the Team, change their **Organisation** +3. Modify their Team membership in the **Teams** section +4. Select **Save changes** + +An API Consumer Admin can configure the Team membership of other API Consumer users that share any Teams with the Admin as described [here](/portal/api-consumer#managing-api-consumer-users-in-the-live-portal). This self-service capability allows Organisations to manage their own structure while API Owners maintain control over API access. + +Remember that users can belong to multiple teams, gaining access to all API Catalogs assigned to any of their teams. + +### Default Team + +Each Organisation has a system-generated Default Team that serves several important purposes: + +- Provides an initial home for new users in the Organisation +- Provides a home for API Consumer users who have been [removed](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#deleting-organisations) from all other Teams in the Org +- Can be used for Organisation-wide API access + +The Default Team cannot be deleted, however you can: + +- Rename it to better reflect your business needs +- Move users from it to other Teams as needed +- Use it as a holding area for users awaiting proper Team assignment + +#### Developer App visibility + +[Team level app visibility](/portal/developer-app#visibility) is not applied within the Default Team. This behavior has been implemented to prevent accidental exposure of Developer Apps if a user is removed from a team and automatically reverts to the Default Team. + +
+ + +We do not recommend using the Default Team for consumption of API Products and Plans except for Organisation-wide API access. + + + +### Best Practices for Team Management + +- **Logical grouping**: Create teams based on project needs or functional areas +- **Minimal access**: Assign only the APIs each team needs to function +- **Regular audits**: Periodically review team membership and API access +- **Descriptive naming**: Use clear, consistent naming conventions for teams +- **Documentation**: Maintain records of each team's purpose and required access + + +## Requesting a New Organisation + +The Developer Portal allows potential API Consumers to request the creation of a new Organisation during self-registration. This powerful feature balances self-service convenience with administrative control, addressing several key business needs: + +- When running an open API program that welcomes new business partners +- When scaling your API ecosystem to reach more companies without proportionally increasing administrative work +- When you want to capture interest from potential partners outside normal business hours +- When you need clear differentiation between individual developers and those representing companies + +The Organisation request feature adds value with: + +- Accelerated Onboarding: Reduces the time from initial interest to active API usage by eliminating manual Organisation creation steps +- Business Intelligence: Provides visibility into which companies are interested in your APIs, creating potential partnership opportunities +- Improved User Experience: Allows users to properly identify themselves as representing a company from the start +- Proper Governance: Maintains security through approval workflows while enabling self-service + +This self-service approach reduces administrative overhead while ensuring proper governance of your API ecosystem. It's particularly valuable for open API programs or when expanding your API consumer base. + +### Requesting a new Organisation + +1. Visit the Developer Portal and [register] without an Invite Code +2. Log in to the Developer Portal (this can be done without the account having been approved) +3. Select **Create an Organisation** + Request a new Organisation +4. Provide the requested Org with a **Name** + Specify name of the Organisation +5. Select **Create Organisation** +6. The user receives confirmation that their request is pending review + Organisation registration is pending +7. Note that if the Developer Portal settings are configured for [automatic approval](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#configuring-organisation-request-settings) of Organisation Requests without API Owner [review](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#reviewing-organisation-requests) then the Organisation will be created immediately and the requestor approved and converted to an API Consumer Admin within the new Org. + Organisation registration is approved + +### Reviewing Organisation Requests + +1. If [automatic approval](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-api-consumer-organisations#configuring-organisation-request-settings) of Organisation Requests is not set, the API Owner users will be notified of Organisation request via email. + New Organisation registration request notification +2. Navigate to **API Consumers > Organisations** in the Admin Portal +2. The requested Organisation appears as *pending* in the list +3. Select the pending Organisation to see which user made the request +4. After reviewing the request, an API Owner can use the options in the three dot menu to: + - Approve the request, activating the new Organisation with the requestor automatically becoming an API Consumer Admin + - Reject the request, with the requestor remaining a Team Member + New Organisation registration request view +5. The requestor will receive an email notifying them of the approval or rejection of the request. + +**Note**: The API Owner can modify the name of the new Org during the review, if required. + +The content of the emails sent to API Owners and API Consumers can be [customized](/portal/customization/email-notifications) to meet your business needs. + +### Configuring Organisation Request Settings + +Control whether and how users can request new Organisations by configuring the Developer Portal settings: + +1. Navigate to **Settings > General > API Consumer access** in the Admin Portal + Organisation registration settings +2. Check or clear the options: + - **Enable API consumers to register Organisations** + - **Auto-approve API consumers registering organisation** +4. Select **Save changes** + +Note that enabling auto-approval will mean there is no opportunity to review Org requests, so should only be used in carefully controlled business environments. + + +## Use Cases and Implementation Strategies + +The Organisation and Team structure in Tyk Developer Portal can be adapted to support various business models and API programs. Here are strategic approaches for common scenarios: + +### Enterprise Partner Ecosystem + +**Scenario**: Managing APIs for a network of business partners with different access needs + +**Implementation**: + - Create an Organisation for each partner company + - Structure teams based on partner's functional departments (e.g., Development, QA, Analytics) + - Assign graduated API access tiers based on partnership level + - Designate partner technical leads as API Consumer Admins + +**Benefits**: + - Clear separation between different partner companies + - Partners can self-manage their internal team structure + - Access revocation is simplified when partnerships change + - Usage analytics can be tracked at the partner company level + +### Internal Developer Program + +**Scenario**: Providing API access across departments within your own company + +**Implementation**: +- Create Organisations representing major business units or subsidiaries +- Form teams based on projects, product lines, or functional groups +- Use the Default Organisation for central IT or platform teams +- Implement consistent naming conventions that align with internal structure + +**Benefits**: +- Mirrors existing company hierarchy for easier governance +- Supports chargeback models for internal API consumption +- Enables department-specific policies and quotas +- Provides visibility into cross-departmental API usage + +### Public API Marketplace + +**Scenario**: Offering APIs to external developers with tiered access models + +**Implementation**: +- Enable Organisation self-registration requests +- Create template teams for common access patterns (Basic, Professional, Enterprise) +- Implement automated workflows for upgrading access tiers +- Use Default Teams for individual developers without complex needs + +**Benefits**: +- Scales efficiently as your developer community grows +- Supports freemium to premium conversion paths +- Allows companies to start small and expand access as needed +- Provides clear separation between individual developers and companies + +### Implementation Checklist + +Regardless of your use case, consider these factors when designing your Organisation and Team structure: +- **Scalability**: Will the structure accommodate growth in users and APIs? +- **Governance**: Does it support your compliance and security requirements? +- **Administration**: Is the overhead manageable for your API team? +- **User Experience**: Does it make sense from the API Consumer perspective? +- **Analytics**: Will you get the usage insights needed for your business? +- **Flexibility**: Can it adapt as your API program evolves? + +By thoughtfully designing your Organisation and Team structure to match your specific business needs, you can create an API program that balances security, usability, and administrative efficiency. diff --git a/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-catalogues.mdx b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-catalogues.mdx new file mode 100644 index 0000000000..9097d541c9 --- /dev/null +++ b/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-catalogues.mdx @@ -0,0 +1,122 @@ +--- +title: "API Catalogs" +description: "Working with API Catalogs" +keywords: "Developer Portal, Tyk, Managing Access, Catalogs" +sidebarTitle: "API Catalogs" +--- + +## Introduction + +API Catalogs are curated collections of API Products and Plans that enable you to organize and present your API offerings to different developer audiences. Catalogs serve as the primary navigation and discovery mechanism in the Tyk Developer Portal, allowing you to create tailored API marketplaces for different consumer segments. + +Unlike traditional API documentation sites that present all APIs to everyone, Catalogs give you fine-grained control over who sees what. This enables you to create personalized experiences for different developer audiences - from public APIs available to anyone, to specialized offerings for specific partners or internal teams. + +Catalogs transform your API portfolio management by: + +- Segmenting API Products for different developer audiences +- Creating customized discovery experiences for different use cases +- Controlling visibility of API offerings based on business relationships +- Enabling consistent organization of related API Products + +In the Tyk Developer Portal, Catalogs act as the bridge between your API Products and your developer community, ensuring that each developer sees exactly the APIs they need. + +## Key Concepts + +### Catalog Types + +The Tyk Developer Portal supports two visibility modes for Catalogs: + +- Public Catalogs: Visible to anyone visiting your Developer Portal, even without logging in. Ideal for openly available APIs and developer recruitment. +- Private Catalogs: Visible only to authenticated users who have logged into your Developer Portal. They can be further restricted only to members of specific [teams](/portal/api-consumer). Perfect for partner-specific APIs, internal teams, or premium offerings. + +### Catalog Structure + +Each Catalog contains: + +- [API Products](/portal/api-products): The functional API offerings available in this Catalog +- [Plans](/portal/api-plans): The subscription options available for Products in this Catalog +- Visibility Settings: Controls which developers can see this Catalog +- Presentation Elements: Name, description, and other display properties + +### Catalog Relationships + +Understanding how Catalogs relate to other elements in the Developer Portal: + +- Products and Plans: A Product or Plan can appear in multiple Catalogs +- Teams and Organisations: Can be granted access to specific Custom Catalogs +- Developer Experience: Developers only see Catalogs they have access to + +## API Catalog Reference Guide + +This comprehensive reference guide details all the configurable options and features of API Catalogs in the Tyk Developer Portal. + +### Core Features + +#### Catalog Name + +The primary identifier for your Catalog within the Admin Portal, this is not exposed in the Live Portal + +- **Location**: *Catalogues > Add/Edit Catalogues > Name* +- **Purpose**: Identifies the Catalog within the Developer Portal +- **Best Practice**: Choose a clear, descriptive name that reflects the Catalog's purpose or audience + +#### Path URL + +This configuration is not currently in use and can be ignored. + +#### Sync URL with Name + +- **Location**: *Catalogues > Add/Edit Catalogues > Sync URL with Name* +- **Note**: This configuration must be checked (selected). + +### Catalog Visibility + +#### Visibility Options + +Controls which API Consumers can see and access this Catalog. + +- **Location**: *Catalogues > Add/Edit Catalogues > Visibility options* +- **Options**: + - Public: Visible to all visitors, even without logging in + - Private: Visible only to authenticated users in the teams select in the [Audience](/tyk-stack/tyk-developer-portal/enterprise-developer-portal/managing-access/manage-catalogues#audience) +- **Default**: Private +- **Best Practice**: Use the most restrictive visibility that meets your business needs + +#### Audience + +Specifies which teams can access a Private Catalog. + +- **Location**: *Catalogues > Add/Edit Catalogues > Team* +- **Selection**: Select **Add Team** then choose from any Teams created on the Developer Portal; you can add multiple teams by repeating this action +- **Behavior**: Only members of the selected teams will see this Catalog +- **Note**: Teams must be created before they can be added to the audience; any combination of Teams can be added to a Catalog's audience across any number of Organisations + +### Catalog Content + +#### Products + +Determines which API Products appear in this Catalog. + +- **Location**: *Catalogues > Add/Edit Catalogues > Products* +- **Selection**: Select one or more Products from the dropdown +- **Removal**: Click on the `x` next to the name of the Product you want to delete from the Catalog +- **Relationship**: A Product can be assigned to multiple Catalogs +- **Best Practice**: Ensure that Products and their relevant Plans are assigned to the same Catalogs + +#### Plans + +Determines which API Plans appear in this Catalog. + +- **Location**: *Catalogues > Add/Edit Catalogues > Plans* +- **Selection**: Select one or more Plans from the dropdown +- **Removal**: Click on the `x` next to the name of the Plan you want to delete from the Catalog +- **Relationship**: A Plan can be assigned to multiple Catalogs +- **Best Practice**: Ensure that Products and their relevant Plans are assigned to the same Catalogs + +## Best Practices for API Catalogs + +- Create purpose-driven Catalogs: Design each Catalog with a specific audience and purpose in mind +- Use clear naming conventions: Make Catalog names intuitive and descriptive +- Maintain consistent organization: Apply similar structures across Catalogs for a predictable developer experience +- Limit the number of Catalogs: Too many Catalogs can create confusion; aim for a manageable number +- Review access regularly: Periodically audit Custom Catalog access to ensure it remains appropriate diff --git a/tyk-stack/tyk-gateway/important-prerequisites.mdx b/tyk-stack/tyk-gateway/important-prerequisites.mdx new file mode 100644 index 0000000000..352438ceea --- /dev/null +++ b/tyk-stack/tyk-gateway/important-prerequisites.mdx @@ -0,0 +1,55 @@ +--- +title: "Useful Configurations when Getting started" +description: "Important prerequisites and configurations needed before proceeding with Tyk tutorials." +sidebarTitle: "Useful Configurations" +--- + +These are some common settings that you need before proceeding with other parts of our tutorials. + +## Tyk Config + +### Path to Tyk API Definitions configurations directory + +You may need to explicitly define the path in your Tyk config to the directory where you will add +the API definitions for Tyk to serve. + +```yaml +... +"app_path": "/opt/tyk-gateway/apps", +... +``` + +### Path to Policies file + +You need to explicitly set the path to your Policies JSON file in your Tyk config. + +```yaml +... + "policies": { + "policy_source": "file", + "policy_record_name": "policies/policies.json" + }, +... +``` + +### Remove Tyk Dashboard related config options + +Some config options for the Community Edition are not compatible with the Dashboard +version, which requires a license. So, **remove** any section in your Tyk config which +starts with: + +```yaml +... +"db_app_conf_options" { + ... +}, +... +``` + +## Hot reload is critical in Tyk CE + +Each time you add an API definition in Tyk CE, you need to make a hot reload API call as follows: + +```curl +curl -H "x-tyk-authorization: {your-secret}" -s https://{your-tyk-host}:{port}/tyk/reload/group | python -mjson.tool +``` diff --git a/tyk-stack/tyk-operator/create-an-api.mdx b/tyk-stack/tyk-operator/create-an-api.mdx new file mode 100644 index 0000000000..3015333e14 --- /dev/null +++ b/tyk-stack/tyk-operator/create-an-api.mdx @@ -0,0 +1,2048 @@ +--- +title: "Create and Secure an API with Tyk Operator" +description: "Learn how to create an API using Tyk Operator in Kubernetes" +keywords: "Tyk Operator, Kubernetes, API Management" +sidebarTitle: "Create an API" +--- + +## Introduction + +Tyk Operator allows you to manage your Tyk APIs, policies, and other configurations using Kubernetes Custom Resource Definitions (CRDs). This page will help you create an API using Tyk Operator. + +## Set Up Tyk OAS API +Setting up OpenAPI Specification (OAS) APIs with Tyk involves preparing an OAS-compliant API definition and configuring it within your Kubernetes cluster using Tyk Operator. This process allows you to streamline API management by storing the OAS definition in a Kubernetes ConfigMap and linking it to Tyk Gateway through a TykOasApiDefinition resource. + +### Create your Tyk OAS API +#### Prepare the Tyk OAS API Definition +First, you need to have a complete Tyk OAS API definition file ready. This file will contain all the necessary configuration details for your API in OpenAPI Specification (OAS) format. + +Here is an example of what the Tyk OAS API definition might look like. Note that Tyk extension `x-tyk-api-gateway` section should be present. + +```json {hl_lines=["9-25"],linenos=true} +{ + "info": { + "title": "Petstore", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Petstore", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore.swagger.io/v2" + }, + "server": { + "listenPath": { + "value": "/petstore/", + "strip": true + } + } + } +} +``` + +Save this API definition file (e.g., `oas-api-definition.json`) locally. + + + +**Tips** + +You can create and configure your API easily using Tyk Dashboard in a developer environment, and then obtain the Tyk OAS API definition following these instructions: + +1. Open the Tyk Dashboard +2. Navigate to the API you want to manage with the Tyk Operator +3. Click on the "Actions" menu button and select "View API Definition." +4. This will display the raw Tyk OAS API definition of your API, which you can then copy and save locally. + + + +#### Create a ConfigMap for the Tyk OAS API Definition + +You need to create a [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/#configmap-object) in Kubernetes to store your Tyk OAS API definition. The Tyk Operator will reference this ConfigMap to retrieve the API configuration. + +To create the ConfigMap, run the following command: + +```sh +kubectl create configmap tyk-oas-api-config --from-file=oas-api-definition.json -n tyk +``` + +This command creates a ConfigMap named `tyk-oas-api-config` in the `tyk` namespace (replace `tyk` with your actual namespace if different). + + + +**Notes** + +There is inherent size limit to a ConfigMap. The data stored in a ConfigMap cannot exceed 1 MiB. In case your OpenAPI document exceeds this size limit, it is recommended to split your API into smaller sub-APIs for easy management. For details, please consult [Best Practices for Describing Large APIs](https://learn.openapis.org/best-practices.html#describing-large-apis) from the OpenAPI initiative. + + + + + +**Notes** + +If you prefer to create ConfigMap with a manifest using `kubectl apply` command, you may get an error that the annotation metadata cannot exceed 256KB. It is because by using `kubectl apply`, `kubectl` automatically saves the whole configuration in the annotation [kubectl.kubernetes.io/last-applied-configuration](https://kubernetes.io/docs/reference/labels-annotations-taints/#kubectl-kubernetes-io-last-applied-configuration) for tracking changes. Your Tyk OAS API Definition may easily exceed the size limit of annotations (256KB). Therefore, `kubectl create` is used here to get around the problem. + + + +#### Create a TykOasApiDefinition Custom Resource + +Now, create a `TykOasApiDefinition` resource to tell the Tyk Operator to use the Tyk OAS API definition stored in the ConfigMap. + +Create a manifest file named `tyk-oas-api-definition.yaml` with the following content: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: petstore +spec: + tykOAS: + configmapRef: + name: tyk-oas-api-config # Metadata name of the ConfigMap resource that stores the Tyk OAS API Definition + namespace: tyk # Metadata namespace of the ConfigMap resource + keyName: oas-api-definition.json # Key for retrieving Tyk OAS API Definition from the ConfigMap +``` + +#### Apply the TykOasApiDefinition Manifest + +Use `kubectl` to apply the `TykOasApiDefinition` manifest to your cluster: + +```sh +kubectl apply -f tyk-oas-api-definition.yaml +``` + +This command creates a new `TykOasApiDefinition` resource in your cluster. The Tyk Operator will watch for this resource and configures Tyk Gateway or Tyk Dashboard with a new API using the provided Tyk OAS API definition. + +#### Verify the Tyk OAS API Creation + +To verify that the API has been successfully created, check the status of the TykOasApiDefinition resource: + +```sh +kubectl get tykoasapidefinition petstore +``` + +You should see the status of the resource, which will indicate if the API creation was successful. + +```bash +NAME DOMAIN LISTENPATH PROXY.TARGETURL ENABLED SYNCSTATUS INGRESSTEMPLATE +petstore /petstore/ https://petstore.swagger.io/v2 true Successful +``` + +#### Test the Tyk OAS API +After the Tyk OAS API has been successfully created, you can test it by sending a request to the API endpoint defined in your OAS file. + +For example, if your API endpoint is `/store/inventory"`, you can use `curl` or any API client to test it: + +```sh +curl "TYK_GATEWAY_URL/petstore/store/inventory" +``` + +Replace TYK_GATEWAY_URL with a URL of Tyk Gateway. + +#### Manage and Update the Tyk OAS API +To make any changes to your API configuration, update the OAS file in your ConfigMap and then re-apply the ConfigMap using `kubectl replace`: + +```sh +kubectl create configmap tyk-oas-api-config --from-file=oas-api-definition.json -n tyk --dry-run=client -o yaml | kubectl replace -f - +``` + +The Tyk Operator will automatically detect the change and update the API in the Tyk Gateway. + + + +**Notes** + +`kubectl replace` without `--save-config` option is used here instead of `kubectl apply` because we do not want to save the Tyk OAS API definition in its annotation. If you want to enable `--save-config` option or use `kubectl apply`, the Tyk OAS API definition size would be further limited to at most 262144 bytes. + + + +#### Tyk OAS API Example +This example shows the minimum resources and fields required to define a Tyk OAS API using Tyk Operator. + +```yaml{hl_lines=["7-7", "41-44"],linenos=true} +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm + namespace: default +data: + test_oas.json: |- + { + "info": { + "title": "Petstore", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": {}, + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Petstore", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore.swagger.io/v2" + }, + "server": { + "listenPath": { + "value": "/petstore/", + "strip": true + } + } + } + } +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: petstore +spec: + tykOAS: + configmapRef: + name: cm + namespace: default + keyName: test_oas.json +``` + +Here, a `ConfigMap` is created that contains the Tyk OAS API Definition with the `data` field with key `test_oas.json`. This is linked to from a `TykOasApiDefinition` resource via `spec.tykOAS.configmapRef`. + +To apply it, simply save the manifest into a file (e.g., `tyk-oas-api.yaml`) and use `kubectl apply -f tyk-oas-api.yaml` to create the required resources in your Kubernetes cluster. This command will create the necessary ConfigMap and TykOasApiDefinition resources in the `default` namespace. + + + +### Secure your Tyk OAS API +#### Update your Tyk OAS API Definition + +First, you'll modify your existing Tyk OAS API Definition to include the API key authentication configuration. + +When creating the Tyk OAS API, you stored your OAS definition in a file named `oas-api-definition.json` and created a ConfigMap named `tyk-oas-api-config` in the `tyk` namespace. + +Modify your Tyk OAS API Definition `oas-api-definition.json` as follow. + +```json {hl_lines=["8-14","16-20","33-40"],linenos=true} +{ + "info": { + "title": "Petstore protected", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "components": { + "securitySchemes": { + "petstore_auth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } + }, + "security": [ + { + "petstore_auth": [] + } + ], + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Petstore", + "state": { + "active": true + } + }, + "upstream": { + "url": "https://petstore.swagger.io/v2" + }, + "server": { + "authentication": { + "enabled": true, + "securitySchemes": { + "petstore_auth": { + "enabled": true + } + } + }, + "listenPath": { + "value": "/petstore/", + "strip": true + } + } + } +} +``` + +In this example, we added the following sections to configure key authentication for this API. + +- `components.securitySchemes` defines the authentication method (in this case, `apiKey` in the header). +- `security`: Applies the authentication globally to all endpoints. +- `x-tyk-api-gateway.server.authentication`: Tyk-specific extension to enable the authentication scheme. + +You can configure your API for any Tyk supported authentication method by following the [Client Authentication](/api-management/client-authentication) documentation. + +Save your updated API definition in the same file, `oas-api-definition.json`. + +#### Update the ConfigMap with the new Tyk OAS API Definition + +Update the existing ConfigMap that contains your Tyk OAS API Definition with the following command: + +```sh +kubectl create configmap tyk-oas-api-config --from-file=oas-api-definition.json -n tyk --dry-run=client -o yaml | kubectl replace -f - +``` + +This command updates the existing ConfigMap named `tyk-oas-api-config` in the `tyk` namespace (replace `tyk` with your actual namespace if different) with the new Tyk OAS API Definition stored in `oas-api-definition.json`. + +Since a `TykOasApiDefinition` resource has been created with reference to this ConfigMap in the previous tutorial: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykOasApiDefinition +metadata: + name: petstore +spec: + tykOAS: + configmapRef: + name: tyk-oas-api-config # Metadata name of the ConfigMap resource that stores the Tyk OAS API Definition + namespace: tyk # Metadata namespace of the ConfigMap resource + keyName: oas-api-definition.json # Key for retrieving Tyk OAS API Definition from the ConfigMap +``` + +Any changes in the ConfigMap would be detected by Tyk Operator. Tyk Operator will then automatically reconcile the changes and update the API configuration at Tyk. + +#### Verify the changes + +Verify that the `TykOasApiDefinition` has been updated successfully: + +```sh +kubectl get tykoasapidefinition petstore -o yaml +``` + +Look for the `latestTransaction` field in `status`: + +```yaml +status: + latestTransaction: + status: Successful + time: "2024-09-16T11:48:20Z" +``` + +The **Successful** status shows that Tyk Operator has reconciled the API with Tyk successfully. The last update time is shown in the `time` field. + +#### Test the API Endpoint +Now, test your API endpoint to confirm that it requires an API key. + +For example, if your API endpoint is `/store/inventory"`, you can use `curl` or any API client to test it: + +```sh +curl -v "TYK_GATEWAY_URL/petstore/store/inventory" +``` + +Replace TYK_GATEWAY_URL with a URL of Tyk Gateway. + +Request should fail with a `401 Unauthorized` response now as an API key is required for access. Your API has been secured by Tyk Gateway. + +## Set Up Tyk Classic API + +### Create a Tyk Classic API +First, specify the details of your API using the [ApiDefinition CRD](/api-management/automations/operator#apidefinition-crd), then deploy it to create the corresponding Kubernetes resource. Tyk Operator will take control of the CRD and create the actual API in the Tyk data plane. + +#### Create an ApiDefinition resource in YAML format +Create a file called `httpbin.yaml`, then add the following: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +You can also use other sample files from the following pages: + +- [HTTP Proxy example](/tyk-stack/tyk-operator/create-an-api#set-up-manifest-for-http) +- [TCP Proxy example](#set-up-manifest-for-tcp) +- [GraphQL Proxy example](#set-up-manifest-for-graphql) +- [UDG example](#set-up-manifest-for-udg) + +#### Deploy the ApiDefinition resource +We are going to create an ApiDefinition from the httpbin.yaml file, by running the following command: + +```console +$ kubectl apply -f httpbin.yaml +``` + +Or, if you don’t have the manifest with you, you can run the following command: + +```yaml +cat <.`.svc.cluster.local DNS entry once they are created. +For example, if you have a service called `httpbin` in `default` namespace, you can contact `httpbin` service with `httpbin.default.svc` DNS record in the cluster, instead of IP addresses. +Please visit the official [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) for more details. +Suppose you want to create a Deployment of [httpbin](https://hub.docker.com/r/kennethreitz/httpbin/) service using [ci/upstreams/httpbin.yaml](https://github.com/TykTechnologies/tyk-operator/blob/master/ci/upstreams/httpbin.yaml) file. You are going to expose the application through port `8000` as described under the Service [specification](https://github.com/TykTechnologies/tyk-operator/blob/master/ci/upstreams/httpbin.yaml#L10). +You can create Service and Deployment by either applying the manifest defined in our repository: + +```console +$ kubectl apply -f ci/upstreams/httpbin.yaml +``` + +Or, if you don’t have the manifest with you, you can run the following command: + +```yaml +cat <` namespace as follows: + +```console +$ kubectl get service -n +``` + +You can update your `httpbin` as follows: + +```yaml +cat <..svc:`). +Now, if you send your request to the `/httpbin` endpoint of the Tyk Gateway, the request will be proxied to the `httpbin Service`: + +```curl +curl -sS http://localhost:8080/httpbin/headers +{ + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Host": "httpbin.default.svc:8000", + "User-Agent": "curl/7.68.0" + } +} +``` + +As you can see from the response, the host that your request should be proxied to is `httpbin.default.svc:8000`. + +### Secure your Classic API +#### Update your API to Require a Key + +You might already have realized that our `httpbin` API is keyless. If you check the APIDefinition's specification, the `use_keyless` field is set to `true`. +Tyk keyless access represents completely open access for your API and causes Tyk to bypass any session-based middleware (middleware that requires access to token-related metadata). Keyless access will enable all requests through. +You can disable keyless access by setting `use_keyless` to false. + +1. Update your `httpbin.yaml` file + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: false + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +2. Apply the changes + +```bash +kubectl apply -f httpbin.yaml +``` + +Or, if you don’t have the manifest with you, you can run the following command: + +```yaml +cat < +Tyk Operator supported authentication types are listed in the [API Definition features](/api-management/automations/operator#apidefinition-crd) section. + + + +#### Create an API key + +You need to generate a key to access the `httpbin` API now. Follow [this guide](/getting-started/configure-first-api#create-an-api-key) to see how to create an API key for your installation. + +You can obtain the API name and API ID of our example `httpbin` API by following command: + +```yaml +kubectl describe tykapis httpbin +Name: httpbin +Namespace: default +Labels: +Annotations: +API Version: tyk.tyk.io/v1alpha1 +Kind: ApiDefinition +Metadata: + ... +Spec: + ... + Name: httpbin + ... +Status: + api_id: ZGVmYXVsdC9odHRwYmlu +Events: +``` + +You can obtain the API name and API ID from `name` and `status.api_id` field. + +In our example, it is as follows: + +- API-NAME: httpbin +- API-ID: ZGVmYXVsdC9odHRwYmlu + +When you have successfully created a key, you can use it to access the `httpbin` API. + +```curl +curl -H "Authorization: Bearer {Key ID}" localhost:8080/httpbin/get +{ + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Authorization": "Bearer {Key ID}", + "Host": "httpbin.org", + "User-Agent": "curl/7.77.0", + "X-Amzn-Trace-Id": "Root=1-6221de2a-01aa10dd56f6f13f420ba313" + }, + "origin": "127.0.0.1, 176.42.143.200", + "url": "http://httpbin.org/get" +} +``` +Since you have provided a valid key along with your request, you do not get a `HTTP 401 Unauthorized` response. + + +### Set Up Tyk Classic API Authentication +Client to Gateway Authentication in Tyk ensures secure communication between clients and the Tyk Gateway. Tyk supports various authentication methods to authenticate and authorize clients before they can access your APIs. These methods include API keys, Static Bearer Tokens, JWT, mTLS, Basic Authentication, and more. This document provides example manifests for each authentication method supported by Tyk. + +#### Keyless (Open) + +This configuration allows [keyless (open)](/basic-config-and-security/security/authentication-authorization/open-keyless) access to the API without any authentication. + +```yaml {hl_lines=["7-7"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-keyless +spec: + name: httpbin-keyless + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +#### Auth Token + +This setup requires an [auth token](/api-management/authentication/bearer-token) for access. + +In the below example, the authentication token is set by default to the `Authorization` header of the request. You can customize this behavior by configuring the following fields: + +- `use_cookie`: Set to true to use a cookie value for the token. +- `cookie_name`: Specify the name of the cookie if use_cookie is enabled. +- `use_param`: Set to true to allow the token to be passed as a query parameter. +- `param_name`: Specify the parameter name if use_param is enabled. +- `use_certificate`: Enable client certificate. This allows you to create dynamic keys based on certificates. +- `validate_signature`: Enable [signature validation](/api-management/authentication/bearer-token#auth-token-with-signature). + +```yaml {hl_lines=["13-35"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-auth-token +spec: + name: httpbin-auth-token + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + use_standard_auth: true + auth_configs: + authToken: + # Auth Key Header Name + auth_header_name: Authorization + # Use cookie value + use_cookie: false + # Cookie name + cookie_name: "" + # Allow query parameter as well as header + use_param: false + # Parameter name + param_name: "" + # Enable client certificate + use_certificate: false + # Enable Signature validation + validate_signature: false + signature: + algorithm: "" + header: "" + secret: "" + allowed_clock_skew: 0 + error_code: 0 +``` + +#### JWT + +This configuration uses [JWT tokens](/basic-config-and-security/security/authentication-authorization/json-web-tokens) for authentication. + +Users can configure JWT authentication by defining the following fields: + +- `jwt_signing_method`: Specify the method used to sign the JWT. Refer to the documentation on [JWT Signatures](/basic-config-and-security/security/authentication-authorization/json-web-tokens) for supported methods. +- `jwt_source`: Specify the public key used for verifying the JWT. +- `jwt_identity_base_field`: Define the identity source, typically set to `sub` (subject), which uniquely identifies the user or entity. +- `jwt_policy_field_name`: Specify the claim within the JWT payload that indicates the policy ID to apply. +- `jwt_default_policies` (Optional): Define default policies to apply if no policy claim is found in the JWT payload. + +The following example configures an API to use JWT authentication. It specifies the ECDSA signing method and public key, sets the `sub` claim as the identity source, uses the `pol` claim for policy ID, and assigns a default policy (`jwt-policy` SecurityPolicy in `default` namespace) if no policy is specified in the token. + +```yaml {hl_lines=["13-22"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-jwt1 +spec: + name: httpbin-jwt1 + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin-jwt1 + strip_listen_path: true + enable_jwt: true + strip_auth_data: true + jwt_signing_method: ecdsa + # ecdsa pvt: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ2V2WnpMMWdkQUZyODhoYjIKT0YvMk54QXBKQ3pHQ0VEZGZTcDZWUU8zMGh5aFJBTkNBQVFSV3oram42NUJ0T012ZHlIS2N2akJlQlNEWkgycgoxUlR3am1ZU2k5Ui96cEJudVE0RWlNbkNxZk1QV2lacUI0UWRiQWQwRTdvSDUwVnB1WjFQMDg3RwotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t + # ecdsa pub: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRVZzL281K3VRYlRqTDNjaHluTDR3WGdVZzJSOQpxOVVVOEk1bUVvdlVmODZRWjdrT0JJakp3cW56RDFvbWFnZUVIV3dIZEJPNkIrZEZhYm1kVDlQT3hnPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t + jwt_source: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRVZzL281K3VRYlRqTDNjaHluTDR3WGdVZzJSOQpxOVVVOEk1bUVvdlVmODZRWjdrT0JJakp3cW56RDFvbWFnZUVIV3dIZEJPNkIrZEZhYm1kVDlQT3hnPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t + jwt_identity_base_field: sub + jwt_policy_field_name: pol + jwt_default_policies: + - default/jwt-policy +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: jwt-policy +spec: + access_rights_array: + - name: httpbin-jwt1 + namespace: default + versions: + - Default + active: true + name: jwt-policy + state: active +``` + +You can verify the API is properly authenticated with following command: + +1. JWT with default policy +```bash +curl http://localhost:8080/httpbin-jwt1/get -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNTE2MjM5MDIyfQ.rgPyrCJYs2im7zG6im5XUqsf_oAf_Kqk-F6IlLb3yzZCSZvrQObhBnkLKgfmVTbhQ5El7Q6KskXPal5-eZFuTQ' +{ + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Host": "httpbin.org", + "Traceparent": "00-d2b93d763ca27f29181c8e508b5ac0c9-a446afa3bd053617-01", + "User-Agent": "curl/8.6.0", + "X-Amzn-Trace-Id": "Root=1-6696f0bf-1d9e532c6a2eb3a709e7086b" + }, + "origin": "127.0.0.1, 178.128.43.98", + "url": "http://httpbin.org/get" +} +``` + +2. JWT with explicit policy +```bash +curl http://localhost:8080/httpbin-jwt1/get -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNTE2MjM5MDIyLCJwb2wiOiJaR1ZtWVhWc2RDOXFkM1F0Y0c5c2FXTjUifQ.7nY9TvYgsAZqIHLhJdUPqZtzqU_5T-dcNtCt4zt8YPyUj893Z_NopL6Q8PlF8TlMdxUq1Ff8rt4-p8gVboIqlA' +{ + "args": {}, + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip", + "Host": "httpbin.org", + "Traceparent": "00-002adf6632ec20377cb7ccf6c3037e78-3c4cb97c70d790cb-01", + "User-Agent": "curl/8.6.0", + "X-Amzn-Trace-Id": "Root=1-6696f1dd-7f9de5f947c8c73279f7cca6" + }, + "origin": "127.0.0.1, 178.128.43.98", + "url": "http://httpbin.org/get" +} +``` + +#### Basic Authentication + +This configuration uses [Basic Authentication](/api-management/authentication/basic-authentication), requiring a username and password for access. + +```yaml {hl_lines=["13-13"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-basic-auth +spec: + name: Httpbin Basic Authentication + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + use_basic_auth: true +``` + +#### Custom Plugin Auth (go) + +This configuration uses a [Golang plugin](/api-management/plugins/golang#) for custom authentication. The following example shows how to create an API definition with a Golang custom plugin for `httpbin-go-auth`. + +For an example of Golang authentication middleware, see [Performing custom authentication with a Golang plugin](/api-management/plugins/golang#performing-custom-authentication-with-a-golang-plugin). + +```yaml {hl_lines=["7-7", "14-21"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-go-auth +spec: + name: httpbin-go-auth + use_go_plugin_auth: true # Turn on GO auth + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + custom_middleware: + driver: goplugin + pre: + - name: "AddFooBarHeader" + path: "/mnt/tyk-gateway/example-go-plugin.so" + auth_check: + name: "MyPluginCustomAuthCheck" + path: "/mnt/tyk-gateway/example-go-plugin.so" +``` + +#### Custom Plugin Auth (gRPC) + +This configuration uses a [gRPC plugin](/api-management/plugins/golang#) for custom authentication. The following example shows how to create an API definition with a gRPC custom plugin for `httpbin-grpc-auth`. + +For a detailed walkthrough on setting up Tyk with gRPC authentication plugins, refer to [Extending Tyk with gRPC Authentication Plugins](https://tyk.io/blog/how-to-setup-custom-authentication-middleware-using-grpc-and-java/). + +```yaml {hl_lines=["9-9", "14-26"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-grpc-auth +spec: + name: httpbin-grpc-auth + protocol: http + active: true + enable_coprocess_auth: true + proxy: + target_url: http://httpbin.default.svc:8000 + listen_path: /httpbin-grpc-auth + strip_listen_path: true + custom_middleware: + driver: grpc + post_key_auth: + - name: "HelloFromPostKeyAuth" + path: "" + auth_check: + name: foo + path: "" + id_extractor: + extract_from: header + extract_with: value + extractor_config: + header_name: Authorization +``` + +#### Multiple (Chained) Auth + +This setup allows for [multiple authentication](/basic-config-and-security/security/authentication-authorization/multiple-auth) methods to be chained together, requiring clients to pass through each specified authentication provider. + +To enable multiple (chained) auth, you should set `base_identity_provided_by` field to one of the supported chained enums. Consult the [Multi (Chained) Authentication](/basic-config-and-security/security/authentication-authorization/multiple-auth) section for the supported auths. + +In this example, we are creating an API definition with basic authentication and mTLS with basic authentication as base identity for `httpbin-multiple-authentications`. + +```yaml {hl_lines=["19-21"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-multiple-authentications +spec: + name: Httpbin Multiple Authentications + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + base_identity_provided_by: basic_auth_user + use_basic_auth: true + use_mutual_tls_auth: true +``` + +#### IP Allowlist + +To enable [IP Allowlist](/api-management/gateway-config-tyk-classic#ip-access-control), set the following fields: + +* `enable_ip_whitelisting`: Enables IPs allowlist. When set to `true`, only requests coming from the explicit list of IP addresses defined in (`allowed_ips`) are allowed through. +* `allowed_ips`: A list of strings that defines the IP addresses (in [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation) notation) that are allowed access via Tyk. + +In this example, only requests coming from 127.0.0.2 is allowed. + +```yaml {hl_lines=["10-12"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + enable_ip_whitelisting: true + allowed_ips: + - 127.0.0.2 + proxy: + target_url: http://httpbin.default.svc:8000 + listen_path: /httpbin + strip_listen_path: true +``` + +#### IP Blocklist + +To enable [IP Blocklist](/api-management/gateway-config-tyk-classic#ip-access-control), set the following fields: + +* `enable_ip_blacklisting`: Enables IPs blocklist. If set to `true`, requests coming from the explicit list of IP addresses (blacklisted_ips) are not allowed through. +* `blacklisted_ips`: A list of strings that defines the IP addresses (in [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation) notation) that are blocked access via Tyk. This list is explicit and wildcards are currently not supported. + +In this example, requests coming from 127.0.0.2 will be forbidden (`403`). + +```yaml {hl_lines=["10-12"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + enable_ip_blacklisting: true + blacklisted_ips: + - 127.0.0.2 + proxy: + target_url: http://httpbin.default.svc:8000 + listen_path: /httpbin + strip_listen_path: true +``` + + +### Set Up Manifest for GraphQL +In the example below we can see that the configuration is contained within the `graphql` configuration object. A GraphQL schema is specified within the `schema` field and the execution mode is set to `proxyOnly`. The [GraphQL public playground](/api-management/graphql#enabling-public-graphql-playground) is enabled with the path set to `/playground`. + +```yaml {hl_lines=["15-17", "18-92"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: trevorblades +spec: + name: trevorblades + use_keyless: true + protocol: http + active: true + proxy: + target_url: https://countries.trevorblades.com + listen_path: /trevorblades + strip_listen_path: true + graphql: + enabled: true + version: "2" + execution_mode: proxyOnly + schema: | + directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE + + enum CacheControlScope { + PUBLIC + PRIVATE + } + + type Continent { + code: ID! + name: String! + countries: [Country!]! + } + + input ContinentFilterInput { + code: StringQueryOperatorInput + } + + type Country { + code: ID! + name: String! + native: String! + phone: String! + continent: Continent! + capital: String + currency: String + languages: [Language!]! + emoji: String! + emojiU: String! + states: [State!]! + } + + input CountryFilterInput { + code: StringQueryOperatorInput + currency: StringQueryOperatorInput + continent: StringQueryOperatorInput + } + + type Language { + code: ID! + name: String + native: String + rtl: Boolean! + } + + input LanguageFilterInput { + code: StringQueryOperatorInput + } + + type Query { + continents(filter: ContinentFilterInput): [Continent!]! + continent(code: ID!): Continent + countries(filter: CountryFilterInput): [Country!]! + country(code: ID!): Country + languages(filter: LanguageFilterInput): [Language!]! + language(code: ID!): Language + } + + type State { + code: String + name: String! + country: Country! + } + + input StringQueryOperatorInput { + eq: String + ne: String + in: [String] + nin: [String] + regex: String + glob: String + } + + """The `Upload` scalar type represents a file upload.""" + scalar Upload + playground: + enabled: true + path: /playground +``` + +### Set Up Manifest for HTTP +#### HTTP Proxy + +This example creates a basic API definition that routes requests to listen path `/httpbin` to target URL `http://httpbin.org`. + +Traffic routing can be configured under `spec.proxy`: +- `target_url` defines the upstream address (or target URL) to which requests should be proxied. +- `listen_path` is the base path on Tyk to which requests for this API should be sent. Tyk listens out for any requests coming into the host at this path, on the port that Tyk is configured to run on and processes these accordingly. For example, `/api/` or `/` or `/httpbin/`. +- `strip_listen_path` removes the inbound listen path (as accessed by the client) when generating the outbound request for the upstream service. For example, consider the scenario where the Tyk base address is `http://acme.com/`, the listen path is `example/` and the upstream URL is `http://httpbin.org/`: If the client application sends a request to `http://acme.com/example/get` then the request will be proxied to `http://httpbin.org/example/get` + +```yaml {hl_lines=["10-13"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +#### HTTP Host-based Proxy + +`spec.domain` is the domain to bind this API to. This enforces domain matching for client requests. + +In this example, requests to `httpbin.tyk.io` will be proxied to upstream URL `http://httpbin.org` + +```yaml {hl_lines=["10-10"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: http + active: true + domain: httpbin.tyk.io + proxy: + target_url: http://httpbin.org + listen_path: / + strip_listen_path: true +``` + +#### HTTPS Proxy + +This example creates a API definition that routes requests to a http://httpbin.org via port 8443. + +```yaml {hl_lines=["35-38"],linenos=false} +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: { } +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: my-test-cert +spec: + secretName: my-test-tls + dnsNames: + - foo.com + - bar.com + privateKey: + rotationPolicy: Always + issuerRef: + name: selfsigned-issuer + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer + # This is optional since cert-manager will default to this value however + # if you are using an external issuer, change this to that issuer group. + group: cert-manager.io +--- +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin +spec: + name: httpbin + use_keyless: true + protocol: https + listen_port: 8443 + certificate_secret_names: + - my-test-tls + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +#### Load Balancing + +Tyk Operator supports round-robin load balancing via the `ApiDefinition` CRD. Enable it with `proxy.enable_load_balancing` and list the upstream targets in `proxy.target_list`. + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: httpbin-load-balanced +spec: + name: httpbin-load-balanced + use_keyless: true + protocol: http + active: true + proxy: + enable_load_balancing: true + target_list: + - "http://10.0.0.1" + - "http://10.0.0.2" + - "http://10.0.0.3" + listen_path: /httpbin + strip_listen_path: true +``` + +### Set Up Manifest for TCP + +This example creates a API definition that proxies request from TCP port `6380` to `tcp://localhost:6379`. + +```yaml {hl_lines=["8-11"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: redis-tcp +spec: + name: redis-tcp + active: true + protocol: tcp + listen_port: 6380 + proxy: + target_url: tcp://localhost:6379 +``` + +### Set Up Manifest for UDG +#### UDG v2 (Tyk 3.2 and above) + +If you are on Tyk 3.2 and above, you can use the following manifest to create an UDG API. This example configures a Universal Data Graph from a [GraphQL datasource](/api-management/data-graph#graphql) and a [REST Datasource](/api-management/data-graph#rest). + +```yaml {hl_lines=["20-39", "46-80"],linenos=false} +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: udg +spec: + name: Universal Data Graph v2a + use_keyless: true + protocol: http + active: true + proxy: + target_url: "" + listen_path: /udg + strip_listen_path: true + version_data: + default_version: Default + not_versioned: true + versions: + Default: + name: Default + graphql: + enabled: true + execution_mode: executionEngine + schema: | + type Country { + name: String + code: String + restCountry: RestCountry + } + + type Query { + countries: [Country] + } + + type RestCountry { + altSpellings: [String] + subregion: String + population: Int + } + version: "2" + last_schema_update: "2022-10-12T14:27:55.511+03:00" + type_field_configurations: [] + playground: + enabled: true + path: /playground + engine: + field_configs: + - disable_default_mapping: false + field_name: countries + path: + - "countries" + type_name: Query + - disable_default_mapping: true #very important for rest APIs + field_name: restCountry + path: [] + type_name: Country + data_sources: + - kind: "GraphQL" + name: "countries" + internal: false + root_fields: + - type: Query + fields: + - "countries" + config: + url: "https://countries.trevorblades.com/" + method: "POST" + headers: {} + body: "" + - kind: "REST" + internal: false + name: "restCountries" + root_fields: + - type: "Country" + fields: + - "restCountry" + config: + url: "https://restcountries.com/v2/alpha/{{ .object.code }}" + method: "GET" + body: "" + headers: {} +``` + +#### UDG v1 (Tyk 3.1 or before) + +If you are on Tyk 3.1, you can use the following manifest to create an UDG API. This example creates a Universal Data Graph with GraphQL datasource and HTTP JSON datasource. + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: udg +spec: + name: Universal Data Graph Example + use_keyless: true + protocol: http + active: true + proxy: + target_url: "" + listen_path: /udg + strip_listen_path: true + graphql: + enabled: true + execution_mode: executionEngine + schema: | + type Country { + name: String + code: String + restCountry: RestCountry + } + + type Query { + countries: [Country] + } + + type RestCountry { + altSpellings: [String] + subregion: String + population: String + } + type_field_configurations: + - type_name: Query + field_name: countries + mapping: + disabled: false + path: countries + data_source: + kind: GraphQLDataSource + data_source_config: + url: "https://countries.trevorblades.com" + method: POST + status_code_type_name_mappings: [] + - type_name: Country + field_name: restCountry + mapping: + disabled: true + path: "" + data_source: + kind: HTTPJSONDataSource + data_source_config: + url: "https://restcountries.com/v2/alpha/{{ .object.code }}" + method: GET + default_type_name: RestCountry + status_code_type_name_mappings: + - status_code: 200 + playground: + enabled: true + path: /playground +``` + +## Set Up Tyk Streams API +Tyk Streams integrates natively with Tyk OpenAPI Specification (OAS), allowing you to manage APIs as code and automate processes in Kubernetes using Tyk Operator. Setting up Tyk Streams API is similar to configuring a standard Tyk OAS API. You can store the Tyk Streams OAS definition in a Kubernetes ConfigMap and connect it to Tyk Gateway through a `TykStreamsApiDefinition` resource. + +### Create your Tyk Streams API +#### Prepare the Tyk Streams API Definition +To create a Tyk Streams API, start by preparing a complete Tyk Streams API definition in the OpenAPI Specification (OAS) format. This file must include: + +- The `x-tyk-api-gateway` extension for Tyk-specific settings. +- The `x-tyk-streaming` extension for Tyk Streams configuration. + +Here’s an example of a Tyk Streams API definition: + +```json {hl_lines=["17-54"],linenos=true} +{ + "info": { + "title": "Simple streaming demo", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "servers": [ + { + "url": "http://tyk-gw.local/streams/" + } + ], + "security": [], + "paths": {}, + "components": { + "securitySchemes": {} + }, + "x-tyk-streaming": { + "streams": { + "example-publisher": { + "input": { + "http_server": { + "allowed_verbs": [ + "POST" + ], + "path": "/pub", + "timeout": "1s" + } + }, + "output": { + "http_server": { + "ws_path": "/ws" + } + } + } + } + }, + "x-tyk-api-gateway": { + "info": { + "name": "Simple streaming demo", + "state": { + "active": true, + "internal": false + } + }, + "server": { + "listenPath": { + "strip": true, + "value": "/streams/" + } + }, + "upstream": { + "url": "https://not-needed" + } + } +} +``` + +#### Create a TykStreamsApiDefinition Custom Resource +Once your Tyk Streams API definition is ready, use a Kubernetes ConfigMap to store the definition and link it to a `TykStreamsApiDefinition` custom resource. + +Example manifest: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: TykStreamsApiDefinition +metadata: + name: simple-stream +spec: + tykStreams: + configmapRef: + name: simple-stream-cm #k8s resource name of configmap + namespace: default #The k8s namespace of the resource being targeted. If Namespace is not provided, + #we assume that the ConfigMap is in the same namespace as TykStreamsApiDefinition resource. + keyName: test_stream.json +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: simple-stream-cm +data: + test_stream.json: |- + { + "components": {}, + "info": { + "title": "Simple streaming demo", + "version": "1.0.0" + }, + "openapi": "3.0.3", + "paths": {}, + "x-tyk-api-gateway": { + "info": { + "name": "Simple streaming demo", + "state": { + "active": true + } + }, + "server": { + "detailedTracing": { + "enabled": true + }, + "listenPath": { + "strip": true, + "value": "/streams/" + } + }, + "upstream": { + "url": "https://not-needed" + } + }, + "x-tyk-streaming": { + "streams": { + "example-publisher": { + "input": { + "http_server": { + "path": "/pub", + "allowed_verbs": ["POST"], + "timeout": "1s" + } + }, + "output": { + "http_server": { + "ws_path": "/ws" + } + } + } + } + } + } +``` + +#### Apply the TykStreamsApiDefinition Manifest + +Use the `kubectl` command to apply the `TykStreamsApiDefinition` manifest to your Kubernetes cluster: + +```sh +kubectl apply -f tyk-streams-api-definition.yaml +``` + +This will create a new `TykStreamsApiDefinition` resource. The Tyk Operator watches this resource and configures the Tyk Gateway or Tyk Dashboard with the new API. + +#### Verify the Tyk Streams API Creation + +Check the status of the `TykStreamsApiDefinition` resource to ensure that the API has been successfully created: + +```sh +kubectl get tykstreamsapidefinitions simple-stream +``` + +You should see output similar to this: + +```bash +NAME DOMAIN LISTENPATH ENABLED SYNCSTATUS +simple-stream /streams/ true Successful +``` + +#### Manage and Update the Tyk Streams API +To update your API configuration, modify the linked `ConfigMap`. The Tyk Operator will automatically detect changes and update the API in the Tyk Gateway. + +### Secure your Tyk Streams API +To secure your Tyk Streams API, configure security fields in the OAS definition just as you would for a standard Tyk OAS API. For more details, refer to the [Secure your Tyk OAS API](#secure-your-tyk-oas-api) guide. + +## Add a Security Policy to your API +To further protect access to your APIs, you will want to add a security policy. +Below, we take you through how to define the security policy but you can also find [Security Policy Example](/tyk-stack/tyk-operator/create-an-api#security-policy-example) below. + +### Define the Security Policy manifest + +To create a security policy, you must define a Kubernetes manifest using the `SecurityPolicy` CRD. The following example illustrates how to configure a default policy for trial users for a Tyk Classic API named `httpbin`, a Tyk OAS API named `petstore`, and a Tyk Streams API named `http-to-kafka`. + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: trial-policy # Unique Kubernetes name +spec: + name: Default policy for trial users # Descriptive name for the policy + state: active + active: true + access_rights_array: + - name: httpbin # Kubernetes name of referenced API + namespace: default # Kubernetes namespace of referenced API + kind: ApiDefinition # Omit this field or use `ApiDefinition` if you are referencing Tyk Classic API + versions: + - Default # The default version of Tyk Classic API is "Default" + - name: petstore + namespace: default + kind: TykOasApiDefinition # Use `TykOasApiDefinition` if you are referencing Tyk OAS API + versions: + - "" # The default version of Tyk OAS API is "" + - name: http-to-kafka + namespace: default + kind: TykStreamsApiDefinition # Use `TykStreamsApiDefinition` if you are referencing Tyk Streams API + versions: + - "" # The default version of Tyk Streams API is "" + quota_max: 1000 + quota_renewal_rate: 3600 + rate: 120 + per: 60 + throttle_interval: -1 + throttle_retry_limit: -1 +``` + +Save the manifest locally in a file, e.g. `trial-policy.yaml` + +In this example, we have defined a security policy as described below: + +**Define Security Policy status and metadata** + + - **`name`**: A descriptive name for the security policy. + - **`active`**: Marks the policy as active (true or false). + - **`state`**: The current state of the policy. It can have one of three values: + - **`active`**: Keys connected to this policy are enabled and new keys can be created. + - **`draft`**: Keys connected to this policy are disabled; no new keys can be created. + - **`deny`**: Policy is not published to Gateway; no keys can be created. + - **`tags`**: A list of tags to categorize or label the security policy, e.g. + + ```yaml + tags: + - Hello + - World + ``` + + - **`meta_data`**: Key-value pairs for additional metadata related to the policy, e.g. + + ```yaml + meta_data: + key: value + hello: world + ``` + +**Define Access Lists for APIs** + + - **`access_rights_array`**: Defines the list of APIs that the security policy applies to and the versions of those APIs. + - **`name`**: The Kubernetes metadata name of the API resource to which the policy grants access. + - **`namespace`**: The Kubernetes namespace where the API resource is deployed. + - **`kind`**: Tyk OAS APIs (`TykOasApiDefinition`), Tyk Streams (`TykStreamsApiDefinition`) and Tyk Classic APIs (`ApiDefinition`) can be referenced here. The API format can be specified by `kind` field. If omitted, `ApiDefinition` is assumed. + - **`versions`**: Specifies the API versions the policy will cover. If the API is not versioned, include the default version here. The default version of a Classic API is "Default". The default version of a Tyk OAS API is "". + +In this example, the security policy will apply to an `ApiDefinition` resource named `httpbin` in the `default` namespace, a `TykOasApiDefinition` resource named `petstore` in the `default` namespace, and a `TykStreamsApiDefinition` resource named `http-to-kafka` in the `default` namespace. Note that you do not need to specify the API ID; Tyk Operator will automatically retrieve the API ID of referenced API Definition resources for you. + +**Define Rate Limits, Usage Quota, and Throttling** + +- **`rate`**: The maximum number of requests allowed per time period (Set to `-1` to disable). +- **`per`**: The time period (in seconds) for the rate limit (Set to `-1` to disable). +- **`throttle_interval`**: The interval (in seconds) between each request retry (Set to `-1` to disable). +- **`throttle_retry_limit`**: The maximum number of retry attempts allowed (Set to `-1` to disable). +- **`quota_max`**: The maximum number of requests allowed over a quota period (Set to `-1` to disable). +- **`quota_renewal_rate`**: The time, in seconds, after which the quota is renewed. + +In this example, trial users under this security policy can gain access to the `httpbin` API at a rate limit of maximum 120 times per 60 seconds (`"rate": 120, "per": 60`), with a usage quota of 1000 every hour (`"quota_max": 1000, "quota_renewal_rate": 3600`), without any request throttling (`throttle_interval: -1, throttle_retry_limit: -1`). + +### Apply the Security Policy manifest +Once you have defined your security policy manifest, apply it to your Kubernetes cluster using the `kubectl apply` command: + +```bash +kubectl apply -f trial-policy.yaml +``` + +### Verify the Security Policy + +After applying the manifest, you can verify that the security policy has been created successfully by running: + +```bash +kubectl describe securitypolicy trial-policy + +... +Status: + Latest CRD Spec Hash: 901732141095659136 + Latest Tyk Spec Hash: 5475428707334545086 + linked_apis: + Kind: ApiDefinition + Name: httpbin + Namespace: default + Kind: TykOasApiDefinition + Name: petstore + Namespace: default + Kind: TykStreamsApiDefinition + Name: http-to-kafka + Namespace: default + pol_id: 66e9a27bfdd3040001af6246 +Events: +``` + +From the `status` field, you can see that this security policy has been linked to `httpbin`, `petstore`, and `http-to-kafka` APIs. + + +### Security policy example + + +#### Key-level per-API rate limits and quota + +By configuring per-API limits, you can set specific rate limits, quotas, and throttling rules for each API in the access rights array. When these per-API settings are enabled, the API inherits the global limit settings unless specific limits and quotas are set in the `limit` field for that API. + +The following manifest defines a security policy with per-API rate limits and quotas for two APIs: `httpbin` and `petstore`. + +```yaml {hl_lines=["15-21", "27-33", "40-41"],linenos=true} +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: policy-per-api-limits +spec: + name: Policy with Per API Limits + state: active + active: true + access_rights_array: + - name: httpbin # Kubernetes name of referenced API + namespace: default # Kubernetes namespace of referenced API + kind: ApiDefinition # `ApiDefinition` (Default), `TykOasApiDefinition` or `TykStreamsApiDefinition` + versions: + - Default # The default version of Tyk Classic API is "Default" + limit: # APILimit stores quota and rate limit on ACL level + rate: 10 # Max 10 requests per 60 seconds + per: 60 # Time period for rate limit + quota_max: 100 # Max 100 requests allowed over the quota period + quota_renewal_rate: 3600 # Quota renewal period in seconds (1 hour) + throttle_interval: -1 # No throttling between retries + throttle_retry_limit: -1 # No limit on request retries + - name: petstore + namespace: default + kind: TykOasApiDefinition # Use `TykOasApiDefinition` for Tyk OAS API + versions: + - "" # The default version of Tyk OAS API is "" + limit: + rate: 5 # Max 5 requests per 60 seconds + per: 60 # Time period for rate limit + quota_max: 100 # Max 100 requests allowed over the quota period + quota_renewal_rate: 3600 # Quota renewal period in seconds (1 hour) + throttle_interval: -1 # No throttling between retries + throttle_retry_limit: -1 # No limit on request retries + rate: -1 # Disable global rate limit + per: -1 # Disable global rate limit period + throttle_interval: -1 # Disable global throttling + throttle_retry_limit: -1 # Disable global retry limit + quota_max: -1 # Disable global quota + quota_renewal_rate: 60 # Quota renewal rate in seconds (1 minute) +``` + +With this security policy applied: + +For the `httpbin` API: +- The rate limit allows a maximum of 10 requests per 60 seconds. +- The quota allows a maximum of 100 requests per hour (3600 seconds). +- There is no throttling or retry limit (throttle_interval and throttle_retry_limit are set to -1). + +For the `petstore` API: +- The rate limit allows a maximum of 5 requests per 60 seconds. +- The quota allows a maximum of 100 requests per hour (3600 seconds). +- There is no throttling or retry limit (throttle_interval and throttle_retry_limit are set to -1). + +Global Rate Limits and Quota: +- All global limits (rate, quota, and throttling) are disabled (-1), so they do not apply. + +By setting per-API rate limits and quotas, you gain granular control over how each API is accessed and used, allowing you to apply different limits for different APIs as needed. This configuration is particularly useful when you want to ensure that critical APIs have stricter controls while allowing more flexibility for others. Use this example as a guideline to tailor your security policies to your specific requirements. + +#### Key-level per-endpoint rate limits + +By configuring key-level per-endpoint limits, you can restrict the request rate for specific API clients to a specific endpoint of an API. + +The following manifest defines a security policy with per-endpoint rate limits for two APIs: `httpbin` and `petstore`. + +```yaml {hl_lines=["15-29", "35-49"],linenos=true} +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: policy-per-api-limits +spec: + name: Policy with Per API Limits + state: active + active: true + access_rights_array: + - name: httpbin # Kubernetes name of referenced API + namespace: default # Kubernetes namespace of referenced API + kind: ApiDefinition # `ApiDefinition` (Default), `TykOasApiDefinition` or `TykStreamsApiDefinition` + versions: + - Default # The default version of Tyk Classic API is "Default" + endpoints: # Per-endpoint rate limits + - path: /anything + methods: + - name: POST + limit: + rate: 5 + per: 60 + - name: PUT + limit: + rate: 5 + per: 60 + - name: GET + limit: + rate: 10 + per: 60 + - name: petstore + namespace: default + kind: TykOasApiDefinition # Use `TykOasApiDefinition` for Tyk OAS API + versions: + - "" # The default version of Tyk OAS API is "" + endpoints: # Per-endpoint rate limits + - path: /pet + methods: + - name: POST + limit: + rate: 5 + per: 60 + - name: PUT + limit: + rate: 5 + per: 60 + - name: GET + limit: + rate: 10 + per: 60 + rate: -1 # Disable global rate limit + per: -1 # Disable global rate limit period + throttle_interval: -1 # Disable global throttling + throttle_retry_limit: -1 # Disable global retry limit + quota_max: -1 # Disable global quota + quota_renewal_rate: 60 # Quota renewal rate in seconds (1 minute) +``` + +#### Path based permissions + + +You can secure your APIs by specifying [allowed URLs](/api-management/access-control/sessions-and-keys/access-rights#granular-endpoint-access) (methods and paths) for each API within a security policy. This is done using the `allowed_urls` field under `access_rights_array`. + +The following manifest defines a security policy that allows access only to specific URLs and HTTP methods for two APIs: `httpbin`(a Tyk Classic API) and `petstore` (a Tyk OAS API). + +```yaml {hl_lines=["15-18", "24-28"],linenos=true} +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: policy-with-allowed-urls +spec: + name: Policy with allowed URLs + state: active + active: true + access_rights_array: + - name: httpbin # Kubernetes name of referenced API + namespace: default # Kubernetes namespace of referenced API + kind: ApiDefinition # `ApiDefinition` (Default), `TykOasApiDefinition` or `TykStreamsApiDefinition` + versions: + - Default # The default version of Tyk Classic API is "Default" + allowed_urls: # Define allowed paths and methods + - url: /get # Only allow access to the "/get" path + methods: + - GET # Only allow the GET method + - name: petstore + namespace: default + kind: TykOasApiDefinition # Use `TykOasApiDefinition` for Tyk OAS API + versions: + - "" # The default version of Tyk OAS API is "" + allowed_urls: # Define allowed paths and methods + - url: "/pet/(.*)" # Allow access to any path starting with "/pet/" + methods: + - GET # Allow GET method + - POST # Allow POST method +``` + +With this security policy applied: + +- Allowed access: + - `curl -H "Authorization: Bearer $KEY_AUTH" http://tyk-gw.org/petstore/pet/10` returns a `200 OK` response. + - `curl -H "Authorization: Bearer $KEY_AUTH" http://tyk-gw.org/httpbin/get` returns a `200 OK` response. + +- Restricted access: + - `curl -H "Authorization: Bearer $KEY_AUTH" http://tyk-gw.org/petstore/pet` returns a `403 Forbidden` response with the message: + + ```json + { "error": "Access to this resource has been disallowed" } + ``` + + - `curl -H "Authorization: Bearer $KEY_AUTH" http://tyk-gw.org/httpbin/anything` returns a `403 Forbidden` response with the message: + + ```json + { "error": "Access to this resource has been disallowed" } + ``` + +#### Partitioned policies + + +[Partitioned policies](/api-management/access-control/policies/applying-policies#partitioned-policies) allow you to selectively enforce different segments of a security policy, such as quota, rate limiting, access control lists (ACL), and GraphQL complexity rules. This provides flexibility in applying different security controls as needed. + +To configure a partitioned policy, set the segments you want to enable in the `partitions` field: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: partitioned-policy-example +spec: + name: Partitioned Policy Example + state: active + active: true + access_rights_array: + - name: httpbin # Kubernetes name of referenced API + namespace: default # Kubernetes namespace of referenced API + kind: ApiDefinition # `ApiDefinition` (Default), `TykOasApiDefinition` or `TykStreamsApiDefinition` + versions: + - Default # The default version of Tyk Classic API is "Default" + - name: petstore + namespace: default + kind: TykOasApiDefinition # Use `TykOasApiDefinition` if you are referencing Tyk OAS API + versions: + - "" # The default version of Tyk OAS API is "" + partitions: + quota: false # Do not enforce quota rules + rate_limit: false # Do not enforce rate limiting rules + acl: true # Enforce access control rules + complexity: false # Do not enforce GraphQL complexity rules +``` + +- **`quota`**: Set to true to enforce quota rules (limits the number of requests allowed over a period). +- **`rate_limit`**: Set to true to enforce rate limiting rules (limits the number of requests per second or minute). +- **`acl`**: Set to true to enforce access control rules (controls which APIs or paths can be accessed). +- **`complexity`**: Set to true to enforce GraphQL complexity rules (limits the complexity of GraphQL queries to prevent resource exhaustion). + + + +## Migrate Existing APIs to Tyk Operator + +If you have existing APIs and Policies running on your Tyk platform, and you want to start using Tyk Operator to manage them, you probably would not want to re-create the APIs and Policies on the platform using Operator CRDs. It is because you will lose keys, policies, and analytics linked to the APIs. You can instead link existing APIs and Policies to a CRD by specifying the API ID or Policy ID in the CRD spec. This way, Operator will update the existing API or Policy according to the CRD spec. Any keys, policies and analytics linked to the API will continue to operate the same. This is great for idempotency. + +### Export existing configurations to CRDs + +Instead of creating the API and Policy CRDs from scratch, you can try exporting them from Dashboard using a snapshot tool. You can find the detail usage guide [here](https://github.com/TykTechnologies/tyk-operator/blob/master/pkg/snapshot/README.md). This is great if you want to have a quick start. However, this is still a PoC feature so we recommend you to double check the output files before applying them to your cluster. + +### Migration of existing API + +If there are existing APIs that you want to link to a CRD, it's very easy to do so. You need to simply add the `api_id` from your API Definition to the YAML of your `ApiDefinition` type. Then, the Operator will take care of the rest. + +Example: + +1. From the existing API Definition, grab the following field: + +```json +"api_id": "5e0fac4845bb46c77543be28300fd9d7" +``` + +2. Simply add this value to your YAML, in the `spec.api_id`field: + +```yaml +apiVersion: tyk.tyk.io/v1alpha1 +kind: ApiDefinition +metadata: + name: my-existing-api +spec: + api_id: 5e0fac4845bb46c77543be28300fd9d7 + name: existing API + protocol: http + active: true + proxy: + target_url: http://httpbin.org + listen_path: /httpbin + strip_listen_path: true +``` + +3. Then apply your changes: + +```console +$ kubectl apply -f config/samples/httpbin_protected.yaml +apidefinition.tyk.tyk.io/my-existing-api created +``` + + + +The source of truth for the API definition is now the CRD, meaning it will override any differences in your existing API definition. + + + +### Migration of existing Policy +If you have existing pre-Operator policies, you can easily link them to a CRD, which will allow you to modify them through the YAML moving forward. +Simply set the id field in the SecurityPolicy YAML to the _id field in the existing Policy's JSON. This will allow the Operator to make the link. +Note that the YAML becomes the source of truth and will overwrite any changes between it and the existing Policy. + +**Example**: +1. Find out your existing Policy ID, e.g. `5f8f3933f56e1a5ffe2cd58c` + +2. Stick the policy ID `5f8f3933f56e1a5ffe2cd58c` into the YAML's `spec.id` field like below + +```yaml +my-security-policy.yaml: +apiVersion: tyk.tyk.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: new-httpbin-policy +spec: + id: 5f8f3933f56e1a5ffe2cd58c + name: My New HttpBin Policy + state: active + active: true + access_rights_array: + - name: new-httpbin-api # name of your ApiDefinition object. + namespace: default # namespace of your ApiDefinition object. + versions: + - Default +``` + +The `spec.access_rights_array` field of the YAML must refer to the ApiDefinition object that the policy identified by the id will affect. + +To find available ApiDefinition objects: + +```console +$ kubectl get tykapis -A +NAMESPACE NAME DOMAIN LISTENPATH PROXY.TARGETURL ENABLED +default new-httpbin-api /httpbin http://httpbin.org true +``` + +3. And then apply this file: + +```console +$ kubectl apply -f my-security-policy.yaml +securitypolicy.tyk.tyk.io/new-httpbin-policy created +``` + +Now the changes in the YAML were applied to the existing Policy. You can now manage this policy through the CRD moving forward. +Note, if this resource is unintentionally deleted, the Operator will recreate it with the same `id` field as above, allowing keys to continue to work as before the delete event. + +### Idempotency + +Because of the ability to declaratively define the `api_id`, this gives us the ability to preserve Keys that are tied to APIs or policies which are tied to APIs. +Imagine any use case where you have keys tied to policies, and policies tied to APIs. +Now imagine that these resources are unintentionally destroyed. Our database goes down, or our cluster, or something else. +Well, using the Tyk Operator, we can easily re-generate all our resources in a non-destructive fashion. That's because the operator intelligently constructs the unique ID using the unique namespaced name of our CRD resources. For that reason. +Alternatively, if you don't explicitly state it, it will be hard-coded for you by Base64 encoding the namespaced name of the CRD. + +For example: + +1. we have keys tied to policies tied to APIs in production. +2. Our production DB gets destroyed, all our Policies and APIs are wiped +3. The Tyk Operator can resync all the changes from our CRDs into a new environment, by explicitly defining the Policy IDs and API IDs as before. +4. This allows keys to continue to work normally as Tyk resources are generated idempotently through the Operator. + + diff --git a/tyk-stack/tyk-operator/installing-tyk-operator.mdx b/tyk-stack/tyk-operator/installing-tyk-operator.mdx new file mode 100644 index 0000000000..bdfa80a57b --- /dev/null +++ b/tyk-stack/tyk-operator/installing-tyk-operator.mdx @@ -0,0 +1,319 @@ +--- +title: "Install Tyk Operator" +description: "Learn how to install Tyk Operator on Kubernetes to manage your Tyk API configurations" +keywords: "Tyk Operator, Kubernetes, API Management" +sidebarTitle: "Installation" +--- + +## Introduction + +We assume you have already installed Tyk. If you don’t have it, check out [Tyk +Cloud](/tyk-cloud#quick-start-tyk-cloud) or [Tyk Self +Managed](/tyk-self-managed) page. [Tyk Helm +Chart](/product-stack/tyk-charts/overview) is the preferred (and easiest) way to install Tyk on Kubernetes. + +In order for policy ID matching to work correctly, Dashboard must have `allow_explicit_policy_id` and +`enable_duplicate_slugs` set to `true` and Gateway must have `policies.allow_explicit_policy_id` set to `true`. + +Tyk Operator needs a [user credential](/api-management/automations/operator#operator-user) to connect with +Tyk Dashboard. The Operator user should have write access to the resources it is going to manage, e.g. APIs, Certificates, +Policies, and Portal. It is the recommended practice to turn off write access for other users for the above resources. See +[Using Tyk Operator to enable GitOps with Tyk](/api-management/automations) about +maintaining a single source of truth for your API configurations. + +## Install cert-manager + +Tyk Operator uses cert-manager to provision certificates for the webhook server. If you don't have cert-manager +installed, you can follow this command to install it: + +Alternatively, you have the option to manually handle TLS certificates by disabling the `cert-manager` requirement. For more details, please refer to this [configuration](#webhook-configuration). + +```console +$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.8.0/cert-manager.yaml +``` + +Since Tyk Operator supports Kubernetes v1.19+, the minimum cert-manager version you can use is v1.8. If you run into the +cert-manager related errors, please ensure that the desired version of Kubernetes version works with the chosen version +of cert-manager by checking [supported releases page](https://cert-manager.io/docs/installation/supported-releases/) and +[cert-manager documentation](https://cert-manager.io/docs/installation/supported-releases/). + +Please wait for the cert-manager to become available before continuing with the next step. + +## Option 1: Install Tyk Operator via Tyk's Umbrella Helm Charts + +If you are using [Tyk Stack](/product-stack/tyk-charts/tyk-stack-chart), [Tyk Control +Plane](/product-stack/tyk-charts/tyk-control-plane-chart), or [Tyk Open +Source Chart](/product-stack/tyk-charts/tyk-oss-chart), you can install Tyk Operator alongside other Tyk +components by setting value `global.components.operator` to `true`. + +Starting from Tyk Operator v1.0, a license key is required to use the Tyk Operator. You can provide it while installing +Tyk Stack, Tyk Control Plane or Tyk OSS helm chart by setting `global.license.operator` field. You can also set license +key via a Kubernetes secret using `global.secrets.useSecretName` field. The secret should contain a key called +`OperatorLicense` + +Note: If you are using `global.secrets.useSecretName`, you must configure the operator license in the referenced Kubernetes secret. `global.license.operator` will not be used in this case. + +## Option 2: Install Tyk Operator via stand-alone Helm Chart + +If you prefer to install Tyk Operator separately, follow this section to install Tyk Operator using Helm. + +### Configure Tyk Operator via environment variable or tyk-operator-conf secret + +Tyk Operator configurations can be set using `envVars` field of helm chart. See the table below for a list of expected +environment variable names and example values. + +```yaml +envVars: + - name: TYK_OPERATOR_LICENSEKEY + value: "{YOUR_LICENSE_KEY}" + - name: TYK_MODE + value: "pro" + - name: TYK_URL + value: "http://dashboard-svc-tyk-tyk-dashboard.tyk.svc:3000" + - name: TYK_AUTH + value: "2d095c2155774fe36d77e5cbe3ac963b" + - name: TYK_ORG + value: "5e9d9544a1dcd60001d0ed20" +``` + +It can also be set via a Kubernetes secret. The default K8s secret name is `tyk-operator-conf`. If you want to use +another name, configure it through Helm Chart [envFrom](#install-tyk-operator-and-custom-resource-definitions-crds) value. + +The Kubernetes secret or envVars field should set the following keys: + + + + + +| Key | Mandatory | Example Value | Description | +| :--------------------------- | :-------- | :-------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- | +| TYK_OPERATOR_LICENSEKEY | Yes | `` | Tyk Operator license key | +| TYK_MODE | Yes | pro | “ce” for Tyk Open Source mode, “pro” for Tyk licensed mode. | +| TYK_URL | Yes | http://dashboard-svc-tyk-tyk-dashboard.tyk.svc:3000 | Management URL of Tyk Gateway (Open Source) or Tyk Dashboard | +| TYK_AUTH | Yes | 2d095c2155774fe36d77e5cbe3ac963b | Operator user API key. | +| TYK_ORG | Yes | 5e9d9544a1dcd60001d0ed20 | Operator user ORG ID. | +| TYK_TLS_INSECURE_SKIP_VERIFY | No | true | Set to `“true”` if the Tyk URL is HTTPS and has a self-signed certificate. If it isn't set, the default value is `false`. | +| WATCH_NAMESPACE | No | foo,bar | Comma separated list of namespaces for Operator to operate on. The default is to operate on all namespaces if not specified. | +| WATCH_INGRESS_CLASS | No | customclass | Define the ingress class Tyk Operator should watch. Default is `tyk` | +| TYK_HTTPS_INGRESS_PORT | No | 8443 | Define the ListenPort for HTTPS ingress. Default is `8443`. | +| TYK_HTTP_INGRESS_PORT | No | 8080 | Define the ListenPort for HTTP ingress. Default is `8080`. | + + + + + +**Note**: From Tyk Operator v1.0, although Tyk Operator is compatible with the Open Source Tyk Gateway, a valid license +key is required for running Tyk Operator. + +| Key | Mandatory | Example Value | Description | +| :--------------------------- | :-------- | :------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- | +| TYK_OPERATOR_LICENSEKEY | Yes | `` | Tyk Operator license key | +| TYK_MODE | Yes | ce | “ce” for Tyk Open Source mode, “pro” for Tyk licensed mode. | +| TYK_URL | Yes | http://gateway-svc-tyk-ce-tyk-gateway.tyk.svc:8080 | Management URL of Tyk Gateway (Open Source) or Tyk Dashboard | +| TYK_AUTH | Yes | myapisecret | Operator user API key. | +| TYK_ORG | Yes | myorgid | Operator user ORG ID. | +| TYK_TLS_INSECURE_SKIP_VERIFY | No | true | Set to `“true”` if the Tyk URL is HTTPS and has a self-signed certificate. If it isn't set, the default value is `false`. | +| WATCH_NAMESPACE | No | foo,bar | Comma separated list of namespaces for Operator to operate on. The default is to operate on all namespaces if not specified. | +| WATCH_INGRESS_CLASS | No | customclass | Define the ingress class Tyk Operator should watch. Default is `tyk` | +| TYK_HTTPS_INGRESS_PORT | No | 8443 | Define the ListenPort for HTTPS ingress. Default is `8443`. | +| TYK_HTTP_INGRESS_PORT | No | 8080 | Define the ListenPort for HTTP ingress. Default is `8080`. | + + + + + +**Connect to Tyk Gateway or Dashboard** + +If you install Tyk using Helm Chart, `tyk-operator-conf` will have been created with the following keys: +`TYK_OPERATOR_LICENSEKEY, TYK_AUTH, TYK_MODE, TYK_ORG`, and `TYK_URL` by default. If you didn't use Helm Chart for +installation, please prepare `tyk-operator-conf` secret yourself using the commands below: + +```console +$ kubectl create namespace tyk-operator-system + +$ kubectl create secret -n tyk-operator-system generic tyk-operator-conf \ + --from-literal "TYK_OPERATOR_LICENSEKEY=${TYK_OPERATOR_LICENSEKEY}" \ + --from-literal "TYK_AUTH=${TYK_AUTH}" \ + --from-literal "TYK_ORG=${TYK_ORG}" \ + --from-literal "TYK_MODE=${TYK_MODE}" \ + --from-literal "TYK_URL=${TYK_URL}" +``` + + + +User API key and Organization ID can be found under "Add / Edit User" page within Tyk Dashboard. `TYK_AUTH` corresponds +to Tyk Dashboard API Access Credentials. `TYK_ORG` corresponds to Organization ID. + + + + + +If the credentials embedded in the `tyk-operator-conf` are ever changed or updated, the tyk-operator-controller-manager +pod must be restarted to pick up these changes. + + + +**Watch Namespaces** + +Tyk Operator is installed with cluster permissions. However, you can optionally control which namespaces it watches by +setting the `WATCH_NAMESPACE` through `tyk-operator-conf` secret or the environment variable to a comma separated list +of k8s namespaces. For example: + +- `WATCH_NAMESPACE=""` will watch for resources across the entire cluster. +- `WATCH_NAMESPACE="foo"` will watch for resources in the `foo` namespace. +- `WATCH_NAMESPACE="foo,bar"` will watch for resources in the `foo` and `bar` namespace. + +**Watch custom ingress class** + +You can configure [Tyk Operator as Ingress Controller](/product-stack/tyk-operator/tyk-ingress-controller) so +that [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) resources can be managed by Tyk as +APIs. By default, Tyk Operator looks for the value `tyk` in Ingress resources `kubernetes.io/ingress.class` annotation +and will ignore all other ingress classes. If you want to override this default behavior, you may do so by setting +[WATCH_INGRESS_CLASS](#configure-tyk-operator-via-environment-variable-or-tyk-operator-conf-secret) through `tyk-operator-conf` or the environment variable. + +### Install Tyk Operator and Custom Resource Definitions (CRDs) + +You can install CRDs and Tyk Operator using the stand-alone Helm Chart by running the following command: + +```console +$ helm repo add tyk-helm https://helm.tyk.io/public/helm/charts/ +$ helm repo update + +$ helm install tyk-operator tyk-helm/tyk-operator -n tyk-operator-system +``` + +This process will deploy Tyk Operator and its required Custom Resource Definitions (CRDs) into your Kubernetes cluster +in `tyk-operator-system` namespace. + +**Helm configurations** + + + +Starting from Tyk Operator v1.2.0, `webhookPort` is deprecated in favor of `webhooks.port`. + + + +| Key | Type | Default | +| :------------------------------------------- | :------ | :-------------------------------------- | +| envFrom[0].secretRef.name | string | `"tyk-operator-conf"` | +| envVars[0].name | string | `"TYK_OPERATOR_LICENSEKEY"` | +| envVars[0].value | string | `"{OPERATOR_LICENSEKEY}"` | +| envVars[1].name | string | `"TYK_HTTPS_INGRESS_PORT"` | +| envVars[1].value | string | `"8443"` | +| envVars[2].name | string | `"TYK_HTTP_INGRESS_PORT"` | +| envVars[2].value | string | `"8080"` | +| extraVolumeMounts | list | `[]` | +| extraVolumes | list | `[]` | +| fullnameOverride | string | `""` | +| healthProbePort | int | `8081` | +| hostNetwork | bool | `false` | +| image.pullPolicy | string | `"IfNotPresent"` | +| image.repository | string | `"tykio/tyk-operator"` | +| image.tag | string | `"v1.0.0"` | +| imagePullSecrets | list | `[]` | +| metricsPort | int | `8080` | +| nameOverride | string | `""` | +| nodeSelector | object | `{}` | +| podAnnotations | object | `{}` | +| podSecurityContext.allowPrivilegeEscalation | bool | `false` | +| rbac.port | int | `8443` | +| rbac.resources | object | `{}` | +| replicaCount | int | `1` | +| resources | object | `{}` | +| serviceMonitor | bool | `false` | +| webhookPort | int | `9443` | +| webhooks.enabled | bool | `true` | +| webhooks.port | int | `9443` | +| webhooks.annotations | object | `{}` | +| webhooks.tls.useCertManager | bool | `true` | +| webhooks.tls.secretName | string | `webhook-server-cert` | +| webhooks.tls.certificatesMountPath | string | `/tmp/k8s-webhook-server/serving-certs`| + +## Production Deployment Guidelines + +For high availability and large-scale environments (e.g., 1000 to 2000+ APIs), we recommend the following best practices for deploying Tyk Operator: + +### Recommended Replicas +We advise running exactly **2 replicas** of the Tyk Operator for High Availability. Tyk Operator uses an active-passive model with Kubernetes leader election, meaning only one pod (the leader) actively reconciles CRDs at any given time, while the other remains on standby. Scaling beyond 2 replicas does not increase throughput or distribute the workload; it only provides redundancy. + +### Scaling Strategy +Because of the active-passive model, Horizontal Pod Autoscaling (HPA) will not improve reconciliation performance. For large environments, you must scale the Operator **vertically**. Monitor the leader pod's resource consumption during mass deployments and increase the CPU and Memory `requests` and `limits` as needed. The Operator caches resources in memory, so its memory footprint will increase as the number of CRDs grows. + +### Connection Management +During bulk deployments of thousands of CRDs, the leader Operator pod can open a large number of TCP connections to the Tyk Dashboard. To prevent ephemeral port exhaustion on the node, we recommend deploying large batches of CRDs incrementally rather than all at once. + +## Upgrading Tyk Operator + +### Upgrading from v0.x to v1.0+ + +Starting from Tyk Operator v1.0, a valid license key is required for the Tyk Operator to function. If Tyk Operator is +upgraded from v0.x versions to one of v1.0+ versions, Tyk Operator needs a valid license key that needs to be provided +during upgrade process. This section describes how to set Tyk Operator license key to make sure Tyk Operator continues +functioning. + +To provide the license key for Tyk Operator, Kubernetes secret used to configure Tyk Operator (typically named +tyk-operator-conf as described above) requires an additional field called `TYK_OPERATOR_LICENSEKEY`. Populate this field +with your Tyk Operator license key. + +To configure the license key: + +1. Locate the Kubernetes Secret used to configure Tyk Operator (typically named `tyk-operator-conf`). +2. Add a new field called `TYK_OPERATOR_LICENSEKEY` to this Secret. +3. Set the value of `TYK_OPERATOR_LICENSEKEY` to your Tyk Operator license key. + +After updating the Kubernetes secret with this field, proceed with the standard upgrade process outlined below. + +### Upgrading Tyk Operator and CRDs + +You can upgrade Tyk Operator through Helm Chart by running the following command: + +```console +$ helm upgrade -n tyk-operator-system tyk-operator tyk-helm/tyk-operator --wait +``` + +[Helm does not upgrade or delete CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations) +when performing an upgrade. Because of this restriction, an additional step is required when upgrading Tyk Operator with +Helm. + +```console +$ kubectl apply -f https://raw.githubusercontent.com/TykTechnologies/tyk-charts/refs/heads/main/tyk-operator-crds/crd-$TYK_OPERATOR_VERSION.yaml +``` + + + +Replace $TYK_OPERATOR_VERSION with the image tag corresponding to the Tyk Operator version to which +the Custom Resource Definitions (CRDs) belong. For example, to install CRDs compatible with Tyk Operator v1.0.0, set $TYK_OPERATOR_VERSION to v1.0.0. + + + + +## Uninstalling Tyk Operator + +To uninstall Tyk Operator, you need to run the following command: + +```console +$ helm delete tyk-operator -n tyk-operator-system +``` + +## Webhook Configuration + +Starting from Operator v1.2.0 release, [Kubernetes Webhooks](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers) can now be configured using the Helm chart by specifying the necessary settings in the values.yaml file of the operator. +Since webhooks are enabled by default, there will be no impact to existing users. + +``` +webhooks: + enabled: true + port: 9443 + annotations: {} + tls: + useCertManager: true + secretName: webhook-server-cert + certificatesMountPath: "/tmp/k8s-webhook-server/serving-certs" +``` +- `enabled`: Enables or disables webhooks. +- `port`: Specifies the port for webhook communication. +- `annotations`: Allows adding custom annotations. +- `tls.useCertManager`: If true, Cert-Manager will handle TLS certificates. +- `tls.secretName`: The name of the Kubernetes Secret storing the TLS certificate. +- `tls.certificatesMountPath`: Path where the webhook server mounts its certificates. + diff --git a/tyk-stack/tyk-operator/publish-an-api.mdx b/tyk-stack/tyk-operator/publish-an-api.mdx new file mode 100644 index 0000000000..e89e49876e --- /dev/null +++ b/tyk-stack/tyk-operator/publish-an-api.mdx @@ -0,0 +1,141 @@ +--- +title: "Publish an API to Developer Portal with Tyk Operator" +description: "Learn how to publish an API to the Tyk Developer Portal using Tyk Operator in Kubernetes, enabling third-party developers to access your APIs." +keywords: "Tyk Operator, Developer Portal, API Management, Kubernetes" +sidebarTitle: "Publish an API" +--- + +## Introduction + +For Tyk Self Managed or Tyk Cloud, you can set up a Developer Portal to expose a facade of your APIs and then allow third-party developers to register and use your APIs. +You can make use of Tyk Operator CRDs to publish the APIs as part of your CI/CD workflow. If you have followed this Getting Started guide to create the httpbin example API, you can publish it to your Tyk Classic Developer Portal in a few steps. + + + +Currently Operator only supports publishing Tyk Classic API to the Tyk Classic Portal. + + + +## Publish an API with Tyk Operator + +### 1. Creating a security policy + +When you publish an API to the Portal, Tyk actually publishes a way for developers to enroll in a policy, not into the API directly. Therefore, you should first set up a security policy for the developers, before proceeding with the publishing. + +To do that, you can use the following command: + +```yml +cat <|"Views accounts, + initiates payments"| tykFAPI + tpp -->|"Integrates with, + consumes APIs from"| tykFAPI + aspsp -->|"Configures, monitors, + provides services through"| tykFAPI + + %% Styling + classDef person fill:#335FFD,color:#F7F7FF,stroke:#9393AA + classDef system fill:#00A3A0,color:#F7F7FF,stroke:#03031C + class psu,tpp,aspsp person + class tykFAPISystem system +``` + +### Tyk FAPI Accelerator + +The diagram below shows all major components of the Tyk FAPI Accelerator and their interactions. + +```mermaid +flowchart TB + %% People/Actors + psu(["PSU (Payment Services User) + A customer of the bank who accesses their accounts and initiates payments through third-party providers"]) + tpp(["TPP (Third Party Provider) + Companies providing financial services like account aggregators, credit checkers, and savings apps that integrate with banks"]) + + %% Tyk FAPI Accelerator System with Containers + subgraph tykFAPI ["Tyk FAPI Accelerator"] + tppApp["TPP Application + (NextJS) + Demonstrates how a TPP would interact with a bank's API"] + apiGateway["API Gateway + (Tyk Gateway) + Secures and routes API requests, enforces FAPI compliance, and handles event notifications"] + authServer["Authorization Server + (Keycloak) + Handles authentication and authorization"] + tykBank["Tyk Bank + (Node.js) + Mock bank implementation providing backend services"] + database[(Database)] + databaseLabel["PostgreSQL + Stores account information, payment data, and event subscriptions"] + kafka[(Message Broker)] + kafkaLabel["Kafka + Handles event notifications"] + end + + %% Connect labels to database and kafka + database --- databaseLabel + kafka --- kafkaLabel + + %% Relationships + psu -->|"Uses + (HTTPS)"| tppApp + tpp -->|"Develops + (IDE)"| tppApp + tppApp -->|"Makes API calls to + (HTTPS)"| apiGateway + tppApp -->|"Authenticates with + (OAuth 2.0/OIDC)"| authServer + apiGateway -->|"Routes requests to + (HTTPS)"| tykBank + authServer -->|"Verifies consents with + (HTTPS)"| tykBank + tykBank -->|"Reads from and writes to + (SQL)"| database + tykBank -->|"Publishes events to + (Kafka Protocol)"| kafka + + %% Event notification flow + kafka -->|"Subscribes to events"| apiGateway + apiGateway -->|"Sends signed notifications + (JWS/HTTPS Webhooks)"| tppApp + + %% Styling + classDef person fill:#335FFD,color:#F7F7FF,stroke:#9393AA + classDef tppStyle fill:#335FFD,color:#F7F7FF,stroke:#9393AA + classDef component fill:#00A3A0,color:#F7F7FF,stroke:#03031C + classDef authStyle fill:#00A3A0,color:#F7F7FF,stroke:#03031C + classDef bankStyle fill:#C01FB8,color:#F7F7FF,stroke:#03031C + classDef kafkaStyle fill:#E09D00,color:#F7F7FF,stroke:#03031C + classDef database fill:#5900CB,color:#F7F7FF,stroke:#03031C + classDef label fill:none,stroke:none + + class psu,tpp person + class tppApp tppStyle + class apiGateway component + class authServer authStyle + class tykBank bankStyle + class database database + class kafka kafkaStyle + class databaseLabel,kafkaLabel label +``` + +### Key Components + +1. **API Gateway (Tyk Gateway)**: + - Routes API requests to appropriate backend services + - Implements DPoP authentication via gRPC plugin + - Handles idempotency for payment requests + - Signs and delivers event notifications to TPPs + +2. **Authorization Server (Keycloak)**: + - Provides FAPI 2.0 compliant OAuth 2.0 and OpenID Connect + - Supports Pushed Authorization Requests (PAR) + - Manages user authentication and consent + +3. **Mock Bank Implementation**: + - Implements UK Open Banking Account Information API + - Implements UK Open Banking Payment Initiation API + - Implements UK Open Banking Event Subscriptions API + - Provides realistic testing environment + +4. **TPP Application**: + - Demonstrates how third parties integrate with the bank's APIs + - Implements FAPI 2.0 security profile + - Shows account information retrieval and payment initiation flows + +### Security Features + +The Tyk FAPI Accelerator implements several security features required for financial-grade APIs: + +1. **DPoP (Demonstrating Proof of Possession)**: + - Ensures the client possesses the private key corresponding to the public key in the token + - Prevents token theft and replay attacks + - Implemented as a gRPC plugin for Tyk Gateway + +2. **JWS Signing for Event Notifications**: + - Signs webhook notifications with JSON Web Signatures (JWS) + - Ensures authenticity and integrity of notifications + - Allows TPPs to verify the source of notifications + +3. **Idempotency Support**: + - Prevents duplicate transactions from repeated API calls + - Caches responses for idempotent requests + - Includes automatic garbage collection of expired entries + +4. **OAuth 2.0 with PAR**: + - Implements Pushed Authorization Requests for enhanced security + - Supports both automatic and manual authorization flows + - Complies with FAPI 2.0 security profile + + +## Getting Started + +For detailed setup instructions, code examples, and deployment guides, please refer to the [Tyk FAPI Accelerator GitHub repository](https://github.com/TykTechnologies/tyk-fapi/tree/main?tab=readme-ov-file#getting-started). + + +## Implementation Examples + +### Payment Flow Example + +The following sequence diagram illustrates a typical payment flow in the Tyk FAPI Accelerator: + +```mermaid +sequenceDiagram + actor User as End User + participant TPP as TPP Application + participant Gateway as API Gateway + participant Auth as Authorization Server + participant Bank as Tyk Bank + participant DB as Database + participant Kafka as Message Broker + + %% Payment Initiation + User->>TPP: 1. Initiate payment (amount, recipient) + + %% Payment Consent Creation + TPP->>Gateway: 2. Create payment consent + Gateway->>Bank: 3. Forward consent request + Bank->>DB: 4. Store consent + DB-->>Bank: 5. Return consent ID + Bank-->>Gateway: 6. Consent response with ConsentId + Gateway-->>TPP: 7. Return ConsentId + + %% Pushed Authorization Request (PAR) + TPP->>Auth: 8. Push Authorization Request (PAR) + Note right of TPP: Direct connection to Auth Server + Auth-->>TPP: 9. Return request_uri + + %% Authorization Options + TPP->>User: 10. Display authorization options + + %% Two possible authorization flows + alt Automatic Authorization + User->>TPP: 11a. Select automatic authorization + TPP->>Bank: 12a. Direct authorize consent request + Note right of TPP: Server-side authorization + Bank->>DB: 13a. Update consent status + DB-->>Bank: 14a. Confirm update + Bank-->>TPP: 15a. Authorization confirmation + else Manual Authorization + User->>TPP: 11b. Select manual authorization + TPP->>User: 12b. Redirect to authorization URL + User->>Auth: 13b. Authorization request with request_uri + Auth->>Bank: 14b. Verify consent + Bank->>DB: 15b. Get consent details + DB-->>Bank: 16b. Return consent details + Bank-->>Auth: 17b. Consent details + Auth->>User: 18b. Display authorization UI + User->>Auth: 19b. Approve authorization + Auth->>DB: 20b. Update consent status + DB-->>Auth: 21b. Confirm update + Auth->>User: 22b. Redirect to callback URL with code + User->>TPP: 23b. Callback with authorization code + end + + %% Payment Creation + TPP->>Gateway: 24. Create payment with authorized consent + Gateway->>Bank: 25. Forward payment request + Bank->>DB: 26. Store payment + DB-->>Bank: 27. Return payment ID + Bank-->>Gateway: 28. Payment response with PaymentId + Gateway-->>TPP: 29. Return PaymentId + + %% Payment Confirmation + TPP->>User: 30. Display payment confirmation + + %% Event Notification + Bank->>Kafka: 31. Publish payment event + Kafka->>Bank: 32. Stream processor consumes event + Bank->>DB: 33. Query subscriptions + DB-->>Bank: 34. Return matching subscriptions + Bank->>TPP: 35. Send payment notification + TPP-->>Bank: 36. Acknowledge notification +``` + +### Event Notification Example + +The event notification system allows TPPs to receive updates about payment status changes: + +```mermaid +sequenceDiagram + actor TPP as TPP Application + participant Gateway as API Gateway + participant EventAPI as Event Subscriptions API + participant PaymentAPI as Payment Initiation API + participant DB as Database + participant Kafka as Message Broker + + %% Subscription Registration + TPP->>Gateway: 1. Register callback URL + Gateway->>EventAPI: 2. Forward registration request + EventAPI->>DB: 3. Store subscription + DB-->>EventAPI: 4. Return subscription ID + EventAPI-->>Gateway: 5. Registration response with SubscriptionId + Gateway-->>TPP: 6. Return SubscriptionId + + %% Event Generation + Note over PaymentAPI: Payment status change or other event occurs + PaymentAPI->>Kafka: 7. Publish event + Note right of PaymentAPI: Event includes type, subject, timestamp + + %% Event Processing + Kafka-->>Gateway: 8. Consume event + Gateway->>DB: 9. Query subscriptions for event type + DB-->>Gateway: 10. Return matching subscriptions + Gateway->>Gateway: 11. Determine target TPPs and sign with JWS + + %% Notification Delivery + Gateway->>TPP: 12. Send signed notification + Note right of Gateway: Notification includes event details, links, and JWS signature + TPP->>TPP: 13. Verify JWS signature + TPP-->>Gateway: 14. Acknowledge (HTTP 200 OK) + + %% Error Handling (Alternative Flow) + alt Delivery Failure + Gateway->>TPP: 12. Send signed notification + TPP--xGateway: 13. Failed delivery (timeout/error) + Gateway->>Gateway: 14. Retry with exponential backoff + Gateway->>TPP: 15. Retry notification + TPP->>TPP: 16. Verify JWS signature + TPP-->>Gateway: 17. Acknowledge (HTTP 200 OK) + end +```