diff --git a/control_plane/safekeepers.conf b/control_plane/safekeepers.conf index df7dd2adca..576cc4a3a9 100644 --- a/control_plane/safekeepers.conf +++ b/control_plane/safekeepers.conf @@ -2,7 +2,8 @@ [pageserver] listen_pg_addr = '127.0.0.1:64000' listen_http_addr = '127.0.0.1:9898' -auth_type = 'Trust' +pg_auth_type = 'Trust' +http_auth_type = 'Trust' [[safekeepers]] id = 1 diff --git a/control_plane/simple.conf b/control_plane/simple.conf index 6014e8dffd..243e13f3d3 100644 --- a/control_plane/simple.conf +++ b/control_plane/simple.conf @@ -3,7 +3,8 @@ [pageserver] listen_pg_addr = '127.0.0.1:64000' listen_http_addr = '127.0.0.1:9898' -auth_type = 'Trust' +pg_auth_type = 'Trust' +http_auth_type = 'Trust' [[safekeepers]] id = 1 diff --git a/control_plane/src/bin/neon_local.rs b/control_plane/src/bin/neon_local.rs index 49b1d31dbc..a9b66f479a 100644 --- a/control_plane/src/bin/neon_local.rs +++ b/control_plane/src/bin/neon_local.rs @@ -53,14 +53,15 @@ listen_addr = '{DEFAULT_BROKER_ADDR}' id = {DEFAULT_PAGESERVER_ID} listen_pg_addr = '{DEFAULT_PAGESERVER_PG_ADDR}' listen_http_addr = '{DEFAULT_PAGESERVER_HTTP_ADDR}' -auth_type = '{pageserver_auth_type}' +pg_auth_type = '{trust_auth}' +http_auth_type = '{trust_auth}' [[safekeepers]] id = {DEFAULT_SAFEKEEPER_ID} pg_port = {DEFAULT_SAFEKEEPER_PG_PORT} http_port = {DEFAULT_SAFEKEEPER_HTTP_PORT} "#, - pageserver_auth_type = AuthType::Trust, + trust_auth = AuthType::Trust, ) } @@ -627,7 +628,7 @@ fn handle_pg(pg_match: &ArgMatches, env: &local_env::LocalEnv) -> Result<()> { let node = cplane.nodes.get(&(tenant_id, node_name.to_string())); - let auth_token = if matches!(env.pageserver.auth_type, AuthType::NeonJWT) { + let auth_token = if matches!(env.pageserver.pg_auth_type, AuthType::NeonJWT) { let claims = Claims::new(Some(tenant_id), Scope::Tenant); Some(env.generate_auth_token(&claims)?) diff --git a/control_plane/src/compute.rs b/control_plane/src/compute.rs index b7029aabc5..094d2add8d 100644 --- a/control_plane/src/compute.rs +++ b/control_plane/src/compute.rs @@ -97,7 +97,7 @@ impl ComputeControlPlane { }); node.create_pgdata()?; - node.setup_pg_conf(self.env.pageserver.auth_type)?; + node.setup_pg_conf(self.env.pageserver.pg_auth_type)?; self.nodes .insert((tenant_id, node.name.clone()), Arc::clone(&node)); diff --git a/control_plane/src/local_env.rs b/control_plane/src/local_env.rs index 09180d96c4..630f8bb664 100644 --- a/control_plane/src/local_env.rs +++ b/control_plane/src/local_env.rs @@ -110,12 +110,14 @@ impl NeonBroker { pub struct PageServerConf { // node id pub id: NodeId, + // Pageserver connection settings pub listen_pg_addr: String, pub listen_http_addr: String, - // used to determine which auth type is used - pub auth_type: AuthType, + // auth type used for the PG and HTTP ports + pub pg_auth_type: AuthType, + pub http_auth_type: AuthType, // jwt auth token used for communication with pageserver pub auth_token: String, @@ -127,7 +129,8 @@ impl Default for PageServerConf { id: NodeId(0), listen_pg_addr: String::new(), listen_http_addr: String::new(), - auth_type: AuthType::Trust, + pg_auth_type: AuthType::Trust, + http_auth_type: AuthType::Trust, auth_token: String::new(), } } diff --git a/control_plane/src/pageserver.rs b/control_plane/src/pageserver.rs index 4b7180c250..07ead45d5b 100644 --- a/control_plane/src/pageserver.rs +++ b/control_plane/src/pageserver.rs @@ -82,7 +82,7 @@ impl PageServerNode { let (host, port) = parse_host_port(&env.pageserver.listen_pg_addr) .expect("Unable to parse listen_pg_addr"); let port = port.unwrap_or(5432); - let password = if env.pageserver.auth_type == AuthType::NeonJWT { + let password = if env.pageserver.pg_auth_type == AuthType::NeonJWT { Some(env.pageserver.auth_token.clone()) } else { None @@ -106,25 +106,32 @@ impl PageServerNode { self.env.pg_distrib_dir_raw().display() ); - let authg_type_param = format!("auth_type='{}'", self.env.pageserver.auth_type); + let http_auth_type_param = + format!("http_auth_type='{}'", self.env.pageserver.http_auth_type); let listen_http_addr_param = format!( "listen_http_addr='{}'", self.env.pageserver.listen_http_addr ); + + let pg_auth_type_param = format!("pg_auth_type='{}'", self.env.pageserver.pg_auth_type); let listen_pg_addr_param = format!("listen_pg_addr='{}'", self.env.pageserver.listen_pg_addr); + let broker_endpoint_param = format!("broker_endpoint='{}'", self.env.broker.client_url()); let mut overrides = vec![ id, pg_distrib_dir_param, - authg_type_param, + http_auth_type_param, + pg_auth_type_param, listen_http_addr_param, listen_pg_addr_param, broker_endpoint_param, ]; - if self.env.pageserver.auth_type != AuthType::Trust { + if self.env.pageserver.http_auth_type != AuthType::Trust + || self.env.pageserver.pg_auth_type != AuthType::Trust + { overrides.push("auth_validation_public_key_path='auth_public_key.pem'".to_owned()); } overrides @@ -247,7 +254,10 @@ impl PageServerNode { } fn pageserver_env_variables(&self) -> anyhow::Result> { - Ok(if self.env.pageserver.auth_type != AuthType::Trust { + // FIXME: why is this tied to pageserver's auth type? Whether or not the safekeeper + // needs a token, and how to generate that token, seems independent to whether + // the pageserver requires a token in incoming requests. + Ok(if self.env.pageserver.http_auth_type != AuthType::Trust { // Generate a token to connect from the pageserver to a safekeeper let token = self .env @@ -283,7 +293,7 @@ impl PageServerNode { fn http_request(&self, method: Method, url: U) -> RequestBuilder { let mut builder = self.http_client.request(method, url); - if self.env.pageserver.auth_type == AuthType::NeonJWT { + if self.env.pageserver.http_auth_type == AuthType::NeonJWT { builder = builder.bearer_auth(&self.env.pageserver.auth_token) } builder diff --git a/docs/authentication.md b/docs/authentication.md index 1637519211..e6b5fa5707 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -137,10 +137,12 @@ Each compute should present a token valid for the timeline's tenant. Pageserver also has HTTP API: some parts are per-tenant, some parts are server-wide, these are different scopes. -The `auth_type` configuration variable in Pageserver's config may have -either of three values: +Authentication can be enabled separately for the HTTP mgmt API, and +for the libpq connections from compute. The `http_auth_type` and +`pg_auth_type` configuration variables in Pageserver's config may +have one of these values: -* `Trust` removes all authentication. The outdated `MD5` value does likewise +* `Trust` removes all authentication. * `NeonJWT` enables JWT validation. Tokens are validated using the public key which lies in a PEM file specified in the `auth_validation_public_key_path` config. diff --git a/pageserver/src/bin/pageserver.rs b/pageserver/src/bin/pageserver.rs index 564a3de82c..14e86ddcb6 100644 --- a/pageserver/src/bin/pageserver.rs +++ b/pageserver/src/bin/pageserver.rs @@ -270,15 +270,31 @@ fn start_pageserver( WALRECEIVER_RUNTIME.block_on(pageserver::broker_client::init_broker_client(conf))?; // Initialize authentication for incoming connections - let auth = match &conf.auth_type { - AuthType::Trust => None, - AuthType::NeonJWT => { - // unwrap is ok because check is performed when creating config, so path is set and file exists - let key_path = conf.auth_validation_public_key_path.as_ref().unwrap(); - Some(JwtAuth::from_key_path(key_path)?.into()) - } - }; - info!("Using auth: {:#?}", conf.auth_type); + let http_auth; + let pg_auth; + if conf.http_auth_type == AuthType::NeonJWT || conf.pg_auth_type == AuthType::NeonJWT { + // unwrap is ok because check is performed when creating config, so path is set and file exists + let key_path = conf.auth_validation_public_key_path.as_ref().unwrap(); + info!( + "Loading public key for verifying JWT tokens from {:#?}", + key_path + ); + let auth: Arc = Arc::new(JwtAuth::from_key_path(key_path)?); + + http_auth = match &conf.http_auth_type { + AuthType::Trust => None, + AuthType::NeonJWT => Some(auth.clone()), + }; + pg_auth = match &conf.pg_auth_type { + AuthType::Trust => None, + AuthType::NeonJWT => Some(auth), + }; + } else { + http_auth = None; + pg_auth = None; + } + info!("Using auth for http API: {:#?}", conf.http_auth_type); + info!("Using auth for pg connections: {:#?}", conf.pg_auth_type); match var("NEON_AUTH_TOKEN") { Ok(v) => { @@ -308,7 +324,7 @@ fn start_pageserver( { let _rt_guard = MGMT_REQUEST_RUNTIME.enter(); - let router = http::make_router(conf, launch_ts, auth.clone(), remote_storage)? + let router = http::make_router(conf, launch_ts, http_auth, remote_storage)? .build() .map_err(|err| anyhow!(err))?; let service = utils::http::RouterService::new(router).unwrap(); @@ -382,9 +398,9 @@ fn start_pageserver( async move { page_service::libpq_listener_main( conf, - auth, + pg_auth, pageserver_listener, - conf.auth_type, + conf.pg_auth_type, libpq_ctx, ) .await diff --git a/pageserver/src/config.rs b/pageserver/src/config.rs index fde889d01a..d17f0bc143 100644 --- a/pageserver/src/config.rs +++ b/pageserver/src/config.rs @@ -138,9 +138,15 @@ pub struct PageServerConf { pub pg_distrib_dir: PathBuf, - pub auth_type: AuthType, - + // Authentication + /// authentication method for the HTTP mgmt API + pub http_auth_type: AuthType, + /// authentication method for libpq connections from compute + pub pg_auth_type: AuthType, + /// Path to a file containing public key for verifying JWT tokens. + /// Used for both mgmt and compute auth, if enabled. pub auth_validation_public_key_path: Option, + pub remote_storage_config: Option, pub default_tenant_conf: TenantConf, @@ -208,7 +214,8 @@ struct PageServerConfigBuilder { pg_distrib_dir: BuilderValue, - auth_type: BuilderValue, + http_auth_type: BuilderValue, + pg_auth_type: BuilderValue, // auth_validation_public_key_path: BuilderValue>, @@ -251,7 +258,8 @@ impl Default for PageServerConfigBuilder { pg_distrib_dir: Set(env::current_dir() .expect("cannot access current directory") .join("pg_install")), - auth_type: Set(AuthType::Trust), + http_auth_type: Set(AuthType::Trust), + pg_auth_type: Set(AuthType::Trust), auth_validation_public_key_path: Set(None), remote_storage_config: Set(None), id: NotSet, @@ -323,8 +331,12 @@ impl PageServerConfigBuilder { self.pg_distrib_dir = BuilderValue::Set(pg_distrib_dir) } - pub fn auth_type(&mut self, auth_type: AuthType) { - self.auth_type = BuilderValue::Set(auth_type) + pub fn http_auth_type(&mut self, auth_type: AuthType) { + self.http_auth_type = BuilderValue::Set(auth_type) + } + + pub fn pg_auth_type(&mut self, auth_type: AuthType) { + self.pg_auth_type = BuilderValue::Set(auth_type) } pub fn auth_validation_public_key_path( @@ -419,7 +431,10 @@ impl PageServerConfigBuilder { pg_distrib_dir: self .pg_distrib_dir .ok_or(anyhow!("missing pg_distrib_dir"))?, - auth_type: self.auth_type.ok_or(anyhow!("missing auth_type"))?, + http_auth_type: self + .http_auth_type + .ok_or(anyhow!("missing http_auth_type"))?, + pg_auth_type: self.pg_auth_type.ok_or(anyhow!("missing pg_auth_type"))?, auth_validation_public_key_path: self .auth_validation_public_key_path .ok_or(anyhow!("missing auth_validation_public_key_path"))?, @@ -612,7 +627,8 @@ impl PageServerConf { "auth_validation_public_key_path" => builder.auth_validation_public_key_path(Some( PathBuf::from(parse_toml_string(key, item)?), )), - "auth_type" => builder.auth_type(parse_toml_from_str(key, item)?), + "http_auth_type" => builder.http_auth_type(parse_toml_from_str(key, item)?), + "pg_auth_type" => builder.pg_auth_type(parse_toml_from_str(key, item)?), "remote_storage" => { builder.remote_storage_config(RemoteStorageConfig::from_toml(item)?) } @@ -647,7 +663,7 @@ impl PageServerConf { let mut conf = builder.build().context("invalid config")?; - if conf.auth_type == AuthType::NeonJWT { + if conf.http_auth_type == AuthType::NeonJWT || conf.pg_auth_type == AuthType::NeonJWT { let auth_validation_public_key_path = conf .auth_validation_public_key_path .get_or_insert_with(|| workdir.join("auth_public_key.pem")); @@ -766,7 +782,8 @@ impl PageServerConf { superuser: "cloud_admin".to_string(), workdir: repo_dir, pg_distrib_dir, - auth_type: AuthType::Trust, + http_auth_type: AuthType::Trust, + pg_auth_type: AuthType::Trust, auth_validation_public_key_path: None, remote_storage_config: None, default_tenant_conf: TenantConf::default(), @@ -951,7 +968,8 @@ log_format = 'json' max_file_descriptors: defaults::DEFAULT_MAX_FILE_DESCRIPTORS, workdir, pg_distrib_dir, - auth_type: AuthType::Trust, + http_auth_type: AuthType::Trust, + pg_auth_type: AuthType::Trust, auth_validation_public_key_path: None, remote_storage_config: None, default_tenant_conf: TenantConf::default(), @@ -1008,7 +1026,8 @@ log_format = 'json' max_file_descriptors: 333, workdir, pg_distrib_dir, - auth_type: AuthType::Trust, + http_auth_type: AuthType::Trust, + pg_auth_type: AuthType::Trust, auth_validation_public_key_path: None, remote_storage_config: None, default_tenant_conf: TenantConf::default(), diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 111bc480c4..9faa994f16 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -82,6 +82,7 @@ fn get_config(request: &Request) -> &'static PageServerConf { get_state(request).conf } +/// Check that the requester is authorized to operate on given tenant fn check_permission(request: &Request, tenant_id: Option) -> Result<(), ApiError> { check_permission_with(request, |claims| { crate::auth::check_permission(claims, tenant_id) diff --git a/test_runner/fixtures/neon_fixtures.py b/test_runner/fixtures/neon_fixtures.py index c5e260a962..8a64be51f1 100644 --- a/test_runner/fixtures/neon_fixtures.py +++ b/test_runner/fixtures/neon_fixtures.py @@ -924,7 +924,8 @@ class NeonEnv: pg=self.port_distributor.get_port(), http=self.port_distributor.get_port(), ) - pageserver_auth_type = "NeonJWT" if config.auth_enabled else "Trust" + http_auth_type = "NeonJWT" if config.auth_enabled else "Trust" + pg_auth_type = "NeonJWT" if config.auth_enabled else "Trust" toml += textwrap.dedent( f""" @@ -932,7 +933,8 @@ class NeonEnv: id=1 listen_pg_addr = 'localhost:{pageserver_port.pg}' listen_http_addr = 'localhost:{pageserver_port.http}' - auth_type = '{pageserver_auth_type}' + pg_auth_type = '{pg_auth_type}' + http_auth_type = '{http_auth_type}' """ ) diff --git a/test_runner/regress/test_compatibility.py b/test_runner/regress/test_compatibility.py index 731e78a3e3..66625dd6f8 100644 --- a/test_runner/regress/test_compatibility.py +++ b/test_runner/regress/test_compatibility.py @@ -246,6 +246,13 @@ def prepare_snapshot( if get_neon_version(neon_binpath) == "49da498f651b9f3a53b56c7c0697636d880ddfe0": pageserver_config["broker_endpoints"] = etcd_broker_endpoints # old etcd version + # Older pageserver versions had just one `auth_type` setting. Now there + # are separate settings for pg and http ports. We don't use authentication + # in compatibility tests so just remove authentication related settings. + pageserver_config.pop("auth_type", None) + pageserver_config.pop("pg_auth_type", None) + pageserver_config.pop("http_auth_type", None) + if pg_distrib_dir: pageserver_config["pg_distrib_dir"] = str(pg_distrib_dir)