Skip to content

auth.m2m: YAML-configurable trusted keys for federated JWT-bearer grants#301

Merged
intel352 merged 6 commits intomainfrom
copilot/add-yaml-configurable-trusted-keys
Mar 12, 2026
Merged

auth.m2m: YAML-configurable trusted keys for federated JWT-bearer grants#301
intel352 merged 6 commits intomainfrom
copilot/add-yaml-configurable-trusted-keys

Conversation

Copy link
Contributor

Copilot AI commented Mar 11, 2026

Trusted external JWT issuers for the auth.m2m JWT-bearer grant could only be registered via Go code (AddTrustedKey), forcing consumers to write boilerplate that type-asserts into the service registry. This adds a trustedKeys YAML config path with audience validation and claim remapping.

Changes

module/auth_m2m.go

  • TrustedKeyConfig — exported struct for YAML/JSON config (issuer, publicKeyPEM, audiences, claimMapping)
  • trustedKeyEntry — internal struct replacing *ecdsa.PublicKey in the trustedKeys map; carries audiences + claim mapping alongside the key
  • AddTrustedKeyFromPEM(issuer, pem, audiences, claimMapping) — parses PKIX PEM public keys; normalises literal \n sequences for Docker/Kubernetes env var compatibility
  • AddTrustedKey() — backward-compatible; wraps key in trustedKeyEntry
  • validateJWTAssertion() — now enforces audience constraint and applies claim mapping from the matched entry
  • validateAssertionAudience() / applyAssertionClaimMapping() — extracted helpers

plugins/auth/plugin.go

  • auth.m2m factory parses the trustedKeys list and calls AddTrustedKeyFromPEM for each entry
  • Schema updated to document trustedKeys

Example

modules:
  - name: m2m-auth
    type: auth.m2m
    config:
      algorithm: ES256
      trustedKeys:
        - issuer: "https://legacy-platform.example.com"
          publicKeyPEM: "${LEGACY_JWT_PUBLIC_KEY}"  # literal \n in env vars handled automatically
          audiences: ["my-api"]
          claimMapping:
            user_id: sub   # external "user_id" → local "sub" in issued token
Original prompt

This section details on the original issue you should resolve

<issue_title>auth.m2m: YAML-configurable trusted keys for federated JWT-bearer grants</issue_title>
<issue_description>## Problem

The auth.m2m module supports JWT-bearer grants and exposes AddTrustedKey(issuer, publicKey) for registering external JWT issuers. However, trusted keys can only be registered via Go code — there is no YAML configuration path. This forces consumers to write boilerplate that parses PEM-encoded keys from environment variables and iterates the service registry to find the M2M module:

func RegisterTrustedKeys(engine *wf.StdEngine, logger *slog.Logger) {
    pubKeyPEM := os.Getenv("OAUTH2_JWT_PUBLIC_KEY")
    // parse PEM, handle escaped Docker env newlines...
    pubKey, _ := parseECPublicKey(pubKeyPEM)
    for name, svc := range engine.App().SvcRegistry() {
        if m2m, ok := svc.(*wfmodule.M2MAuthModule); ok {
            m2m.AddTrustedKey(issuer, pubKey)
        }
    }
}

Use Case: Federated Authentication

