mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-13 08:22:55 +00:00
The 'zenith' CLI utility can now be used to launch safekeepers. By default, one safekeeper is configured. There are new 'safekeeper start/stop' subcommands to manage the safekeepers. Each safekeeper is given a name that can be used to identify the safekeeper to start/stop with the 'zenith start/stop' commands. The safekeeper data is stored in '.zenith/safekeepers/<name>'. The 'zenith start' command now starts the pageserver and also all safekeepers. 'zenith stop' stops pageserver, all safekeepers, and all postgres nodes. Introduce new 'zenith pageserver start/stop' subcommands for starting/stopping just the page server. The biggest change here is to the 'zenith init' command. This adds a new 'zenith init --config=<path to toml file>' option. It takes a toml config file that describes the environment. In the config file, you can specify options for the pageserver, like the pg and http ports, and authentication. For each safekeeper, you can define a name and the pg and http ports. If you don't use the --config option, you get a default configuration with a pageserver and one safekeeper. Note that that's different from the previous default of no safekeepers. Any fields that are omitted in the configuration file are filled with defaults. You can also specify the initial tenant ID in the config file. A couple of sample config files are added in the control_plane/ directory. The --pageserver-pg-port, --pageserver-http-port, and --pageserver-auth options to 'zenith init' are removed. Use a config file instead. Finally, change the python test fixtures to use the new 'zenith' commands and the config file to describe the environment.
121 lines
4.0 KiB
Rust
121 lines
4.0 KiB
Rust
// For details about authentication see docs/authentication.md
|
|
// TODO there are two issues for our use case in jsonwebtoken library which will be resolved in next release
|
|
// The first one is that there is no way to disable expiration claim, but it can be excluded from validation, so use this as a workaround for now.
|
|
// Relevant issue: https://github.com/Keats/jsonwebtoken/issues/190
|
|
// The second one is that we wanted to use ed25519 keys, but they are also not supported until next version. So we go with RSA keys for now.
|
|
// Relevant issue: https://github.com/Keats/jsonwebtoken/issues/162
|
|
|
|
use hex::{self, FromHex};
|
|
use serde::de::Error;
|
|
use serde::{self, Deserializer, Serializer};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
use anyhow::{bail, Result};
|
|
use jsonwebtoken::{
|
|
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::zid::ZTenantId;
|
|
|
|
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Scope {
|
|
Tenant,
|
|
PageServerApi,
|
|
}
|
|
|
|
pub fn to_hex_option<S>(value: &Option<ZTenantId>, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match value {
|
|
Some(tid) => hex::serialize(tid, serializer),
|
|
None => Option::serialize(value, serializer),
|
|
}
|
|
}
|
|
|
|
fn from_hex_option<'de, D>(deserializer: D) -> Result<Option<ZTenantId>, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let opt: Option<String> = Option::deserialize(deserializer)?;
|
|
match opt {
|
|
Some(tid) => Ok(Some(ZTenantId::from_hex(tid).map_err(Error::custom)?)),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Claims {
|
|
// this custom serialize/deserialize_with is needed because Option is not transparent to serde
|
|
// so clearest option is serde(with = "hex") but it is not working, for details see https://github.com/serde-rs/serde/issues/1301
|
|
#[serde(
|
|
default,
|
|
skip_serializing_if = "Option::is_none",
|
|
serialize_with = "to_hex_option",
|
|
deserialize_with = "from_hex_option"
|
|
)]
|
|
pub tenant_id: Option<ZTenantId>,
|
|
pub scope: Scope,
|
|
}
|
|
|
|
impl Claims {
|
|
pub fn new(tenant_id: Option<ZTenantId>, scope: Scope) -> Self {
|
|
Self { tenant_id, scope }
|
|
}
|
|
}
|
|
|
|
pub fn check_permission(claims: &Claims, tenantid: Option<ZTenantId>) -> Result<()> {
|
|
match (&claims.scope, tenantid) {
|
|
(Scope::Tenant, None) => {
|
|
bail!("Attempt to access management api with tenant scope. Permission denied")
|
|
}
|
|
(Scope::Tenant, Some(tenantid)) => {
|
|
if claims.tenant_id.unwrap() != tenantid {
|
|
bail!("Tenant id mismatch. Permission denied")
|
|
}
|
|
Ok(())
|
|
}
|
|
(Scope::PageServerApi, None) => Ok(()), // access to management api for PageServerApi scope
|
|
(Scope::PageServerApi, Some(_)) => Ok(()), // access to tenant api using PageServerApi scope
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct JwtAuth {
|
|
decoding_key: DecodingKey<'static>,
|
|
validation: Validation,
|
|
}
|
|
|
|
impl JwtAuth {
|
|
pub fn new(decoding_key: DecodingKey<'_>) -> Self {
|
|
Self {
|
|
decoding_key: decoding_key.into_static(),
|
|
validation: Validation {
|
|
algorithms: vec![JWT_ALGORITHM],
|
|
validate_exp: false,
|
|
..Default::default()
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn from_key_path(key_path: &Path) -> Result<Self> {
|
|
let public_key = fs::read(key_path)?;
|
|
Ok(Self::new(DecodingKey::from_rsa_pem(&public_key)?))
|
|
}
|
|
|
|
pub fn decode(&self, token: &str) -> Result<TokenData<Claims>> {
|
|
Ok(decode(token, &self.decoding_key, &self.validation)?)
|
|
}
|
|
}
|
|
|
|
// this function is used only for testing purposes in CLI e g generate tokens during init
|
|
pub fn encode_from_key_file(claims: &Claims, key_data: &[u8]) -> Result<String> {
|
|
let key = EncodingKey::from_rsa_pem(key_data)?;
|
|
Ok(encode(&Header::new(JWT_ALGORITHM), claims, &key)?)
|
|
}
|