Skip to content

Updating validation for the Sysdig public API url #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ app:

sysdig:
host: "https://us2.app.sysdig.com"
# public_api_url: "https://<YOUR_CUSTOM_PUBLIC_API_URL>"

mcp:
transport: stdio
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sysdig-mcp-server"
version = "0.1.3"
version = "0.1.4"
description = "Sysdig MCP Server"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
49 changes: 49 additions & 0 deletions tests/config_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
Testing overall configuration of the MCP server.
"""


def test_api_url_format() -> None:
"""
Test that the API URL is formatted correctly.
This test checks if the public API URL is constructed properly
when the old API format is not used.
"""
from utils.sysdig.client_config import _get_public_api_url

# URL regions refer to https://docs.sysdig.com/en/administration/saas-regions-and-ip-ranges/
region_urls = {
"us1": {"url": "https://secure.sysdig.com", "public_url": "https://api.us1.sysdig.com"},
"us2": {"url": "https://us2.app.sysdig.com", "public_url": "https://api.us2.sysdig.com"},
"eu1": {"url": "https://eu1.app.sysdig.com", "public_url": "https://api.eu1.sysdig.com"},
"au1": {"url": "https://app.au1.sysdig.com", "public_url": "https://api.au1.sysdig.com"},
"me2": {"url": "https://app.me2.sysdig.com", "public_url": "https://api.me2.sysdig.com"},
# Edge case that does not follow the standard pattern it should return the same passed URL
"edge": {"url": "https://edge.something.com", "public_url": "https://edge.something.com"},
}

# Check if the public API URL is formatted correctly
public_api_us1 = _get_public_api_url(region_urls["us1"]["url"])
public_api_us2 = _get_public_api_url(region_urls["us2"]["url"])
public_api_eu1 = _get_public_api_url(region_urls["eu1"]["url"])
public_api_au1 = _get_public_api_url(region_urls["au1"]["url"])
public_api_me2 = _get_public_api_url(region_urls["me2"]["url"])
public_api_edge = _get_public_api_url(region_urls["edge"]["url"])

assert public_api_us1 == region_urls["us1"]["public_url"], (
f"Expected {region_urls['us1']['public_url']}, got {public_api_us1}"
)
assert public_api_us2 == region_urls["us2"]["public_url"], (
f"Expected {region_urls['us2']['public_url']}, got {public_api_us2}"
)
assert public_api_eu1 == region_urls["eu1"]["public_url"], (
f"Expected {region_urls['eu1']['public_url']}, got {public_api_eu1}"
)
assert public_api_au1 == region_urls["au1"]["public_url"], (
f"Expected {region_urls['au1']['public_url']}, got {public_api_au1}"
)
assert public_api_me2 == region_urls["me2"]["public_url"], (
f"Expected {region_urls['me2']['public_url']}, got {public_api_me2}"
)
assert not public_api_edge, f"Expected empty string, got {public_api_edge}"
print("All public API URLs are formatted correctly.")
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ def mock_creds():
Fixture to set up mocked credentials.
"""
os.environ["SYSDIG_SECURE_TOKEN"] = "mocked_token"
os.environ["SYSDIG_HOST"] = "https://mocked.secure"
os.environ["SYSDIG_HOST"] = "https://us2.app.sysdig.com"
3 changes: 2 additions & 1 deletion tests/events_feed_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def test_get_event_info(mock_success_response: MagicMock | AsyncMock, mock_creds
mock_success_response (MagicMock | AsyncMock): Mocked response object.
mock_creds: Mocked credentials.
"""

# Override the environment variable for MCP transport
os.environ["MCP_TRANSPORT"] = "stdio"
# Successful response
mock_success_response.return_value.json.return_value = EVENT_INFO_RESPONSE
mock_success_response.return_value.status_code = HTTPStatus.OK
Expand Down
59 changes: 46 additions & 13 deletions utils/sysdig/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
import re
from typing import Optional

# Application config loader
from utils.app_config import get_app_config

# Set up logging
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=os.environ.get("LOGLEVEL", "ERROR"))
log = logging.getLogger(__name__)

app_config = get_app_config()


# Lazy-load the Sysdig client configuration
def get_configuration(
Expand All @@ -22,18 +27,36 @@ def get_configuration(

Args:
token (str): The Sysdig Secure token.
sysdig_host_url (str): The base URL of the Sysdig API.
old_api (bool): If True, uses the old Sysdig API URL format. Defaults to False.
sysdig_host_url (str): The base URL of the Sysdig API,
refer to the docs https://docs.sysdig.com/en/administration/saas-regions-and-ip-ranges/#sysdig-platform-regions.
old_api (bool): If True, uses the old Sysdig API URL format.
Defaults to False using the public API URL format https://api.{region}.sysdig.com.
Returns:
sysdig_client.Configuration: A configured Sysdig client instance.
Raises:
ValueError: If the Sysdig host URL is not provided or is invalid.
"""
# Check if the token and sysdig_host_url are provided, otherwise fetch from environment variables
if not token and not sysdig_host_url:
env_vars = get_api_env_vars()
token = env_vars["SYSDIG_SECURE_TOKEN"]
sysdig_host_url = env_vars["SYSDIG_HOST"]
if not old_api:
"""
Client expecting the public API URL in the format https://api.{region}.sysdig.com. We will check the following:
- A valid Sysdig host URL is provided by matching the expected patterns with a regex.
- If not, we will try to fetch the public API URL from the app config yaml 'sysdig.public_api_url'.
- If neither is available, we will raise an error.
"""
sysdig_host_url = _get_public_api_url(sysdig_host_url)
if not sysdig_host_url:
sysdig_host_url = app_config.get("sysdig", {}).get("public_api_url")
if not sysdig_host_url:
raise ValueError(
"No valid Sysdig public API URL found. Please check your Sysdig host URL or"
"explicitly set the public API URL in the app config 'sysdig.public_api_url'."
"The expected format is https://api.{region}.sysdig.com."
)
log.info(f"Using public API URL: {sysdig_host_url}")

configuration = sysdig_client.Configuration(
Expand Down Expand Up @@ -67,20 +90,30 @@ def get_api_env_vars() -> dict:

def _get_public_api_url(base_url: str) -> str:
"""
Get the public API URL from the base URL.
Maps a Sysdig base URL to its corresponding public API URL.
This function extracts the region from the base URL and constructs the public API URL in the format
https://api.{region}.sysdig.com.

If the base URL does not match any known patterns, it returns an empty string.

Args:
base_url: The base URL of the Sysdig API

Returns:
str: The public API URL in the format https://api.<region>.sysdig.com
str: The public API URL in the format https://api.{region}.sysdig.com
"""
# Regex to capture the region pattern (like us2, us3, au1, etc.)
# This assumes the region is a subdomain that starts with 2 lowercase letters and ends with a digit
pattern = re.search(r"https://(?:(?P<region1>[a-z]{2}\d)\.app|app\.(?P<region2>[a-z]{2}\d))\.sysdig\.com", base_url)
if pattern:
region = pattern.group("region1") or pattern.group("region2") # Extract the region
return f"https://api.{region}.sysdig.com"
else:
# Edge case for the secure API URL that is us1
return "https://api.us1.sysdig.com"

patterns = [
(r"^https://secure\.sysdig\.com$", lambda m: "us1"),
(r"^https://([a-z]{2}\d)\.app\.sysdig\.com$", lambda m: m.group(1)),
(r"^https://app\.([a-z]{2}\d)\.sysdig\.com$", lambda m: m.group(1)),
]

for pattern, region_fn in patterns:
match = re.match(pattern, base_url)
if match:
region = region_fn(match)
return f"https://api.{region}.sysdig.com"

log.warning("A not recognized Sysdig URL was provided, returning an empty string. This may lead to unexpected behavior.")
return ""
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.