In multi-party systems, multiple services issue JWTs that need to be accepted by a single API:

  1. Platform migration: A legacy monolith (e.g., PHP/Java) issues JWTs for browser sessions. A new Go service must accept these tokens via JWT-bearer grant to issue its own access tokens. The legacy platform's public key must be trusted.

  2. Service mesh: Multiple microservices each have their own auth.m2m module. Service A needs to accept tokens from Service B as assertions for token exchange. Each service's JWKS endpoint or public key must be registered as a trusted issuer.

  3. Third-party integrations: External partners (e.g., an SSO provider, a customer's identity service) present JWTs that need to be exchanged for local access tokens.

In all cases, the trust relationship involves:

  • Issuer identification (URL, service name)
  • Key material (PEM public key, JWKS URL, or pre-shared symmetric key)
  • Algorithm constraints (ES256, RS256, etc.)
  • Claim mapping (which claims from the external JWT become claims in the local token)
  • Audience validation (the external JWT's aud must match this service)

Proposed Solution

Static trusted keys via YAML

modules:
  - name: m2m-auth
    type: auth.m2m
    config:
      algorithm: ES256
      trustedKeys:
        - issuer: "https://legacy-platform.example.com"
          algorithm: ES256
          publicKeyPEM: "${LEGACY_JWT_PUBLIC_KEY}"   # env var substitution
          audiences: ["chimera-api"]                  # optional audience check
          claimMapping:                               # optional claim remapping
            user_id: sub
            org_id: affiliate_id
        - issuer: "https://partner.example.com"
          jwksUrl: "https://partner.example.com/.well-known/jwks.json"
          jwksCacheTTL: 1h

JWKS URL discovery

For services that expose standard JWKS endpoints, the module should fetch and cache keys automatically rather than requiring static PEM configuration:

trustedKeys:
  - issuer: "https://auth.internal.example.com"
    jwksUrl: "https://auth.internal.example.com/oauth/jwks"
    jwksCacheTTL: 5m

PEM newline handling

Environment variables in Docker/Kubernetes can't contain literal newlines. The module should handle common escape patterns (\n as literal backslash-n) automatically when parsing PEM from env vars, rather than requiring each consumer to implement this workaround.

Current Workaround

Application code manually parses PEM keys with escaped newline handling, iterates the service registry, type-asserts to *wfmodule.M2MAuthModule, and calls AddTrustedKey(). This is fragile (depends on internal types), invisible to YAML config, and must be replicated by every consumer that needs federated auth.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…er grants

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copilot AI changed the title [WIP] Add YAML-configurable trusted keys for federated JWT-bearer grants auth.m2m: YAML-configurable trusted keys for federated JWT-bearer grants Mar 11, 2026
@intel352 intel352 marked this pull request as ready for review March 12, 2026 00:03
Copilot AI review requested due to automatic review settings March 12, 2026 00:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a YAML-configurable path for registering trusted external JWT issuers for the auth.m2m JWT-bearer grant, including optional audience validation and claim remapping, so consumers don’t need Go-side boilerplate to call AddTrustedKey manually.

Changes:

  • Extend auth.m2m to store per-trusted-issuer metadata (audiences + claim mapping) and add AddTrustedKeyFromPEM for PEM-based registration (with \n normalization).
  • Enforce audience checks and apply claim mapping during JWT-bearer assertion validation.
  • Update the auth plugin factory + schema and add tests covering trusted-keys configuration and the new behaviors.

Reviewed changes

Copilot reviewed 4 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
module/auth_m2m.go Adds trusted-key config/types, PEM registration helper, and audience + claim-mapping handling in JWT assertion validation.
plugins/auth/plugin.go Parses trustedKeys from module config and registers them; updates schema description to document the new config.
plugins/auth/plugin_test.go Adds a factory-level integration test ensuring YAML-configured trusted keys allow JWT-bearer grants.
module/auth_m2m_test.go Adds unit/integration tests for PEM parsing, newline normalization, audience enforcement, and claim mapping.
module/scan_provider_test.go Formatting-only adjustments.
module/platform_do_database.go Comment formatting-only adjustments.
module/platform_do_database_test.go Formatting-only adjustments.

ecKey, ok := pubAny.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("auth.m2m: public key for issuer %q is not an ECDSA key", issuer)
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

AddTrustedKeyFromPEM accepts any ECDSA public key returned by x509.ParsePKIXPublicKey, but the module docs/config state only ES256 (P-256) is supported. As written, a P-384/P-521 key could be registered and later validateJWTAssertion would accept assertions signed with ES384/ES512 (since it only checks SigningMethodECDSA), which expands the accepted algorithm set unexpectedly. Consider enforcing ecKey.Curve == elliptic.P256() here (and/or checking token.Header["alg"] == "ES256" in validateJWTAssertion) and returning an error otherwise.

Suggested change
}
}
if ecKey.Curve != elliptic.P256() {
return fmt.Errorf("auth.m2m: public key for issuer %q must use P-256 (ES256) curve", issuer)
}

