mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-06 17:59:11 +00:00
feat(credential): support custom credential ID generation (#1984)
introduces support for custom credential ID generation, allowing users to specify their own credential IDs instead of relying solely on auto-generated UUIDs.
This commit is contained in:
@@ -355,6 +355,11 @@ enum CredentialSubCommand {
|
|||||||
Generate {
|
Generate {
|
||||||
#[arg(long, help = "TTL in seconds (required)")]
|
#[arg(long, help = "TTL in seconds (required)")]
|
||||||
ttl: i64,
|
ttl: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "custom credential ID, return existing credential if already generated"
|
||||||
|
)]
|
||||||
|
credential_id: Option<String>,
|
||||||
#[arg(long, value_delimiter = ',', help = "ACL groups (comma-separated)")]
|
#[arg(long, value_delimiter = ',', help = "ACL groups (comma-separated)")]
|
||||||
groups: Option<Vec<String>>,
|
groups: Option<Vec<String>>,
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -1417,12 +1422,14 @@ impl CommandHandler<'_> {
|
|||||||
async fn handle_credential_generate(
|
async fn handle_credential_generate(
|
||||||
&self,
|
&self,
|
||||||
ttl: i64,
|
ttl: i64,
|
||||||
|
credential_id: Option<String>,
|
||||||
groups: Vec<String>,
|
groups: Vec<String>,
|
||||||
allow_relay: bool,
|
allow_relay: bool,
|
||||||
allowed_proxy_cidrs: Vec<String>,
|
allowed_proxy_cidrs: Vec<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let client = self.get_credential_client().await?;
|
let client = self.get_credential_client().await?;
|
||||||
let request = GenerateCredentialRequest {
|
let request = GenerateCredentialRequest {
|
||||||
|
credential_id,
|
||||||
groups,
|
groups,
|
||||||
allow_relay,
|
allow_relay,
|
||||||
allowed_proxy_cidrs,
|
allowed_proxy_cidrs,
|
||||||
@@ -2362,6 +2369,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
SubCommand::Credential(credential_args) => match &credential_args.sub_command {
|
SubCommand::Credential(credential_args) => match &credential_args.sub_command {
|
||||||
CredentialSubCommand::Generate {
|
CredentialSubCommand::Generate {
|
||||||
ttl,
|
ttl,
|
||||||
|
credential_id,
|
||||||
groups,
|
groups,
|
||||||
allow_relay,
|
allow_relay,
|
||||||
allowed_proxy_cidrs,
|
allowed_proxy_cidrs,
|
||||||
@@ -2369,6 +2377,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
handler
|
handler
|
||||||
.handle_credential_generate(
|
.handle_credential_generate(
|
||||||
*ttl,
|
*ttl,
|
||||||
|
credential_id.clone(),
|
||||||
groups.clone().unwrap_or_default(),
|
groups.clone().unwrap_or_default(),
|
||||||
*allow_relay,
|
*allow_relay,
|
||||||
allowed_proxy_cidrs.clone().unwrap_or_default(),
|
allowed_proxy_cidrs.clone().unwrap_or_default(),
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ use crate::proto::peer_rpc::{TrustedCredentialPubkey, TrustedCredentialPubkeyPro
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct CredentialEntry {
|
struct CredentialEntry {
|
||||||
pubkey: String,
|
pubkey: String,
|
||||||
|
#[serde(default)]
|
||||||
|
secret: String,
|
||||||
groups: Vec<String>,
|
groups: Vec<String>,
|
||||||
allow_relay: bool,
|
allow_relay: bool,
|
||||||
allowed_proxy_cidrs: Vec<String>,
|
allowed_proxy_cidrs: Vec<String>,
|
||||||
@@ -44,9 +46,47 @@ impl CredentialManager {
|
|||||||
allowed_proxy_cidrs: Vec<String>,
|
allowed_proxy_cidrs: Vec<String>,
|
||||||
ttl: Duration,
|
ttl: Duration,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
|
self.generate_credential_with_id(groups, allow_relay, allowed_proxy_cidrs, ttl, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_credential_with_id(
|
||||||
|
&self,
|
||||||
|
groups: Vec<String>,
|
||||||
|
allow_relay: bool,
|
||||||
|
allowed_proxy_cidrs: Vec<String>,
|
||||||
|
ttl: Duration,
|
||||||
|
credential_id: Option<String>,
|
||||||
|
) -> (String, String) {
|
||||||
|
let mut credentials = self.credentials.lock().unwrap();
|
||||||
|
let id = if let Some(id) = credential_id
|
||||||
|
.map(|x| x.trim().to_string())
|
||||||
|
.filter(|x| !x.is_empty())
|
||||||
|
{
|
||||||
|
if let Some(existing) = credentials.get(&id) {
|
||||||
|
if !existing.secret.is_empty() {
|
||||||
|
return (id, existing.secret.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
uuid::Uuid::new_v4().to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (entry, secret) = Self::build_entry(groups, allow_relay, allowed_proxy_cidrs, ttl);
|
||||||
|
credentials.insert(id.clone(), entry);
|
||||||
|
drop(credentials);
|
||||||
|
self.save_to_disk();
|
||||||
|
(id, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_entry(
|
||||||
|
groups: Vec<String>,
|
||||||
|
allow_relay: bool,
|
||||||
|
allowed_proxy_cidrs: Vec<String>,
|
||||||
|
ttl: Duration,
|
||||||
|
) -> (CredentialEntry, String) {
|
||||||
let private = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
let private = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
||||||
let public = PublicKey::from(&private);
|
let public = PublicKey::from(&private);
|
||||||
let id = uuid::Uuid::new_v4().to_string();
|
|
||||||
let pubkey = BASE64_STANDARD.encode(public.as_bytes());
|
let pubkey = BASE64_STANDARD.encode(public.as_bytes());
|
||||||
let secret = BASE64_STANDARD.encode(private.as_bytes());
|
let secret = BASE64_STANDARD.encode(private.as_bytes());
|
||||||
|
|
||||||
@@ -58,16 +98,14 @@ impl CredentialManager {
|
|||||||
|
|
||||||
let entry = CredentialEntry {
|
let entry = CredentialEntry {
|
||||||
pubkey,
|
pubkey,
|
||||||
|
secret: secret.clone(),
|
||||||
groups,
|
groups,
|
||||||
allow_relay,
|
allow_relay,
|
||||||
allowed_proxy_cidrs,
|
allowed_proxy_cidrs,
|
||||||
expiry_unix,
|
expiry_unix,
|
||||||
created_at_unix: now,
|
created_at_unix: now,
|
||||||
};
|
};
|
||||||
|
(entry, secret)
|
||||||
self.credentials.lock().unwrap().insert(id.clone(), entry);
|
|
||||||
self.save_to_disk();
|
|
||||||
(id, secret)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn revoke_credential(&self, credential_id: &str) -> bool {
|
pub fn revoke_credential(&self, credential_id: &str) -> bool {
|
||||||
@@ -404,4 +442,35 @@ mod tests {
|
|||||||
let list = mgr.list_credentials();
|
let list = mgr.list_credentials();
|
||||||
assert_eq!(list.len(), 1);
|
assert_eq!(list.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_with_specified_id_reuses_existing_result() {
|
||||||
|
let mgr = CredentialManager::new(None);
|
||||||
|
let fixed_id = "fixed-credential-id".to_string();
|
||||||
|
let (id1, secret1) = mgr.generate_credential_with_id(
|
||||||
|
vec!["group-a".to_string()],
|
||||||
|
false,
|
||||||
|
vec!["10.0.0.0/24".to_string()],
|
||||||
|
Duration::from_secs(3600),
|
||||||
|
Some(fixed_id.clone()),
|
||||||
|
);
|
||||||
|
let (id2, secret2) = mgr.generate_credential_with_id(
|
||||||
|
vec!["group-b".to_string()],
|
||||||
|
true,
|
||||||
|
vec!["192.168.0.0/16".to_string()],
|
||||||
|
Duration::from_secs(7200),
|
||||||
|
Some(fixed_id.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(id1, fixed_id);
|
||||||
|
assert_eq!(id2, fixed_id);
|
||||||
|
assert_eq!(secret1, secret2);
|
||||||
|
|
||||||
|
let list = mgr.list_credentials();
|
||||||
|
assert_eq!(list.len(), 1);
|
||||||
|
assert_eq!(list[0].credential_id, fixed_id);
|
||||||
|
assert_eq!(list[0].groups, vec!["group-a".to_string()]);
|
||||||
|
assert!(!list[0].allow_relay);
|
||||||
|
assert_eq!(list[0].allowed_proxy_cidrs, vec!["10.0.0.0/24".to_string()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,12 +232,15 @@ impl CredentialManageRpc for PeerManagerRpcService {
|
|||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
let (id, secret) = global_ctx.get_credential_manager().generate_credential(
|
let (id, secret) = global_ctx
|
||||||
request.groups,
|
.get_credential_manager()
|
||||||
request.allow_relay,
|
.generate_credential_with_id(
|
||||||
request.allowed_proxy_cidrs,
|
request.groups,
|
||||||
ttl,
|
request.allow_relay,
|
||||||
);
|
request.allowed_proxy_cidrs,
|
||||||
|
ttl,
|
||||||
|
request.credential_id,
|
||||||
|
);
|
||||||
|
|
||||||
global_ctx.issue_event(crate::common::global_ctx::GlobalCtxEvent::CredentialChanged);
|
global_ctx.issue_event(crate::common::global_ctx::GlobalCtxEvent::CredentialChanged);
|
||||||
|
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ message GenerateCredentialRequest {
|
|||||||
bool allow_relay = 2; // optional: allow relay through credential node
|
bool allow_relay = 2; // optional: allow relay through credential node
|
||||||
repeated string allowed_proxy_cidrs = 3; // optional: restrict proxy_cidrs
|
repeated string allowed_proxy_cidrs = 3; // optional: restrict proxy_cidrs
|
||||||
int64 ttl_seconds = 4; // must be > 0: credential TTL in seconds (0 / omitted is invalid)
|
int64 ttl_seconds = 4; // must be > 0: credential TTL in seconds (0 / omitted is invalid)
|
||||||
|
optional string credential_id = 5; // optional: user-specified credential id, reused if already exists
|
||||||
}
|
}
|
||||||
|
|
||||||
message GenerateCredentialResponse {
|
message GenerateCredentialResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user