Skip to content

feat(server): support A2A protocol#2656

Open
Tyooughtul wants to merge 8 commits intoapache:masterfrom
Tyooughtul:feat/server/a2a-jwt-jwks
Open

feat(server): support A2A protocol#2656
Tyooughtul wants to merge 8 commits intoapache:masterfrom
Tyooughtul:feat/server/a2a-jwt-jwks

Conversation

@Tyooughtul
Copy link

Which issue does this PR close?

Closes #1762

Rationale

A2A protocol requires JWKS support to enable secure agent authentication with multiple identity providers. This change allows agents from different tenants to authenticate using their own public keys, and supports key rotation without requiring server restarts.

What changed?

Added JWKS support for secure agent-to-agent authentication. The implementation includes a JwksClient that fetches and caches public keys from JWKS endpoints, integrated JWKS into JwtManager for multi-tenant agent authentication, and updated HTTP middleware to support asynchronous JWT decoding. Also added TrustedIssuerConfig to support configuring multiple trusted issuers.

Local Execution

  • Passed
  • Pre-commit hooks ran

AI Usage

  1. Which tools? Grok fast
  2. Scope of usage?
  • I use ai for write test case and running scripts.
  • Some config code to test code:
# Trusted issuers for A2A (Application-to-Application) authentication
[[http.jwt.trusted_issuers]]
issuer = "test-issuer"
jwks_url = "http://127.0.0.1:8081/.well-known/jwks.json"
audience = "iggy.apache.org"
  • Some debug! to help me find bugs。
  1. How did you verify the generated code works correctly?
  • Compile successfully with cargo check --package server and cargo build --package server.
  • Test case passed.
  1. Can you explain every line of the code if asked? Yes

@Tyooughtul Tyooughtul closed this Jan 31, 2026
@Tyooughtul Tyooughtul reopened this Jan 31, 2026
@hubcio
Copy link
Contributor

hubcio commented Jan 31, 2026

hey! thanks for contribution - we'll check this after the weekend.

@spetz
Copy link
Contributor

spetz commented Feb 2, 2026

Thank you for the contribution, made a few comments here and there :)

Is that all required to fully support A2A, as you wrote that I'd close #1762 which is the full integration?

Also, is there a way to do the proper integration/e2e testing like e..g for existing MCP runtime to ensure it works well with A2A as the full transport?

@Tyooughtul
Copy link
Author

Thanks for the review! All comments are clear and I will address every point as suggested.
I think this PR covers the full A2A support as mentioned in #1762. I will also add the corresponding integration/e2e tests for the MCP runtime & A2A.

@hubcio
Copy link
Contributor

hubcio commented Feb 3, 2026

when testing, see how iggy_harness macro is used for connectors in #2667 or mcp (already merged). we're in the middle of refactor to use it everywhere, so it'd be great if you could use it in your tests (assuming you'll write some tests for this A2A).

