mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-04-22 16:17:23 +08: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 {
|
||||
#[arg(long, help = "TTL in seconds (required)")]
|
||||
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)")]
|
||||
groups: Option<Vec<String>>,
|
||||
#[arg(
|
||||
@@ -1417,12 +1422,14 @@ impl CommandHandler<'_> {
|
||||
async fn handle_credential_generate(
|
||||
&self,
|
||||
ttl: i64,
|
||||
credential_id: Option<String>,
|
||||
groups: Vec<String>,
|
||||
allow_relay: bool,
|
||||
allowed_proxy_cidrs: Vec<String>,
|
||||
) -> Result<(), Error> {
|
||||
let client = self.get_credential_client().await?;
|
||||
let request = GenerateCredentialRequest {
|
||||
credential_id,
|
||||
groups,
|
||||
allow_relay,
|
||||
allowed_proxy_cidrs,
|
||||
@@ -2362,6 +2369,7 @@ async fn main() -> Result<(), Error> {
|
||||
SubCommand::Credential(credential_args) => match &credential_args.sub_command {
|
||||
CredentialSubCommand::Generate {
|
||||
ttl,
|
||||
credential_id,
|
||||
groups,
|
||||
allow_relay,
|
||||
allowed_proxy_cidrs,
|
||||
@@ -2369,6 +2377,7 @@ async fn main() -> Result<(), Error> {
|
||||
handler
|
||||
.handle_credential_generate(
|
||||
*ttl,
|
||||
credential_id.clone(),
|
||||
groups.clone().unwrap_or_default(),
|
||||
*allow_relay,
|
||||
allowed_proxy_cidrs.clone().unwrap_or_default(),
|
||||
|
||||
@@ -15,6 +15,8 @@ use crate::proto::peer_rpc::{TrustedCredentialPubkey, TrustedCredentialPubkeyPro
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CredentialEntry {
|
||||
pubkey: String,
|
||||
#[serde(default)]
|
||||
secret: String,
|
||||
groups: Vec<String>,
|
||||
allow_relay: bool,
|
||||
allowed_proxy_cidrs: Vec<String>,
|
||||
@@ -44,9 +46,47 @@ impl CredentialManager {
|
||||
allowed_proxy_cidrs: Vec<String>,
|
||||
ttl: Duration,
|
||||
) -> (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 public = PublicKey::from(&private);
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
let pubkey = BASE64_STANDARD.encode(public.as_bytes());
|
||||
let secret = BASE64_STANDARD.encode(private.as_bytes());
|
||||
|
||||
@@ -58,16 +98,14 @@ impl CredentialManager {
|
||||
|
||||
let entry = CredentialEntry {
|
||||
pubkey,
|
||||
secret: secret.clone(),
|
||||
groups,
|
||||
allow_relay,
|
||||
allowed_proxy_cidrs,
|
||||
expiry_unix,
|
||||
created_at_unix: now,
|
||||
};
|
||||
|
||||
self.credentials.lock().unwrap().insert(id.clone(), entry);
|
||||
self.save_to_disk();
|
||||
(id, secret)
|
||||
(entry, secret)
|
||||
}
|
||||
|
||||
pub fn revoke_credential(&self, credential_id: &str) -> bool {
|
||||
@@ -404,4 +442,35 @@ mod tests {
|
||||
let list = mgr.list_credentials();
|
||||
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(
|
||||
request.groups,
|
||||
request.allow_relay,
|
||||
request.allowed_proxy_cidrs,
|
||||
ttl,
|
||||
);
|
||||
let (id, secret) = global_ctx
|
||||
.get_credential_manager()
|
||||
.generate_credential_with_id(
|
||||
request.groups,
|
||||
request.allow_relay,
|
||||
request.allowed_proxy_cidrs,
|
||||
ttl,
|
||||
request.credential_id,
|
||||
);
|
||||
|
||||
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
|
||||
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)
|
||||
optional string credential_id = 5; // optional: user-specified credential id, reused if already exists
|
||||
}
|
||||
|
||||
message GenerateCredentialResponse {
|
||||
|
||||
Reference in New Issue
Block a user