M2M Authentication

Machine-to-Machine (M2M) Authentication

This guide explains how to authenticate programmatically with SubImage using Machine-to-Machine (M2M) credentials for API automation, CI/CD pipelines, and external integrations.

Overview

Key Features

  • Role-Based Access Control: M2M applications inherit a specific role (member, operator, or admin)
  • Secure Token Exchange: Client secrets are only shown once during creation
  • Token Rotation: Easily rotate credentials without downtime

Use Cases

  • API Automation: Query security findings, inventory, and attack paths programmatically
  • CI/CD Integration: Integrate SubImage security checks into your deployment pipeline
  • Custom Dashboards: Build custom visualizations using SubImage data
  • MCP Server Access: Connect AI assistants to SubImage

Creating an M2M Application

Prerequisites

  • Admin role in your SubImage organization
  • Access to the SubImage Settings page

Step-by-Step Instructions

  1. Navigate to Settings

    • Log in to your SubImage tenant (e.g., <TENANT_URL>)
    • Click on Settings in the navigation menu
    • Select the API Keys tab
  2. Create New Application

    • Click the Create SubImage Application button
    • Fill in the application details:
      • Name: A descriptive name for your application (e.g., "CI/CD Pipeline", "Slack Bot")
      • Role: Choose the appropriate permission level
        • member: Read-only access to findings, inventory, and reports
        • operator: Full access to findings, inventory, and reports
        • admin: Full access including settings and user management
      • Description: Optional notes about the application's purpose
  3. Save Your Credentials

    • Click Create Application
    • IMPORTANT: The client_secret is displayed only once
    • Copy and securely store both:
      • client_id - Your application's public identifier
      • client_secret - Your application's secret key (treat like a password)
  4. Secure Storage

    • Store credentials in a secure password manager or secrets vault
    • Never commit credentials to version control
    • Use environment variables or secrets management tools in production

Using M2M Credentials

Authentication Flow

  1. Request Access Token - Exchange client credentials for an access token
  2. Use Access Token - Include the token in API requests as a Bearer token
  3. Token Expiration - Tokens expire after a set period; request a new token when needed

Example: Python

Here's a complete example of authenticating and using the SubImage MCP server:

import asyncio
import httpx
from fastmcp import Client

class SubImageM2MClient:
    """Client for SubImage M2M authentication."""

    def __init__(
        self,
        client_id: str,
        client_secret: str,
        tenant_url: str = "<TENANT_URL>",
        auth_url: str = "{{ AUTH_URL }}",
    ):
        self.client_id = client_id
        self.client_secret = client_secret
        self.auth_url = auth_url.rstrip("/")
        self.tenant_url = tenant_url
        self.access_token = None

    async def get_access_token(self) -> str:
        """
        Obtain an access token using client credentials.

        Returns:
            Access token string for API authentication
        """
        token_url = f"{self.auth_url}/oauth2/token"

        async with httpx.AsyncClient() as client:
            response = await client.post(
                token_url,
                data={
                    "grant_type": "client_credentials",
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                    "scope": "openid profile email",
                },
                headers={
                    "Content-Type": "application/x-www-form-urlencoded",
                },
            )

            response.raise_for_status()
            token_data = response.json()
            self.access_token = token_data["access_token"]

            return self.access_token

    async def query_api(self, endpoint: str) -> dict:
        """
        Query SubImage API with authenticated access.

        Args:
            endpoint: API endpoint path (e.g., "/api/findings")

        Returns:
            JSON response from the API
        """
        if not self.access_token:
            await self.get_access_token()

        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.tenant_url}{endpoint}",
                headers={
                    "Authorization": f"Bearer {self.access_token}",
                },
            )

            response.raise_for_status()
            return response.json()


async def main():
    # Initialize the client with your credentials
    client = SubImageM2MClient(
        client_id="client_ABC123",  # Replace with your client_id
        client_secret="secret_XYZ789",  # Replace with your client_secret
        tenant_url="<TENANT_URL>",  # Your tenant URL
    )

    # Get an access token
    access_token = await client.get_access_token()
    print(f"✓ Successfully authenticated")

    # Query the API
    findings = await client.query_api("/api/findings")
    print(f"Found {len(findings)} security findings")

    # Connect to MCP server for AI assistant access
    async with Client(
        f"{client.tenant_url}/mcp",
        auth=access_token,
    ) as mcp_client:
        tools = await mcp_client.list_tools()
        print(f"Available MCP tools: {len(tools)}")


if __name__ == "__main__":
    asyncio.run(main())

Example: cURL

For quick testing or shell scripts, you can use cURL:

# 1. Get access token
TOKEN_RESPONSE=$(curl -X POST {{ AUTH_URL }}/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=client_ABC123" \
  -d "client_secret=secret_XYZ789")

# Extract access token
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')

# 2. Query the API
curl <TENANT_URL>/api/findings \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example: Using Environment Variables

For better security, use environment variables:

# Set environment variables
export SUBIMAGE_CLIENT_ID="client_ABC123"
export SUBIMAGE_CLIENT_SECRET="secret_XYZ789"
export SUBIMAGE_TENANT_URL="<TENANT_URL>"

# Run your script
python your_script.py
import os

client = SubImageM2MClient(
    client_id=os.environ["SUBIMAGE_CLIENT_ID"],
    client_secret=os.environ["SUBIMAGE_CLIENT_SECRET"],
    tenant_url=os.environ["SUBIMAGE_TENANT_URL"],
)