@hubcio hubcio changed the title feat(server): Support A2A protocol (apache#1762) feat(server): support A2A protocol Feb 3, 2026
@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 79.52381% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.40%. Comparing base (33bee3d) to head (7200693).
⚠️ Report is 28 commits behind head on master.

Files with missing lines Patch % Lines
core/server/src/http/jwt/jwks.rs 81.69% 20 Missing and 6 partials ⚠️
core/server/src/http/jwt/jwt_manager.rs 75.00% 14 Missing and 1 partial ⚠️
core/configs/src/server_config/defaults.rs 0.00% 1 Missing ⚠️
core/server/src/http/jwt/middleware.rs 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2656      +/-   ##
============================================
+ Coverage     68.33%   68.40%   +0.06%     
  Complexity      739      739              
============================================
  Files          1053     1054       +1     
  Lines         84763    84955     +192     
  Branches      61297    61499     +202     
============================================
+ Hits          57923    58111     +188     
+ Misses        24468    24460       -8     
- Partials       2372     2384      +12     
Flag Coverage Δ
csharp 67.43% <ø> (-0.19%) ⬇️
go 6.27% <ø> (ø)
java 54.83% <ø> (ø)
node 92.26% <ø> (-0.04%) ⬇️
python 81.57% <ø> (ø)
rust 70.12% <79.52%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
core/common/src/error/iggy_error.rs 100.00% <ø> (ø)
core/configs/src/server_config/http.rs 66.66% <ø> (ø)
core/configs/src/server_config/defaults.rs 0.00% <0.00%> (ø)
core/server/src/http/jwt/middleware.rs 80.00% <85.71%> (+12.69%) ⬆️
core/server/src/http/jwt/jwt_manager.rs 61.25% <75.00%> (+7.12%) ⬆️
core/server/src/http/jwt/jwks.rs 81.69% <81.69%> (ø)

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hubcio
Copy link
Contributor

hubcio commented Feb 6, 2026

It looks like something went wrong with your rebase:
image

@Tyooughtul Tyooughtul force-pushed the feat/server/a2a-jwt-jwks branch 2 times, most recently from bd98498 to b85afac Compare February 6, 2026 16:11
@Tyooughtul
Copy link
Author

It looks like something went wrong with your rebase: image

😱 sorry, I fetched the wrong branch. I have corrected it.

@Tyooughtul Tyooughtul force-pushed the feat/server/a2a-jwt-jwks branch 2 times, most recently from 28f9cb5 to dff37e2 Compare February 13, 2026 14:11
@hubcio
Copy link
Contributor

hubcio commented Feb 19, 2026

Hello @Tyooughtul
did you resolve all comments from @spetz ?
Do you plan to continue?

@Tyooughtul
Copy link
Author

Hello @Tyooughtul did you resolve all comments from @spetz ? Do you plan to continue?

Hi @hubcio , I think I’ve addressed all comments from @spetz, and the PR is ready for review. I’ll keep following up and fix any issues promptly. 😊

@Tyooughtul
Copy link
Author

Hi @spetz @hubcio,
I've addressed all the previous review comments. Could you please take another look when you have a moment? Thanks!
Also, could you let me know if a second review from another maintainer is needed to meet the merge requirements?
Additionally, it seems the workflow is awaiting approval from a maintainer to run the CI checks. Would you mind approving that as well?
Thanks for your time!

@spetz
Copy link
Contributor

spetz commented Feb 21, 2026

@Tyooughtul sure, the CI has started again, however I can see that there are still some pending comments waiting to be resolved.

@Tyooughtul Tyooughtul force-pushed the feat/server/a2a-jwt-jwks branch from 24f9624 to 0b3ec9f Compare February 22, 2026 02:12
@Tyooughtul
Copy link
Author

Tyooughtul commented Feb 22, 2026

Hi @spetz,
All review comments have been addressed and pushed, All newly added tests pass. PTAL when you have time, thanks! 😊

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs.

If you need a review, please ensure CI is green and the PR is rebased on the latest master. Don't hesitate to ping the maintainers - either @core on Discord or by mentioning them directly here on the PR.

Thank you for your contribution!

@github-actions github-actions bot added the stale Inactive issue or pull request label Mar 5, 2026
- Support JWKS for A2A compliant secure agent authentication
- Enable key rotation without restarting the server
- Allow agents from different tenants to publish to the same Iggy bus

rebase to the newest master
…ness macro

Extend `#[iggy_harness]` with `jwks_server(...)` attribute to support
declarative JWKS mock server setup, as suggested in review to follow
the harness macro convention used for MCP and connectors.
- Fix the problem as suggested
- Add `jwks_server(store_path = "...")` attribute to #[iggy_harness]
- Add `config_path` to server(...) for custom TOML via IGGY_CONFIG_PATH
- Start WireMock MockServer and inject trusted issuer env vars before
  server startup
- Add ServerHandle::add_env() for pre-start env var injection
- Add 4 e2e tests: valid_token, expired_token, unknown_issuer,
  missing_token with RSA key pair and JWKS fixtures
@hubcio
Copy link
Contributor

hubcio commented Mar 5, 2026

@Tyooughtul could you please rebase this PR?

@Tyooughtul
Copy link
Author

@Tyooughtul could you please rebase this PR?

Yes, I'm working on it. 😊

@Tyooughtul Tyooughtul force-pushed the feat/server/a2a-jwt-jwks branch from 57c1658 to b18459c Compare March 5, 2026 11:43
@Tyooughtul Tyooughtul requested a review from spetz March 5, 2026 11:43
@Tyooughtul Tyooughtul force-pushed the feat/server/a2a-jwt-jwks branch from b18459c to e8ff862 Compare March 5, 2026 12:21
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there python under rust integration tests?

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/TvzTyulqS+s3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the example certs under core/certs.

}
};

