For Security Review: Management API Authentication#861
For Security Review: Management API Authentication#861
Conversation
|
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. |
rmerry
left a comment
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
is there any point in this optimisation---in reality will there not always be a nonempty list of AuthorizedSigners?
There was a problem hiding this comment.
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.
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:
Signature Verification (
server/signature.go)METHOD\nPATH?QUERY\nTIMESTAMP\nBODYRate Limiting (
server/ratelimit.go)Demo Endpoint (
GET /api/v1/manage/issuers)Security Review Focus Areas
Critical - Please Review:
Known Limitations (by design):
server/signature.go:47-51)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)
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.