mirror of
https://github.com/neondatabase/neon.git
synced 2025-12-22 21:59:59 +00:00
Remove compute_ctl authorization bypass if testing feature was enable (#11596)
We want to exercise the authorization middleware in our regression tests. Signed-off-by: Tristan Partin <tristan@neon.tech>
This commit is contained in:
@@ -6,4 +6,5 @@ pub(crate) mod request_id;
|
||||
pub(crate) use json::Json;
|
||||
pub(crate) use path::Path;
|
||||
pub(crate) use query::Query;
|
||||
#[allow(unused)]
|
||||
pub(crate) use request_id::RequestId;
|
||||
|
||||
@@ -13,7 +13,7 @@ use jsonwebtoken::{Algorithm, DecodingKey, TokenData, Validation, jwk::JwkSet};
|
||||
use tower_http::auth::AsyncAuthorizeRequest;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::http::{JsonResponse, extract::RequestId};
|
||||
use crate::http::JsonResponse;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(in crate::http) struct Authorize {
|
||||
@@ -52,18 +52,6 @@ impl AsyncAuthorizeRequest<Body> for Authorize {
|
||||
let validation = self.validation.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let request_id = request.extract_parts::<RequestId>().await.unwrap();
|
||||
|
||||
// TODO(tristan957): Remove this stanza after teaching neon_local
|
||||
// and the regression tests to use a JWT + JWKS.
|
||||
//
|
||||
// https://github.com/neondatabase/neon/issues/11316
|
||||
if cfg!(feature = "testing") {
|
||||
warn!(%request_id, "Skipping compute_ctl authorization check");
|
||||
|
||||
return Ok(request);
|
||||
}
|
||||
|
||||
let TypedHeader(Authorization(bearer)) = request
|
||||
.extract_parts::<TypedHeader<Authorization<Bearer>>>()
|
||||
.await
|
||||
|
||||
@@ -1544,7 +1544,7 @@ async fn handle_endpoint(subcmd: &EndpointCmd, env: &local_env::LocalEnv) -> Res
|
||||
.with_context(|| format!("postgres endpoint {endpoint_id} is not found"))?;
|
||||
let jwt = endpoint.generate_jwt()?;
|
||||
|
||||
println!("{jwt}");
|
||||
print!("{jwt}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Example docker compose configuration
|
||||
|
||||
The configuration in this directory is used for testing Neon docker images: it is
|
||||
@@ -8,3 +7,13 @@ you can experiment with a miniature Neon system, use `cargo neon` rather than co
|
||||
This configuration does not start the storage controller, because the controller
|
||||
needs a way to reconfigure running computes, and no such thing exists in this setup.
|
||||
|
||||
## Generating the JWKS for a compute
|
||||
|
||||
```shell
|
||||
openssl genpkey -algorithm Ed25519 -out private-key.pem
|
||||
openssl pkey -in private-key.pem -pubout -out public-key.pem
|
||||
openssl pkey -pubin -inform pem -in public-key.pem -pubout -outform der -out public-key.der
|
||||
key="$(xxd -plain -cols 32 -s -32 public-key.der)"
|
||||
key_id="$(printf '%s' "$key" | sha256sum | awk '{ print $1 }' | basenc --base64url --wrap=0)"
|
||||
x="$(printf '%s' "$key" | basenc --base64url --wrap=0)"
|
||||
```
|
||||
|
||||
3
docker-compose/compute_wrapper/private-key.pem
Normal file
3
docker-compose/compute_wrapper/private-key.pem
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIOmnRbzt2AJ0d+S3aU1hiYOl/tXpvz1FmWBfwHYBgOma
|
||||
-----END PRIVATE KEY-----
|
||||
BIN
docker-compose/compute_wrapper/public-key.der
Normal file
BIN
docker-compose/compute_wrapper/public-key.der
Normal file
Binary file not shown.
3
docker-compose/compute_wrapper/public-key.pem
Normal file
3
docker-compose/compute_wrapper/public-key.pem
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEADY0al/U0bgB3+9fUGk+3PKWnsck9OyxN5DjHIN6Xep0=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -142,7 +142,19 @@
|
||||
},
|
||||
"compute_ctl_config": {
|
||||
"jwks": {
|
||||
"keys": []
|
||||
"keys": [
|
||||
{
|
||||
"use": "sig",
|
||||
"key_ops": [
|
||||
"verify"
|
||||
],
|
||||
"alg": "EdDSA",
|
||||
"kid": "ZGIxMzAzOGY0YWQwODk2ODU1MTk1NzMxMDFkYmUyOWU2NzZkOWNjNjMyMGRkZGJjOWY0MjdjYWVmNzE1MjUyOAo=",
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "MGQ4ZDFhOTdmNTM0NmUwMDc3ZmJkN2Q0MWE0ZmI3M2NhNWE3YjFjOTNkM2IyYzRkZTQzOGM3MjBkZTk3N2E5ZAo="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import urllib.parse
|
||||
from typing import TYPE_CHECKING, final
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import AuthBase
|
||||
from typing_extensions import override
|
||||
|
||||
from fixtures.log_helper import log
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from requests import PreparedRequest
|
||||
|
||||
|
||||
@final
|
||||
class BearerAuth(AuthBase):
|
||||
"""
|
||||
Auth implementation for bearer authorization in HTTP requests through the
|
||||
requests HTTP client library.
|
||||
"""
|
||||
|
||||
def __init__(self, jwt: str):
|
||||
self.__jwt = jwt
|
||||
|
||||
@override
|
||||
def __call__(self, request: PreparedRequest) -> PreparedRequest:
|
||||
request.headers["Authorization"] = "Bearer " + self.__jwt
|
||||
return request
|
||||
|
||||
|
||||
@final
|
||||
class EndpointHttpClient(requests.Session):
|
||||
def __init__(
|
||||
self,
|
||||
external_port: int,
|
||||
internal_port: int,
|
||||
jwt: str,
|
||||
):
|
||||
super().__init__()
|
||||
self.external_port: int = external_port
|
||||
self.internal_port: int = internal_port
|
||||
self.auth = BearerAuth(jwt)
|
||||
|
||||
self.mount("http://", HTTPAdapter())
|
||||
|
||||
def dbs_and_roles(self):
|
||||
res = self.get(f"http://localhost:{self.external_port}/dbs_and_roles")
|
||||
res = self.get(f"http://localhost:{self.external_port}/dbs_and_roles", auth=self.auth)
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
|
||||
def database_schema(self, database: str):
|
||||
res = self.get(
|
||||
f"http://localhost:{self.external_port}/database_schema?database={urllib.parse.quote(database, safe='')}"
|
||||
f"http://localhost:{self.external_port}/database_schema?database={urllib.parse.quote(database, safe='')}",
|
||||
auth=self.auth,
|
||||
)
|
||||
res.raise_for_status()
|
||||
return res.text
|
||||
@@ -58,13 +84,13 @@ class EndpointHttpClient(requests.Session):
|
||||
|
||||
# Current compute status.
|
||||
def status(self):
|
||||
res = self.get(f"http://localhost:{self.external_port}/status")
|
||||
res = self.get(f"http://localhost:{self.external_port}/status", auth=self.auth)
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
|
||||
# Compute startup-related metrics.
|
||||
def metrics_json(self):
|
||||
res = self.get(f"http://localhost:{self.external_port}/metrics.json")
|
||||
res = self.get(f"http://localhost:{self.external_port}/metrics.json", auth=self.auth)
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
|
||||
|
||||
@@ -535,6 +535,18 @@ class NeonLocalCli(AbstractNeonCli):
|
||||
res.check_returncode()
|
||||
return res
|
||||
|
||||
def endpoint_generate_jwt(self, endpoint_id: str) -> str:
|
||||
"""
|
||||
Generate a JWT for making requests to the endpoint's external HTTP
|
||||
server.
|
||||
"""
|
||||
args = ["endpoint", "generate-jwt", endpoint_id]
|
||||
|
||||
cmd = self.raw_cli(args)
|
||||
cmd.check_returncode()
|
||||
|
||||
return cmd.stdout
|
||||
|
||||
def endpoint_start(
|
||||
self,
|
||||
endpoint_id: str,
|
||||
|
||||
@@ -4110,13 +4110,14 @@ class Endpoint(PgProtocol, LogUtils):
|
||||
# try and stop the same process twice, as stop() is called by test teardown and
|
||||
# potentially by some __del__ chains in other threads.
|
||||
self._running = threading.Semaphore(0)
|
||||
self.__jwt: str | None = None
|
||||
|
||||
def http_client(
|
||||
self, auth_token: str | None = None, retries: Retry | None = None
|
||||
) -> EndpointHttpClient:
|
||||
def http_client(self, retries: Retry | None = None) -> EndpointHttpClient:
|
||||
assert self.__jwt is not None
|
||||
return EndpointHttpClient(
|
||||
external_port=self.external_http_port,
|
||||
internal_port=self.internal_http_port,
|
||||
jwt=self.__jwt,
|
||||
)
|
||||
|
||||
def create(
|
||||
@@ -4200,6 +4201,8 @@ class Endpoint(PgProtocol, LogUtils):
|
||||
|
||||
self.config(config_lines)
|
||||
|
||||
self.__jwt = self.env.neon_cli.endpoint_generate_jwt(self.endpoint_id)
|
||||
|
||||
return self
|
||||
|
||||
def start(
|
||||
|
||||
Reference in New Issue
Block a user