Skip to content

Add keyserver publishing foundation#37

Closed
YoungJinJung wants to merge 1 commit into
mainfrom
keyserver-foundation
Closed

Add keyserver publishing foundation#37
YoungJinJung wants to merge 1 commit into
mainfrom
keyserver-foundation

Conversation

@YoungJinJung

Copy link
Copy Markdown
Contributor

Summary

  • Add the keyserver service and storage/auth foundations.
  • Add CLI commands for keyserver login, publishing, and peer lookup.
  • Add compose smoke wiring and keyserver tests.

This is a prerequisite draft PR for the security hardening issues #30-#34 because those issues target files that are not present on main yet.

Validation

  • PYTHONPATH=. python3 -m unittest discover keyserver/tests
  • env -u GOROOT -u GOPATH /opt/homebrew/bin/go test ./...

Notes

The security hardening changes are intentionally split into stacked PRs so each issue can be reviewed independently.

Add the local keyserver service, CLI publish/login/fetch commands, compose smoke wiring, and tests as a reviewable baseline for the security hardening work stacked above it.

Constraint: Security fixes for issues #30-#34 target files that are not yet present on main.
Rejected: Fold all security fixes into one PR | would make review and issue closure harder.
Confidence: medium
Scope-risk: broad
Directive: Review this foundation before merging stacked hardening PRs into main.
Tested: PYTHONPATH=. python3 -m unittest discover keyserver/tests; env -u GOROOT -u GOPATH /opt/homebrew/bin/go test ./...
Not-tested: Docker compose smoke test was not run locally.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Security and Stability Review

This PR adds keyserver functionality but introduces critical security vulnerabilities that block merge. The implementation requires immediate fixes before it can be safely deployed.

Critical Issues Found (5)

Security Vulnerabilities:

  1. SSRF vulnerability in auth.py - External network requests are not validated, enabling access to internal services and AWS metadata endpoints
  2. Timing attack vulnerability in server.py - Admin token comparison is not constant-time, allowing token discovery
  3. OAuth phishing risk in keyserver_login_cmd.go - OAuth endpoints are not validated, enabling credential theft

Crash Risks:
4. JSON parsing in server.py - Missing exception handling causes server crashes on malformed requests
5. Port configuration in server.py - Invalid port values crash server on startup

Required Actions

All 5 critical issues must be resolved before merge. Code suggestions have been provided for the crash risks. The security vulnerabilities require design changes to properly validate URLs and implement constant-time comparisons.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment on lines +187 to +205
func deviceFlowConfig(provider, clientID, issuer string) (deviceEndpoint, tokenEndpoint, scope string, err error) {
switch provider {
case "github":
if clientID == "" {
return "", "", "", fmt.Errorf("--client-id is required for GitHub login")
}
return "https://github.com/login/device/code", "https://github.com/login/oauth/access_token", "read:user user:email", nil
case "okta":
if clientID == "" {
return "", "", "", fmt.Errorf("--client-id is required for Okta login")
}
issuer = strings.TrimRight(strings.TrimSpace(issuer), "/")
if issuer == "" {
return "", "", "", fmt.Errorf("--issuer is required for Okta login")
}
return issuer + "/v1/device/authorize", issuer + "/v1/token", "openid profile email", nil
default:
return "", "", "", fmt.Errorf("unsupported provider: %s", provider)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Hardcoded OAuth endpoints for github.com and okta enable attackers to phish credentials by manipulating the issuer parameter to redirect to malicious look-alike domains. An attacker could set issuer to "" and capture user credentials through a fake OAuth flow.

Comment on lines +103 to +106
def _get_json(url: str, headers: dict[str, str]) -> dict:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=10) as resp:
body = resp.read()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Unvalidated external network request enables Server-Side Request Forgery (SSRF)1. The _get_json function uses urllib.request.urlopen without validating the URL or IP address, allowing attackers to access internal services or AWS metadata endpoints by controlling the issuer parameter in Okta authentication.

Footnotes

  1. CWE-918: Server-Side Request Forgery (SSRF) - https://cwe.mitre.org/data/definitions/918.html

Comment on lines +82 to +89
raise ValueError("requested peer id does not match SSO identity mapping")
if requested_peer_id and not mapped and requested_peer_id != subject and requested_peer_id != subject.split("@", 1)[0]:
raise ValueError("requested peer id does not match SSO identity")
return peer_id


def _mapped_peer_id(provider: str, subject: str, email: str) -> str:
raw = os.getenv("ENDE_KEYSERVER_IDENTITY_MAP", "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Timing attack vulnerability in token comparison1. Using direct string comparison (==) at line 86 allows attackers to determine valid tokens character-by-character through response time analysis. The admin token should be compared using a constant-time comparison function.

Footnotes

  1. CWE-208: Observable Timing Discrepancy - https://cwe.mitre.org/data/definitions/208.html


def main() -> None:
host = os.getenv("ENDE_KEYSERVER_HOST", "127.0.0.1")
port = int(os.getenv("ENDE_KEYSERVER_PORT", "8765"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Crash Risk: Missing exception handling for invalid port number will crash the server on startup. If ENDE_KEYSERVER_PORT is set to a non-integer value, int() will raise ValueError and the server will fail to start.

Suggested change
port = int(os.getenv("ENDE_KEYSERVER_PORT", "8765"))
port = int(os.getenv("ENDE_KEYSERVER_PORT", "8765") or "8765")

Comment on lines +140 to +146
def _read_json(self) -> dict[str, Any]:
length = int(self.headers.get("Content-Length", "0"))
raw = self.rfile.read(length)
data = json.loads(raw.decode("utf-8"))
if not isinstance(data, dict):
raise ValueError("request body must be a JSON object")
return data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Crash Risk: Missing exception handling for json.loads and int conversion causes server to crash on malformed input. Line 141 will crash if Content-Length is not a valid integer, and line 143 will crash on invalid JSON, leaving the connection in a broken state and potentially causing the entire request handler thread to fail.

Suggested change
def _read_json(self) -> dict[str, Any]:
length = int(self.headers.get("Content-Length", "0"))
raw = self.rfile.read(length)
data = json.loads(raw.decode("utf-8"))
if not isinstance(data, dict):
raise ValueError("request body must be a JSON object")
return data
def _read_json(self) -> dict[str, Any]:
try:
length = int(self.headers.get("Content-Length", "0"))
except ValueError:
raise ValueError("invalid Content-Length header")
raw = self.rfile.read(length)
try:
data = json.loads(raw.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError) as e:
raise ValueError(f"invalid JSON: {e}")
if not isinstance(data, dict):
raise ValueError("request body must be a JSON object")
return data

@YoungJinJung

Copy link
Copy Markdown
Contributor Author

Closing because this was opened against the wrong repository. The security triage is being redone against companyjupiter/quarkify.

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.

1 participant