// debug!("Found trusted issuer config: {}", config.issuer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove such comments.

}
};

// debug!("Got decoding key from JWKS for kid: {}", kid_str);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this one, and any others similar to this one (commented out line of code).

.http_addr()
.expect("http address should be available");

let client = Client::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use Iggy HTTP client?

}
};

/* debug!(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get rid of any commented out code.

IggyError::InvalidJwtAlgorithm(Self::map_algorithm_to_string(algorithm))
})?;

jsonwebtoken::decode::<JwtClaims>(token, &self.validator.key, validation)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe let's log with error! macro here and don't discard it in map_err?

}

let request_details = request.extensions().get::<RequestDetails>().unwrap();
let user_id = jwt_claims
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sub now uses string instead of u32 yet we expect it to be parsed to numeric value - in such a case, why it's been changed to string?

@spetz
Copy link
Contributor

spetz commented Mar 5, 2026

Added a few more comments (some might be stale already, but some are up to date) - please take a look, and once resolved we could finally merge it :)

@github-actions github-actions bot removed the stale Inactive issue or pull request label Mar 6, 2026
@Tyooughtul
Copy link
Author

@spetz Thanks for your comment and review, it helps me a lot 😊!

I've got the A2A JWT authentication working end-to-end for HTTP with integration test coverage, external IdPs can now authenticate users via JWKS endpoints. That said, I'm running into a few architectural rough edges that I'd rather hash out with you before polishing this further. The immediate concern is identity mapping: I'm currently using the external JWT's 'sub' claim directly as the Iggy user_id, which feels like we're begging for namespace collisions between external issuers and our internal IDs. Should I add a claims mapping layer to transform something like "oidc|user123" into a proper local user_id? Related to that, the permission model currently grants A2A tokens full user privileges, which might be too permissive if these tokens leak🤔

Looking ahead to the VSR clustering work, I took another look at the doc and realized the shared-nothing architecture is actually a core philosophy.So the local-only JWKS cache and revocation lists might actually be fine with short TTLs rather than trying to synchronize state across nodes. That leaves me wondering: should each node just fetch JWKS independently, or is there a better approach? There's also the protocol scope to consider: JWT authentication currently only works for HTTP, so should I plan to extend this to TCP, QUIC, and WebSocket as well, or is HTTP-only the intended scope for now? Would love your guidance on these points!

@hubcio
Copy link
Contributor

hubcio commented Mar 10, 2026

@Tyooughtul good questions, but don't worry about VSR here and don't expand the scope - let's finish this PR first. There are still a few unresolved inline comments from @spetz's latest review - please mark them as resolved if these are already fixed.

As for the architectural stuff:

  • Identity mapping / permissions- valid concerns, but let's track them as separate issues. No need to solve everything in one PR.
  • JWKS cache - local-only with short TTLs is totally fine. Fits our shared-nothing model, no need to sync state across nodes.
  • Protocol - HTTP-only is good enough for now. We can always extend to other transports later if there's demand.

For clustering: JWKS keys could be replicated via the metadata plane, same way PATs are - one node fetches, all replicas get them through VSR. No need for each node to hit external IdP endpoints independently.

@spetz
Copy link
Contributor

spetz commented Mar 13, 2026

@Tyooughtul as @hubcio already suggested, let's stick to HTTP as it's the only supported transport protocol AFAIR.
As for the sub claim, since it's the universal way of passing the actual user ID, we could stick to that cause we'd probably still need to create the actual Iggy user for external issuer anyway?

@Tyooughtul
Copy link
Author

@hubcio @spetz Thanks for your answers and responses! 😊 I'm working hard to modify the code these days. I will push the code after the test is passed, but I'm currently dealing with the issue of test failure, I might need some time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integration with Google Agent2Agent Protocol

3 participants