Copilot uses AI. Check for mistakes.
Comment on lines +218 to +229
// Register YAML-configured trusted keys for JWT-bearer grants.
if trustedKeys, ok := cfg["trustedKeys"].([]any); ok {
for _, tk := range trustedKeys {
tkMap, ok := tk.(map[string]any)
if !ok {
continue
}
issuer := stringFromMap(tkMap, "issuer")
publicKeyPEM := stringFromMap(tkMap, "publicKeyPEM")
if issuer == "" || publicKeyPEM == "" {
continue
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The auth.m2m factory silently ignores malformed trustedKeys entries (non-map items, missing issuer/publicKeyPEM). For security-sensitive configuration this can lead to surprising runtime auth failures with no clear startup error. Consider failing module init (SetInitErr) with a descriptive error when an entry is invalid, rather than continuing silently.

Copilot uses AI. Check for mistakes.
@intel352
Copy link
Contributor

@copilot apply changes based on the comments in this thread

Build, tests, lint, config validation failing

@github-actions
Copy link

github-actions bot commented Mar 12, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:244: parsing iteration count: invalid syntax
baseline-bench.txt:306617: parsing iteration count: invalid syntax
baseline-bench.txt:596433: parsing iteration count: invalid syntax
baseline-bench.txt:884572: parsing iteration count: invalid syntax
baseline-bench.txt:1186922: parsing iteration count: invalid syntax
baseline-bench.txt:1482537: parsing iteration count: invalid syntax
benchmark-results.txt:244: parsing iteration count: invalid syntax
benchmark-results.txt:354449: parsing iteration count: invalid syntax
benchmark-results.txt:736356: parsing iteration count: invalid syntax
benchmark-results.txt:1094875: parsing iteration count: invalid syntax
benchmark-results.txt:1498943: parsing iteration count: invalid syntax
benchmark-results.txt:1885620: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │
                            │       sec/op       │
InterpreterCreation-4               7.200m ± 58%
ComponentLoad-4                     3.483m ±  5%
ComponentExecute-4                  2.026µ ±  2%
PoolContention/workers-1-4          1.103µ ±  3%
PoolContention/workers-2-4          1.092µ ±  1%
PoolContention/workers-4-4          1.102µ ±  1%
PoolContention/workers-8-4          1.098µ ±  2%
PoolContention/workers-16-4         1.095µ ±  2%
ComponentLifecycle-4                3.455m ±  0%
SourceValidation-4                  2.284µ ±  1%
RegistryConcurrent-4                771.9n ±  3%
LoaderLoadFromString-4              3.468m ±  0%
geomean                             18.60µ

                            │ baseline-bench.txt │
                            │        B/op        │
InterpreterCreation-4               1.944Mi ± 0%
ComponentLoad-4                     2.097Mi ± 0%
ComponentExecute-4                  1.203Ki ± 0%
PoolContention/workers-1-4          1.203Ki ± 0%
PoolContention/workers-2-4          1.203Ki ± 0%
PoolContention/workers-4-4          1.203Ki ± 0%
PoolContention/workers-8-4          1.203Ki ± 0%
PoolContention/workers-16-4         1.203Ki ± 0%
ComponentLifecycle-4                2.099Mi ± 0%
SourceValidation-4                  1.984Ki ± 0%
RegistryConcurrent-4                1.133Ki ± 0%
LoaderLoadFromString-4              2.099Mi ± 0%
geomean                             15.05Ki

                            │ baseline-bench.txt │
                            │     allocs/op      │
InterpreterCreation-4                15.09k ± 0%
ComponentLoad-4                      17.43k ± 0%
ComponentExecute-4                    25.00 ± 0%
PoolContention/workers-1-4            25.00 ± 0%
PoolContention/workers-2-4            25.00 ± 0%
PoolContention/workers-4-4            25.00 ± 0%
PoolContention/workers-8-4            25.00 ± 0%
PoolContention/workers-16-4           25.00 ± 0%
ComponentLifecycle-4                 17.48k ± 0%
SourceValidation-4                    32.00 ± 0%
RegistryConcurrent-4                  2.000 ± 0%
LoaderLoadFromString-4               17.47k ± 0%
geomean                               181.2

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                            │ benchmark-results.txt │
                            │        sec/op         │
InterpreterCreation-4                  6.003m ± 63%
ComponentLoad-4                        3.413m ±  5%
ComponentExecute-4                     1.934µ ±  1%
PoolContention/workers-1-4             1.201µ ±  2%
PoolContention/workers-2-4             1.203µ ±  1%
PoolContention/workers-4-4             1.201µ ±  1%
PoolContention/workers-8-4             1.208µ ±  1%
PoolContention/workers-16-4            1.235µ ±  2%
ComponentLifecycle-4                   3.552m ±  1%
SourceValidation-4                     2.242µ ±  1%
RegistryConcurrent-4                   905.6n ±  4%
LoaderLoadFromString-4                 3.579m ±  5%
geomean                                19.28µ

                            │ benchmark-results.txt │
                            │         B/op          │
InterpreterCreation-4                  1.944Mi ± 0%
ComponentLoad-4                        2.097Mi ± 0%
ComponentExecute-4                     1.203Ki ± 0%
PoolContention/workers-1-4             1.203Ki ± 0%
PoolContention/workers-2-4             1.203Ki ± 0%
PoolContention/workers-4-4             1.203Ki ± 0%
PoolContention/workers-8-4             1.203Ki ± 0%
PoolContention/workers-16-4            1.203Ki ± 0%
ComponentLifecycle-4                   2.099Mi ± 0%
SourceValidation-4                     1.984Ki ± 0%
RegistryConcurrent-4                   1.133Ki ± 0%
LoaderLoadFromString-4                 2.099Mi ± 0%
geomean                                15.05Ki

                            │ benchmark-results.txt │
                            │       allocs/op       │
InterpreterCreation-4                   15.09k ± 0%
ComponentLoad-4                         17.43k ± 0%
ComponentExecute-4                       25.00 ± 0%
PoolContention/workers-1-4               25.00 ± 0%
PoolContention/workers-2-4               25.00 ± 0%
PoolContention/workers-4-4               25.00 ± 0%
PoolContention/workers-8-4               25.00 ± 0%
PoolContention/workers-16-4              25.00 ± 0%
ComponentLifecycle-4                    17.48k ± 0%
SourceValidation-4                       32.00 ± 0%
RegistryConcurrent-4                     2.000 ± 0%
LoaderLoadFromString-4                  17.47k ± 0%
geomean                                  181.2

pkg: github.com/GoCodeAlone/workflow/middleware
cpu: AMD EPYC 7763 64-Core Processor                
                                  │ baseline-bench.txt │
                                  │       sec/op       │
CircuitBreakerDetection-4                  287.2n ± 4%
CircuitBreakerExecution_Success-4          22.53n ± 0%
CircuitBreakerExecution_Failure-4          65.38n ± 0%
geomean                                    75.06n

                                  │ baseline-bench.txt │
                                  │        B/op        │
CircuitBreakerDetection-4                 144.0 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │
                                  │     allocs/op      │
CircuitBreakerDetection-4                 1.000 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                  │ benchmark-results.txt │
                                  │        sec/op         │
CircuitBreakerDetection-4                     458.4n ± 3%
CircuitBreakerExecution_Success-4             59.86n ± 1%
CircuitBreakerExecution_Failure-4             64.77n ± 1%
geomean                                       121.1n

                                  │ benchmark-results.txt │
                                  │         B/op          │
CircuitBreakerDetection-4                    144.0 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

                                  │ benchmark-results.txt │
                                  │       allocs/op       │
CircuitBreakerDetection-4                    1.000 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
cpu: AMD EPYC 7763 64-Core Processor                
                                 │ baseline-bench.txt │
                                 │       sec/op       │
JQTransform_Simple-4                     914.2n ± 22%
JQTransform_ObjectConstruction-4         1.455µ ±  0%
JQTransform_ArraySelect-4                3.297µ ±  1%
JQTransform_Complex-4                    37.83µ ±  1%
JQTransform_Throughput-4                 1.798µ ±  1%
SSEPublishDelivery-4                     66.86n ±  0%
geomean                                  1.647µ

                                 │ baseline-bench.txt │
                                 │        B/op        │
JQTransform_Simple-4                   1.273Ki ± 0%
JQTransform_ObjectConstruction-4       1.773Ki ± 0%
JQTransform_ArraySelect-4              2.625Ki ± 0%
JQTransform_Complex-4                  16.22Ki ± 0%
JQTransform_Throughput-4               1.984Ki ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │
                                 │     allocs/op      │
JQTransform_Simple-4                     10.00 ± 0%
JQTransform_ObjectConstruction-4         15.00 ± 0%
JQTransform_ArraySelect-4                30.00 ± 0%
JQTransform_Complex-4                    324.0 ± 0%
JQTransform_Throughput-4                 17.00 ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                 │ benchmark-results.txt │
                                 │        sec/op         │
JQTransform_Simple-4                        909.6n ± 30%
JQTransform_ObjectConstruction-4            1.590µ ± 18%
JQTransform_ArraySelect-4                   3.409µ ±  4%
JQTransform_Complex-4                       36.46µ ±  2%
JQTransform_Throughput-4                    1.878µ ±  2%
SSEPublishDelivery-4                        72.87n ±  1%
geomean                                     1.705µ

                                 │ benchmark-results.txt │
                                 │         B/op          │
JQTransform_Simple-4                      1.273Ki ± 0%
JQTransform_ObjectConstruction-4          1.773Ki ± 0%
JQTransform_ArraySelect-4                 2.625Ki ± 0%
JQTransform_Complex-4                     16.22Ki ± 0%
JQTransform_Throughput-4                  1.984Ki ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                 │ benchmark-results.txt │
                                 │       allocs/op       │
JQTransform_Simple-4                        10.00 ± 0%
JQTransform_ObjectConstruction-4            15.00 ± 0%
JQTransform_ArraySelect-4                   30.00 ± 0%
JQTransform_Complex-4                       324.0 ± 0%
JQTransform_Throughput-4                    17.00 ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
cpu: AMD EPYC 7763 64-Core Processor                
                                    │ baseline-bench.txt │
                                    │       sec/op       │
SchemaValidation_Simple-4                    1.124µ ± 3%
SchemaValidation_AllFields-4                 1.690µ ± 6%
SchemaValidation_FormatValidation-4          1.599µ ± 1%
SchemaValidation_ManySchemas-4               1.820µ ± 3%
geomean                                      1.533µ

                                    │ baseline-bench.txt │
                                    │        B/op        │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │
                                    │     allocs/op      │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                    │ benchmark-results.txt │
                                    │        sec/op         │
SchemaValidation_Simple-4                       1.024µ ± 2%
SchemaValidation_AllFields-4                    1.515µ ± 4%
SchemaValidation_FormatValidation-4             1.476µ ± 2%
SchemaValidation_ManySchemas-4                  1.514µ ± 3%
geomean                                         1.364µ

                                    │ benchmark-results.txt │
                                    │         B/op          │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

                                    │ benchmark-results.txt │
                                    │       allocs/op       │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ baseline-bench.txt │
                                   │       sec/op       │
EventStoreAppend_InMemory-4                1.358µ ± 22%
EventStoreAppend_SQLite-4                  1.564m ±  9%
GetTimeline_InMemory/events-10-4           14.03µ ±  2%
GetTimeline_InMemory/events-50-4           70.13µ ± 14%
GetTimeline_InMemory/events-100-4          123.9µ ±  0%
GetTimeline_InMemory/events-500-4          638.7µ ±  1%
GetTimeline_InMemory/events-1000-4         1.304m ±  0%
GetTimeline_SQLite/events-10-4             112.0µ ±  1%
GetTimeline_SQLite/events-50-4             253.9µ ±  7%
GetTimeline_SQLite/events-100-4            427.8µ ±  1%
GetTimeline_SQLite/events-500-4            1.798m ±  0%
GetTimeline_SQLite/events-1000-4           3.516m ±  1%
geomean                                    224.7µ

                                   │ baseline-bench.txt │
                                   │        B/op        │
EventStoreAppend_InMemory-4                  782.5 ± 8%
EventStoreAppend_SQLite-4                  1.981Ki ± 2%
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%
geomean                                    67.28Ki

                                   │ baseline-bench.txt │
                                   │     allocs/op      │
EventStoreAppend_InMemory-4                  7.000 ± 0%
EventStoreAppend_SQLite-4                    53.00 ± 0%
GetTimeline_InMemory/events-10-4             125.0 ± 0%
GetTimeline_InMemory/events-50-4             653.0 ± 0%
GetTimeline_InMemory/events-100-4           1.306k ± 0%
GetTimeline_InMemory/events-500-4           6.514k ± 0%
GetTimeline_InMemory/events-1000-4          13.02k ± 0%
GetTimeline_SQLite/events-10-4               382.0 ± 0%
GetTimeline_SQLite/events-50-4              1.852k ± 0%
GetTimeline_SQLite/events-100-4             3.681k ± 0%
GetTimeline_SQLite/events-500-4             18.54k ± 0%
GetTimeline_SQLite/events-1000-4            37.29k ± 0%
geomean                                     1.162k

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                   │ benchmark-results.txt │
                                   │        sec/op         │
EventStoreAppend_InMemory-4                   1.134µ ±  5%
EventStoreAppend_SQLite-4                     930.9µ ±  3%
GetTimeline_InMemory/events-10-4              14.05µ ±  2%
GetTimeline_InMemory/events-50-4              76.30µ ±  4%
GetTimeline_InMemory/events-100-4             155.8µ ±  2%
GetTimeline_InMemory/events-500-4             772.9µ ± 21%
GetTimeline_InMemory/events-1000-4            1.231m ±  1%
GetTimeline_SQLite/events-10-4                84.83µ ±  1%
GetTimeline_SQLite/events-50-4                244.7µ ±  1%
GetTimeline_SQLite/events-100-4               441.4µ ±  2%
GetTimeline_SQLite/events-500-4               1.934m ±  3%
GetTimeline_SQLite/events-1000-4              3.723m ±  5%
geomean                                       217.3µ

                                   │ benchmark-results.txt │
                                   │         B/op          │
EventStoreAppend_InMemory-4                     799.0 ± 7%
EventStoreAppend_SQLite-4                     1.986Ki ± 1%
GetTimeline_InMemory/events-10-4              7.953Ki ± 0%
GetTimeline_InMemory/events-50-4              46.62Ki ± 0%
GetTimeline_InMemory/events-100-4             94.48Ki ± 0%
GetTimeline_InMemory/events-500-4             472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4            944.3Ki ± 0%
GetTimeline_SQLite/events-10-4                16.74Ki ± 0%
GetTimeline_SQLite/events-50-4                87.14Ki ± 0%
GetTimeline_SQLite/events-100-4               175.4Ki ± 0%
GetTimeline_SQLite/events-500-4               846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4              1.639Mi ± 0%
geomean                                       67.41Ki

                                   │ benchmark-results.txt │
                                   │       allocs/op       │
EventStoreAppend_InMemory-4                     7.000 ± 0%
EventStoreAppend_SQLite-4                       53.00 ± 0%
GetTimeline_InMemory/events-10-4                125.0 ± 0%
GetTimeline_InMemory/events-50-4                653.0 ± 0%
GetTimeline_InMemory/events-100-4              1.306k ± 0%
GetTimeline_InMemory/events-500-4              6.514k ± 0%
GetTimeline_InMemory/events-1000-4             13.02k ± 0%
GetTimeline_SQLite/events-10-4                  382.0 ± 0%
GetTimeline_SQLite/events-50-4                 1.852k ± 0%
GetTimeline_SQLite/events-100-4                3.681k ± 0%
GetTimeline_SQLite/events-500-4                18.54k ± 0%
GetTimeline_SQLite/events-1000-4               37.29k ± 0%
geomean                                        1.162k

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

…dling, test fixes, example go.mod

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot apply changes based on the comments in this thread

Build, tests, lint, config validation ...

Applied all review changes in 1cfb1c7:

  1. P-256 enforcement (module/auth_m2m.go): AddTrustedKeyFromPEM now rejects non-P-256 ECDSA keys (P-384/P-521) with a clear error.
  2. Malformed trustedKeys entries (plugins/auth/plugin.go): factory now calls SetInitErr with a descriptive error for non-map items, missing issuer, or missing publicKeyPEM — no more silent skips.
  3. Test error handling (module/auth_m2m_test.go): fixed discarded tok.SignedString errors in TestM2M_JWTBearer_AudienceMismatch and TestM2M_JWTBearer_ClaimMapping.
  4. CI build failure (example/go.mod): ran go mod tidy to update the example module's dependencies to match the parent module (pre-existing issue from the main merge).

Also added TestM2M_AddTrustedKeyFromPEM_NonP256Rejected, TestModuleFactoryM2MWithTrustedKeys_MissingIssuer, and TestModuleFactoryM2MWithTrustedKeys_MissingPEM for coverage of the new error paths.

@intel352 intel352 merged commit 4fefd86 into main Mar 12, 2026
11 checks passed
@intel352 intel352 deleted the copilot/add-yaml-configurable-trusted-keys branch March 12, 2026 04:17
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.

auth.m2m: YAML-configurable trusted keys for federated JWT-bearer grants

3 participants