From 104858ed8ea5853c419ffc835615d2b02195f92c Mon Sep 17 00:00:00 2001 From: Greg Meldrum Date: Mon, 19 Jan 2026 15:13:25 -0500 Subject: [PATCH 1/4] feat(a2a-proxy): add ssl_verify config option for self-signed certificates Add a boolean configuration option to disable SSL certificate verification for individual A2A proxied agents. This allows connecting to remote A2A agents using self-signed certificates in development/testing environments. Changes: - Add ssl_verify field to A2AProxiedAgentConfig (default: true) - Apply ssl_verify to httpx clients for agent card fetching and task invocation - Add documentation section on SSL verification with security warnings - Add example configuration in a2a_proxy_example.yaml - Add unit tests for the new configuration option Co-Authored-By: Claude --- docs/docs/documentation/components/proxies.md | 30 ++++++ examples/a2a_proxy_example.yaml | 6 ++ .../agent/proxies/a2a/component.py | 5 +- .../agent/proxies/a2a/config.py | 5 + tests/unit/agent/proxies/__init__.py | 0 tests/unit/agent/proxies/a2a/__init__.py | 0 tests/unit/agent/proxies/a2a/test_config.py | 96 +++++++++++++++++++ 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tests/unit/agent/proxies/__init__.py create mode 100644 tests/unit/agent/proxies/a2a/__init__.py create mode 100644 tests/unit/agent/proxies/a2a/test_config.py diff --git a/docs/docs/documentation/components/proxies.md b/docs/docs/documentation/components/proxies.md index 5f16151c8c..8410b7024d 100644 --- a/docs/docs/documentation/components/proxies.md +++ b/docs/docs/documentation/components/proxies.md @@ -194,6 +194,36 @@ The proxy caches OAuth tokens and automatically refreshes them when they expire. Always use environment variables for sensitive credentials. Never commit tokens or secrets directly in configuration files. ::: +## SSL Certificate Verification + +By default, the proxy verifies SSL certificates when connecting to downstream agents over HTTPS. You can disable certificate verification for specific agents using the `ssl_verify` configuration option. + +### When to Disable SSL Verification + +Disable SSL verification (`ssl_verify: false`) only in these scenarios: + +- **Development/Testing**: Connecting to agents with self-signed certificates in non-production environments +- **Internal Infrastructure**: Agents running on internal networks with private CA certificates not trusted by the system + +:::warning[Security Warning] +Disabling SSL verification removes protection against man-in-the-middle attacks. Never disable SSL verification in production environments unless you fully understand the security implications. +::: + +### Configuration + +```yaml +proxied_agents: + - name: "production-agent" + url: "https://api.example.com/agent" + ssl_verify: true # Default - verify against system CAs + + - name: "dev-agent-self-signed" + url: "https://dev.internal.example.com/agent" + ssl_verify: false # Disable verification for self-signed certs +``` + +The `ssl_verify` setting applies to both agent card fetching and task invocations for the configured agent. + ## Custom HTTP Headers The proxy supports custom HTTP headers for both agent card fetching and A2A task invocations. This is useful for scenarios like API versioning, tenant identification, custom authentication schemes, or any other header-based requirements. diff --git a/examples/a2a_proxy_example.yaml b/examples/a2a_proxy_example.yaml index 0fe1d3fd40..e433754eca 100644 --- a/examples/a2a_proxy_example.yaml +++ b/examples/a2a_proxy_example.yaml @@ -60,3 +60,9 @@ apps: # request_timeout_seconds: 180 # # use_agent_card_url: false # If true (default), uses URL from agent card for tasks. # # If false, uses configured URL directly for all calls. + + # Example 3: Agent with self-signed certificate (development/testing) + # - name: "DevAgent" + # url: "https://dev.internal.example.com/agent" + # ssl_verify: false # Disable SSL verification for self-signed certs + # # WARNING: Only use in development/testing environments diff --git a/src/solace_agent_mesh/agent/proxies/a2a/component.py b/src/solace_agent_mesh/agent/proxies/a2a/component.py index e2b9c8a497..b60276ec31 100644 --- a/src/solace_agent_mesh/agent/proxies/a2a/component.py +++ b/src/solace_agent_mesh/agent/proxies/a2a/component.py @@ -195,8 +195,9 @@ async def _fetch_agent_card( else: log.debug("%s Fetching agent card without authentication", log_identifier) + ssl_verify = agent_config.get("ssl_verify", True) log.info("%s Fetching agent card from %s", log_identifier, agent_url) - async with httpx.AsyncClient(headers=headers) as client: + async with httpx.AsyncClient(headers=headers, verify=ssl_verify) as client: resolver = A2ACardResolver(httpx_client=client, base_url=agent_url, agent_card_path=agent_card_path) agent_card = await resolver.get_agent_card() return agent_card @@ -844,6 +845,7 @@ async def _get_or_create_a2a_client( # Create a new httpx client with the specific timeout and custom headers for this agent # httpx.Timeout requires explicit values for connect, read, write, and pool + ssl_verify = agent_config.get("ssl_verify", True) httpx_client_for_agent = httpx.AsyncClient( timeout=httpx.Timeout( connect=agent_timeout, @@ -852,6 +854,7 @@ async def _get_or_create_a2a_client( pool=agent_timeout, ), headers=task_headers if task_headers else None, + verify=ssl_verify, ) if task_headers: diff --git a/src/solace_agent_mesh/agent/proxies/a2a/config.py b/src/solace_agent_mesh/agent/proxies/a2a/config.py index aa4d31b94a..5446054cf0 100644 --- a/src/solace_agent_mesh/agent/proxies/a2a/config.py +++ b/src/solace_agent_mesh/agent/proxies/a2a/config.py @@ -208,6 +208,11 @@ class A2AProxiedAgentConfig(ProxiedAgentConfig): "headers cannot override authentication. For custom auth, omit the 'authentication' " "config and use task_headers to set auth headers directly.", ) + ssl_verify: bool = Field( + default=True, + description="SSL certificate verification. Set to False to disable " + "verification for self-signed certificates.", + ) class A2AProxyAppConfig(BaseProxyAppConfig): diff --git a/tests/unit/agent/proxies/__init__.py b/tests/unit/agent/proxies/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/agent/proxies/a2a/__init__.py b/tests/unit/agent/proxies/a2a/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/agent/proxies/a2a/test_config.py b/tests/unit/agent/proxies/a2a/test_config.py new file mode 100644 index 0000000000..57f1854331 --- /dev/null +++ b/tests/unit/agent/proxies/a2a/test_config.py @@ -0,0 +1,96 @@ +"""Unit tests for A2A proxy configuration models.""" + +from solace_agent_mesh.agent.proxies.a2a.config import ( + A2AProxiedAgentConfig, + A2AProxyAppConfig, +) + + +class TestA2AProxiedAgentConfigSSLVerify: + """Tests for the ssl_verify configuration option.""" + + def test_ssl_verify_defaults_to_true(self): + """ssl_verify should default to True when not specified.""" + config = A2AProxiedAgentConfig( + name="test-agent", + url="https://example.com/agent", + ) + assert config.ssl_verify is True + + def test_ssl_verify_can_be_set_to_false(self): + """ssl_verify can be explicitly set to False.""" + config = A2AProxiedAgentConfig( + name="test-agent", + url="https://example.com/agent", + ssl_verify=False, + ) + assert config.ssl_verify is False + + def test_ssl_verify_can_be_set_to_true(self): + """ssl_verify can be explicitly set to True.""" + config = A2AProxiedAgentConfig( + name="test-agent", + url="https://example.com/agent", + ssl_verify=True, + ) + assert config.ssl_verify is True + + def test_ssl_verify_with_http_url(self): + """ssl_verify setting is accepted even with HTTP URLs.""" + config = A2AProxiedAgentConfig( + name="test-agent", + url="http://localhost:8080/agent", + ssl_verify=False, + ) + assert config.ssl_verify is False + + def test_ssl_verify_in_full_config(self): + """ssl_verify works alongside other configuration options.""" + config = A2AProxiedAgentConfig( + name="test-agent", + url="https://example.com/agent", + ssl_verify=False, + request_timeout_seconds=120, + use_auth_for_agent_card=True, + ) + assert config.ssl_verify is False + assert config.request_timeout_seconds == 120 + assert config.use_auth_for_agent_card is True + + +class TestA2AProxyAppConfigSSLVerify: + """Tests for ssl_verify in the full app configuration.""" + + def test_proxied_agent_with_ssl_verify_false(self): + """Full app config can include agents with ssl_verify=False.""" + config = A2AProxyAppConfig( + namespace="test/namespace", + proxied_agents=[ + { + "name": "secure-agent", + "url": "https://secure.example.com/agent", + "ssl_verify": True, + }, + { + "name": "self-signed-agent", + "url": "https://self-signed.example.com/agent", + "ssl_verify": False, + }, + ], + ) + assert len(config.proxied_agents) == 2 + assert config.proxied_agents[0].ssl_verify is True + assert config.proxied_agents[1].ssl_verify is False + + def test_proxied_agent_ssl_verify_defaults(self): + """Agents without ssl_verify specified should default to True.""" + config = A2AProxyAppConfig( + namespace="test/namespace", + proxied_agents=[ + { + "name": "default-agent", + "url": "https://example.com/agent", + }, + ], + ) + assert config.proxied_agents[0].ssl_verify is True From 1ec0ee0e22c782e87a2a331a6e4abc3db3f0091c Mon Sep 17 00:00:00 2001 From: Greg Meldrum Date: Wed, 11 Feb 2026 16:45:42 -0500 Subject: [PATCH 2/4] Fix merge with main --- tests/unit/agent/proxies/a2a/test_config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/agent/proxies/a2a/test_config.py b/tests/unit/agent/proxies/a2a/test_config.py index 82875edbd7..206c990fa1 100644 --- a/tests/unit/agent/proxies/a2a/test_config.py +++ b/tests/unit/agent/proxies/a2a/test_config.py @@ -549,5 +549,4 @@ def test_display_name_with_whitespace(self): url="https://example.com", display_name=" My Agent ", ) - assert config.display_name == " My Agent " ->>>>>>> main + assert config.display_name == " My Agent " \ No newline at end of file From 1d00a7ace5ac412bbefbb5c840ee22a3a456dce4 Mon Sep 17 00:00:00 2001 From: Greg Meldrum Date: Wed, 11 Feb 2026 16:55:08 -0500 Subject: [PATCH 3/4] fix: Clean up test file formatting after merge Remove duplicate docstring, add missing blank line between classes, and add trailing newline to satisfy ruff linting. Co-Authored-By: Claude Opus 4.5 --- tests/unit/agent/proxies/a2a/test_config.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/unit/agent/proxies/a2a/test_config.py b/tests/unit/agent/proxies/a2a/test_config.py index 206c990fa1..2c1848b659 100644 --- a/tests/unit/agent/proxies/a2a/test_config.py +++ b/tests/unit/agent/proxies/a2a/test_config.py @@ -1,9 +1,5 @@ """Unit tests for A2A proxy configuration models.""" -""" -Unit tests for A2A proxy configuration models with separate authentication. -""" - import pytest from pydantic import ValidationError @@ -12,6 +8,7 @@ A2AProxyAppConfig, ) + class TestA2AProxiedAgentConfigSSLVerify: """Tests for the ssl_verify configuration option.""" @@ -549,4 +546,4 @@ def test_display_name_with_whitespace(self): url="https://example.com", display_name=" My Agent ", ) - assert config.display_name == " My Agent " \ No newline at end of file + assert config.display_name == " My Agent " From a9d62dce9bd1e5237ba8f9060811cfb249d84ac0 Mon Sep 17 00:00:00 2001 From: Greg Meldrum Date: Thu, 12 Feb 2026 10:27:39 -0500 Subject: [PATCH 4/4] fix(DATAGO-122217): add warning log and fix code style - Add warning log when SSL verification is disabled - Fix missing blank lines between class definitions (PEP 8) Co-Authored-By: Claude Opus 4.5 --- .../agent/proxies/a2a/component.py | 14 ++++++++++++++ src/solace_agent_mesh/agent/proxies/a2a/config.py | 1 + tests/unit/agent/proxies/a2a/test_config.py | 2 ++ 3 files changed, 17 insertions(+) diff --git a/src/solace_agent_mesh/agent/proxies/a2a/component.py b/src/solace_agent_mesh/agent/proxies/a2a/component.py index 988f934ef4..8b7e8c58f6 100644 --- a/src/solace_agent_mesh/agent/proxies/a2a/component.py +++ b/src/solace_agent_mesh/agent/proxies/a2a/component.py @@ -628,6 +628,13 @@ async def _fetch_agent_card( log.debug("%s Fetching agent card without authentication", log_identifier) ssl_verify = agent_config.get("ssl_verify", True) + if not ssl_verify: + log.warning( + "%s SSL verification disabled for agent '%s'. " + "This should only be used in development environments.", + log_identifier, + agent_name, + ) log.info("%s Fetching agent card from %s", log_identifier, agent_url) async with httpx.AsyncClient(headers=headers, verify=ssl_verify) as client: resolver = A2ACardResolver(httpx_client=client, base_url=agent_url, agent_card_path=agent_card_path) @@ -1286,6 +1293,13 @@ async def _get_or_create_a2a_client( # Create a new httpx client with the specific timeout and custom headers for this agent # httpx.Timeout requires explicit values for connect, read, write, and pool ssl_verify = agent_config.get("ssl_verify", True) + if not ssl_verify: + log.warning( + "%s SSL verification disabled for agent '%s'. " + "This should only be used in development environments.", + self.log_identifier, + agent_name, + ) httpx_client_for_agent = httpx.AsyncClient( timeout=httpx.Timeout( connect=agent_timeout, diff --git a/src/solace_agent_mesh/agent/proxies/a2a/config.py b/src/solace_agent_mesh/agent/proxies/a2a/config.py index dca59c8f3b..6572f578c3 100644 --- a/src/solace_agent_mesh/agent/proxies/a2a/config.py +++ b/src/solace_agent_mesh/agent/proxies/a2a/config.py @@ -291,6 +291,7 @@ def validate_auth_configuration(self) -> "A2AProxiedAgentConfig": return self + class A2AProxyAppConfig(BaseProxyAppConfig): """Complete configuration for an A2A proxy application.""" diff --git a/tests/unit/agent/proxies/a2a/test_config.py b/tests/unit/agent/proxies/a2a/test_config.py index 2c1848b659..16910aef17 100644 --- a/tests/unit/agent/proxies/a2a/test_config.py +++ b/tests/unit/agent/proxies/a2a/test_config.py @@ -97,6 +97,8 @@ def test_proxied_agent_ssl_verify_defaults(self): ], ) assert config.proxied_agents[0].ssl_verify is True + + class TestSeparateAuthenticationFields: """Tests for agent_card_authentication and task_authentication fields."""