Skip to content

For Security Review: Management API Authentication#861

Open
Sneagan wants to merge 13 commits intomasterfrom
feature/issuer-management-api-minimal
Open

For Security Review: Management API Authentication#861
Sneagan wants to merge 13 commits intomasterfrom
feature/issuer-management-api-minimal

Conversation

@Sneagan
Copy link
Collaborator

@Sneagan Sneagan commented Feb 12, 2026

Overview

This PR introduces the authentication and authorization protocol for a new Management API. To facilitate security review, this PR includes only the signature verification system and a single read-only endpoint to demonstrate the complete security flow.

What This PR Adds

A secure authentication system based on Ed25519 signatures for administrative API access:

  1. Signature Verification (server/signature.go)

    • Ed25519 public-key cryptography for request authentication
    • Constant-time comparison to prevent timing attacks
    • Request signing over: METHOD\nPATH?QUERY\nTIMESTAMP\nBODY
    • Validates: signature, timestamp freshness (±5 minutes), authorized signer
  2. Rate Limiting (server/ratelimit.go)

    • Per-IP sliding window rate limiting (default: 60 req/min)
    • Zero external dependencies (stdlib only)
    • Applied to all management endpoints via middleware
  3. Demo Endpoint (GET /api/v1/manage/issuers)

    • Single read-only endpoint demonstrating full auth flow
    • Intentionally minimal to focus review on security primitives
    • Additional endpoints will follow in separate PRs after security approval

Security Review Focus Areas

Critical - Please Review:

  1. Signature verification logic - Is the signature scheme correctly implemented?
  2. Timing attack protection - Verify constant-time comparison usage
  3. Replay attack prevention - Is timestamp validation sufficient?
  4. Authorization model - Public key whitelist approach acceptable?
  5. Rate limiting - Adequacy of limits and implementation
  6. Header validation - Proper validation of X-Signature/X-Public-Key/X-Timestamp headers. Note that X-Forwarded-For is trusted for rate limiting. This is sufficient now that the challenge bypass ALB is private.

Known Limitations (by design):

  • Authorized signers currently configured in code (server/signature.go:47-51)
  • No key rotation mechanism for authorized signers. Code change required.
  • Rate limiter uses in-memory storage (resets on server restart)

Request Signature Format

All management endpoints require three headers:
X-Signature:
X-Public-Key:
X-Timestamp:

The signature signs the concatenation:
METHOD + "\n" + PATH + QUERY + "\n" + TIMESTAMP + "\n" + BODY

Threat Model Addressed

Authentication: Only holders of authorized private keys can call API
Integrity: Signatures prevent request tampering
Replay attacks: Timestamp validation (5-minute window)
DoS: Rate limiting per IP address
Timing attacks: Constant-time comparison for signature/key checking

Out of Scope (Future PRs)

  • Write operations (POST/PUT/DELETE endpoints)
  • Key management operations
  • Audit logging
  • Metrics/monitoring dashboards

Note: This PR intentionally contains minimal functionality (one read-only endpoint) to enable focused
security review of the authentication primitives. Once approved, additional management endpoints will be added in separate PRs using the same authentication foundation.

@github-actions
Copy link

The security team is monitoring all repositories for certain keywords. This PR includes the word(s) "cryptography, authentication, authorization, secure" and so security team members have been added as reviewers to take a look.

No need to request a full security review at this stage, the security team will take a look shortly and either clear the label or request more information/changes.

Notifications have already been sent, but if this is blocking your merge feel free to reach out directly to the security team on Slack so that we can expedite this check.

@Sneagan Sneagan marked this pull request as ready for review February 12, 2026 18:32
@Sneagan Sneagan changed the title Feature/issuer management api minimal For Security Review: Management API Authentication Feb 12, 2026
Copy link
Collaborator

@rmerry rmerry left a comment

Choose a reason for hiding this comment

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

Reviewed and tested the code locally, looks great to me. A couple of comments mainly for my own benefit.

// Allow checks if a request from the given IP should be allowed
// Uses a sliding window algorithm
func (rl *RateLimiter) Allow(ip string) bool {
limiter := rl.getLimiter(ip)
Copy link
Collaborator

Choose a reason for hiding this comment

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

as the rl.mu is locked and then released in the getLimiter function is it possible that this limiter could be deleted by the clean up routine as we're using it here... if a subsequent Allow() call then came in for the same IP we would then be operating on two limiters for the same IP. Seems very unlikely and I might be wrong, just through I'd point it out as a potential issue. The way around that would be to lock the rl.mu for the duration of the Allow call not just the getLimiter call but I'm not sure that's a good idea.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think you're right that the case is possible but it would be quite a race, as the next line after getLimiter, which unlocks in defer, is to lock the gotten limiter. Because cleanup is hourly and management use should be rare (a few dozen calls a month in most cases) I think this is tolerable.

// Performs checks in order from cheapest to most expensive for optimal performance.
func VerifySignedRequest(r *http.Request) ([]byte, error) {
// Step 1: Check if authorized signers are configured (cheapest - single check)
if len(AuthorizedSigners) == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there any point in this optimisation---in reality will there not always be a nonempty list of AuthorizedSigners?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. I just want to handle the case explicitly so that there's never any future ambiguity about what to do in the absence of signers. For example, someone naive might make it pass when empty for testing purposes if the expectation is not communicated clearly.

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.

4 participants