first commit
This commit is contained in:
commit
b10773b7dd
1919
Cargo.lock
generated
Normal file
1919
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
Normal file
50
Cargo.toml
Normal file
@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "nv_ls"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rsa = "0.7.2"
|
||||
ring = "0.16.20"
|
||||
rand = "0.8.5"
|
||||
pem = { version = "1.0" }
|
||||
rcgen = { version = "0.10.0", features = ["pem"] }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
num-bigint-dig = { version = "0.8", default-features = false }
|
||||
sha2 = "0.10.6"
|
||||
base64 = "0.13.1"
|
||||
uuid = { version = "1.2.2", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] }
|
||||
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
jsonwebtoken = "8"
|
||||
time = { version = "0.3.17", features = ["macros", "serde", "formatting", "parsing"] }
|
||||
|
||||
|
||||
tokio = { version = "1.13.1", features = ["full"] }
|
||||
actix-web = { version = "4", features = ["rustls"] }
|
||||
actix-web-httpauth = "0.8.0"
|
||||
rustls = "0.20"
|
||||
rustls-pemfile = "1"
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
futures = "0.3.25"
|
||||
|
||||
redis = { version = "0.22", features = ["tokio-comp"] }
|
||||
obfstr = "0.4.1"
|
||||
derive_more = "0.99.17"
|
||||
|
||||
|
||||
[profile.dev.package.num-bigint-dig]
|
||||
opt-level = 3
|
||||
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
73
readme.md
Normal file
73
readme.md
Normal file
@ -0,0 +1,73 @@
|
||||
## config.json
|
||||
|
||||
### server_addr
|
||||
|
||||
The service listener addr.
|
||||
|
||||
### server_port
|
||||
|
||||
The service listener port.
|
||||
|
||||
### domain
|
||||
|
||||
Automatic generation of ssl certificates for domain names.
|
||||
|
||||
If you have a certificate, you do not need to change anything.
|
||||
|
||||
### req_host
|
||||
|
||||
The host filled in the client's token, it is also the address of the client request
|
||||
|
||||
### req_port
|
||||
|
||||
The port filled in the client's token, it is also the port of the client request
|
||||
|
||||
### redis_url
|
||||
|
||||
Same as the name
|
||||
|
||||
### redis_task_interval
|
||||
|
||||
How often to automatically clean up the client release inside redis.
|
||||
|
||||
### scope_ref_list
|
||||
|
||||
Whatever, just let it as is, or ur can random some uuid v4, but keep in mind it only takes two
|
||||
|
||||
### nls_service_instance_ref
|
||||
|
||||
Whatever, just let it as is, or ur can random a new uuid v4
|
||||
|
||||
### lease_time
|
||||
|
||||
Client lease time, In second
|
||||
|
||||
### lease_renewal_factor
|
||||
|
||||
The interval factor between client vm requests to the server, expressed as a percentage.
|
||||
|
||||
*lease_time* * *lease_renewal_factor*
|
||||
|
||||
e.g:
|
||||
|
||||
lease_time: 600s, lease_renewal_factor: 20, time now: 2022/12/03 10:00:00
|
||||
|
||||
It means, The time of the next client request time is: 2022/12/03 10:02:00, and next time is 2022/12/03 10:04:00 till the client lease renew
|
||||
|
||||
### cert_https
|
||||
|
||||
Same as the name
|
||||
|
||||
### rsa_client_token
|
||||
|
||||
For vm client token encryption and vm-side verification of signatures, can delete the corresponding file and generate it again randomly
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Get vm client token
|
||||
|
||||
just access `https://ur-ip-addr:server_port/genClientToken`
|
440
src/api.rs
Normal file
440
src/api.rs
Normal file
@ -0,0 +1,440 @@
|
||||
use actix_web::{get, post, put, delete, web, Result, Responder, HttpResponse, http::header::ContentType, Either};
|
||||
use std::str::FromStr;
|
||||
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use sha2::{Sha256, Digest};
|
||||
use uuid::{Uuid, uuid};
|
||||
use time::OffsetDateTime;
|
||||
use time::macros::format_description;
|
||||
use rsa::{PublicKeyParts, RsaPublicKey};
|
||||
use rsa::pkcs8::{DecodePublicKey, EncodePublicKey, LineEnding};
|
||||
|
||||
|
||||
use crate::core_struct::{AppConfigState, NodeUrl, PortMap, PortSet, JwtAuthToken};
|
||||
use crate::core_struct::origin::{OriginRequest, OriginResponse};
|
||||
use crate::core_struct::code::{CodeRequest, CodeResponse, AcData};
|
||||
use crate::core_struct::client_token::{ClientTokenRequest, ServiceInstanceConfiguration, ServiceInstancePublicKeyConfiguration, ServiceInstancePublicKeyMe};
|
||||
use crate::core_struct::token::{AuthCode, TokenRequest, TokenResponse};
|
||||
use crate::core_struct::leases::{ClientLeasesResponse, AddLessorResponse, CreateLeaseResult, LeaseCreateDetail, UpdateLeasesResponse, UpdateLeasesErrorResponse, DeleteLeasesResponse};
|
||||
|
||||
|
||||
#[derive(Debug, derive_more::Display, derive_more::Error)]
|
||||
pub enum ApiError {
|
||||
#[display(fmt = "Api Error: {}", detail)]
|
||||
InternalError { detail: String },
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for ApiError {
|
||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
match *self {
|
||||
ApiError::InternalError { .. } => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code())
|
||||
.insert_header(ContentType::html())
|
||||
.body(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[get("/genClientToken")]
|
||||
pub async fn gen_client_token(config_state: web::Data<AppConfigState>) -> HttpResponse {
|
||||
let req_port = &config_state.req_port;
|
||||
let req_host = &config_state.req_host;
|
||||
let token_public_key_str = &config_state.rsa_client_token.public_key.clone();
|
||||
let token_public_key = RsaPublicKey::from_public_key_pem(std::str::from_utf8(token_public_key_str).expect("error read key")).expect("Error get public key");
|
||||
let key_modulus = token_public_key.n().clone();
|
||||
let key_exponent = token_public_key.e().clone();
|
||||
|
||||
let current_time = OffsetDateTime::now_utc();
|
||||
let exp_time = current_time.clone() + std::time::Duration::from_secs(60 * 60 * 24 * 30);
|
||||
let client_token_obj = ClientTokenRequest {
|
||||
jti: Uuid::new_v4(),
|
||||
iss: "NLS Service Instance".to_string(),
|
||||
aud: "NLS Licensed Client".to_string(),
|
||||
iat: current_time.clone(),
|
||||
nbf: current_time.clone(),
|
||||
exp: exp_time,
|
||||
update_mode: "ABSOLUTE".to_string(),
|
||||
scope_ref_list: config_state.scope_ref_list.clone(),
|
||||
fulfillment_class_ref_list: None,
|
||||
service_instance_configuration: ServiceInstanceConfiguration {
|
||||
nls_service_instance_ref: config_state.nls_service_instance_ref.clone(),
|
||||
svc_port_set_list: vec![PortSet {
|
||||
idx: 0,
|
||||
d_name: "DLS".to_string(),
|
||||
svc_port_map: vec![
|
||||
PortMap {
|
||||
service: "auth".to_string(),
|
||||
port: req_port.clone(),
|
||||
}, PortMap {
|
||||
service: "lease".to_string(),
|
||||
port: req_port.clone(),
|
||||
},
|
||||
],
|
||||
}],
|
||||
node_url_list: vec![
|
||||
NodeUrl {
|
||||
idx: 0,
|
||||
url: req_host.clone(),
|
||||
url_qr: req_host.clone(),
|
||||
svc_port_set_idx: 0,
|
||||
}
|
||||
],
|
||||
},
|
||||
service_instance_public_key_configuration: ServiceInstancePublicKeyConfiguration {
|
||||
service_instance_public_key_me: ServiceInstancePublicKeyMe { type_mod: format!("{:x}", key_modulus), exp: key_exponent.to_u32().unwrap() },
|
||||
service_instance_public_key_pem: token_public_key.to_public_key_pem(LineEnding::LF).unwrap(),
|
||||
key_retention_mode: "LATEST_ONLY".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let jwt_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256);
|
||||
|
||||
let client_token_str = jsonwebtoken::encode(
|
||||
&jwt_header,
|
||||
&client_token_obj,
|
||||
&jsonwebtoken::EncodingKey::from_rsa_pem(&config_state.rsa_client_token.private_key).expect("Error read jwt encode key!"),
|
||||
).expect("Failed to encode jwt data!");
|
||||
|
||||
let tok_format = format_description!("[day]-[month]-[year]-[hour]:[minute]:[second]");
|
||||
let tok_time = OffsetDateTime::now_utc().format(&tok_format).unwrap();
|
||||
HttpResponse::Ok()
|
||||
.content_type(ContentType::octet_stream())
|
||||
.append_header(("Content-Disposition", format!("attachment; filename=\"client_configuration_token_{}.tok\"", &tok_time)))
|
||||
.body(client_token_str.into_bytes())
|
||||
}
|
||||
|
||||
|
||||
#[post("/auth/v1/origin")]
|
||||
pub async fn origin_req(origin_request: web::Json<OriginRequest>) -> Result<impl Responder> {
|
||||
let origin_response = OriginResponse {
|
||||
origin_ref: origin_request.candidate_origin_ref,
|
||||
environment: origin_request.environment.clone(),
|
||||
svc_port_set_list: None,
|
||||
node_url_list: None,
|
||||
node_query_order: None,
|
||||
prompts: None,
|
||||
sync_timestamp: OffsetDateTime::now_utc(),
|
||||
};
|
||||
Ok(web::Json(origin_response))
|
||||
}
|
||||
|
||||
|
||||
// todo error catch
|
||||
#[post("/auth/v1/code")]
|
||||
pub async fn code_req(code_request: web::Json<CodeRequest>, config_state: web::Data<AppConfigState>) -> Either<web::Json<CodeResponse>, Result<HttpResponse, ApiError>> {
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
let exp_time = time_now.clone() + std::time::Duration::from_secs(600);
|
||||
let code_challenge = code_request.code_challenge.clone();
|
||||
|
||||
let ac_data = AcData {
|
||||
iat: time_now,
|
||||
exp: exp_time,
|
||||
challenge: code_challenge,
|
||||
origin_ref: code_request.origin_ref,
|
||||
key_ref: uuid!("00000000-0000-0000-0000-000000000000"),
|
||||
kid: uuid!("00000000-0000-0000-0000-000000000000"),
|
||||
};
|
||||
|
||||
let mut jwt_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256);
|
||||
jwt_header.kid = Option::from("00000000-0000-0000-0000-000000000000".to_string());
|
||||
|
||||
let auth_code = jsonwebtoken::encode(
|
||||
&jwt_header,
|
||||
&ac_data,
|
||||
&jsonwebtoken::EncodingKey::from_rsa_pem(&config_state.rsa_server_jwt.private_key).expect("Error read jwt encode key!"),
|
||||
).expect("Failed to encode jwt data!");
|
||||
|
||||
let code_resp = CodeResponse {
|
||||
auth_code,
|
||||
sync_timestamp: OffsetDateTime::now_utc(),
|
||||
prompts: None,
|
||||
};
|
||||
Either::Left(web::Json(code_resp))
|
||||
}
|
||||
|
||||
|
||||
#[post("/auth/v1/token")]
|
||||
pub async fn auth_token(token_request: web::Json<TokenRequest>, config_state: web::Data<AppConfigState>) -> Either<web::Json<TokenResponse>, Result<HttpResponse, ApiError>> {
|
||||
// decode jwt
|
||||
let client_auth_code = match jsonwebtoken::decode::<AuthCode>(
|
||||
&token_request.auth_code,
|
||||
&jsonwebtoken::DecodingKey::from_rsa_pem(&config_state.rsa_server_jwt.public_key).expect("Error read jwt decode key"),
|
||||
&jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::RS256),
|
||||
) {
|
||||
Ok(data) => data,
|
||||
Err(decode_error) => {
|
||||
log::info!("jwt decode error!");
|
||||
return Either::Right(Err(ApiError::InternalError { detail: decode_error.to_string() }));
|
||||
}
|
||||
};
|
||||
|
||||
// check challenge
|
||||
let code_verifier = token_request.code_verifier.clone();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&code_verifier);
|
||||
let result = hasher.finalize();
|
||||
let b64_result = base64::encode(result).rsplit("=").collect::<String>();
|
||||
if b64_result != client_auth_code.claims.challenge {
|
||||
log::info!("Challenge failed!");
|
||||
return Either::Right(Err(ApiError::InternalError { detail: "Challenge Failed!".to_string() }));
|
||||
}
|
||||
|
||||
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
let exp_time = time_now.clone() + std::time::Duration::from_secs(60 * 60); // 1hr
|
||||
|
||||
let auth_token = JwtAuthToken {
|
||||
iat: time_now.clone(),
|
||||
nbf: time_now.clone(),
|
||||
iss: "https://cls.nvidia.org".to_string(),
|
||||
aud: "https://cls.nvidia.org".to_string(),
|
||||
exp: exp_time.clone(),
|
||||
origin_ref: client_auth_code.claims.origin_ref,
|
||||
key_ref: client_auth_code.claims.key_ref,
|
||||
kid: client_auth_code.claims.kid,
|
||||
};
|
||||
|
||||
|
||||
let mut jwt_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256);
|
||||
jwt_header.kid = Option::from("00000000-0000-0000-0000-000000000000".to_string());
|
||||
|
||||
let auth_token = jsonwebtoken::encode(
|
||||
&jwt_header,
|
||||
&auth_token,
|
||||
&jsonwebtoken::EncodingKey::from_rsa_pem(&config_state.rsa_server_jwt.private_key).expect("Error read jwt encode key!"),
|
||||
).expect("Failed to encode jwt data!");
|
||||
|
||||
let token_resp = TokenResponse {
|
||||
auth_token,
|
||||
expires: exp_time,
|
||||
prompts: None,
|
||||
sync_timestamp: time_now,
|
||||
};
|
||||
Either::Left(web::Json(token_resp))
|
||||
}
|
||||
|
||||
|
||||
// todo error catch
|
||||
#[get("/v1/lessor/leases")]
|
||||
pub async fn get_all_leases(jwt_auth_token: Option<web::ReqData<JwtAuthToken>>, redis_client: web::Data<redis::Client>) -> Result<impl Responder> {
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
|
||||
let mut client_leases = ClientLeasesResponse {
|
||||
active_lease_list: vec![],
|
||||
prompts: None,
|
||||
sync_timestamp: time_now,
|
||||
};
|
||||
|
||||
let origin_ref: Uuid = match jwt_auth_token {
|
||||
Some(data) => data.origin_ref,
|
||||
None => {
|
||||
return Ok(web::Json(client_leases));
|
||||
}
|
||||
};
|
||||
|
||||
let mut redis_conn = redis_client.get_async_connection().await.expect("failed to get redis connection!");
|
||||
|
||||
let lease_data: Vec<String> = redis::cmd("ZRANGEBYSCORE")
|
||||
.arg(&[
|
||||
origin_ref.to_string().as_str(),
|
||||
format!("({}", time_now.unix_timestamp()).as_str(),
|
||||
"+inf"
|
||||
])
|
||||
.query_async(&mut redis_conn)
|
||||
.await.expect("unable to get redis");
|
||||
|
||||
if lease_data.len() == 0 {
|
||||
return Ok(web::Json(client_leases));
|
||||
} else {
|
||||
for lease in lease_data {
|
||||
client_leases.active_lease_list.push(Uuid::from_str(lease.as_str()).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(web::Json(client_leases))
|
||||
}
|
||||
|
||||
#[post("/v1/lessor")]
|
||||
pub async fn add_lessor(
|
||||
jwt_auth_token: Option<web::ReqData<JwtAuthToken>>,
|
||||
config_state: web::Data<AppConfigState>,
|
||||
redis_client: web::Data<redis::Client>,
|
||||
) -> Either<web::Json<AddLessorResponse>, Result<HttpResponse, actix_web::Error>> {
|
||||
let origin_ref: Uuid = match jwt_auth_token {
|
||||
Some(data) => data.origin_ref,
|
||||
None => {
|
||||
return Either::Right(Ok(HttpResponse::Ok().content_type("application/json").body("none")));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
let exp_time = time_now + time::Duration::minutes(i64::from(config_state.lease_time));
|
||||
|
||||
let new_ref = Uuid::new_v4();
|
||||
|
||||
let mut redis_conn = redis_client.get_async_connection().await.expect("failed to get redis connection!");
|
||||
let redis_status: i32 = redis::cmd("ZADD")
|
||||
.arg(&[
|
||||
origin_ref.to_string().as_str(),
|
||||
exp_time.unix_timestamp().to_string().as_str(),
|
||||
new_ref.to_string().as_str()
|
||||
])
|
||||
.query_async(&mut redis_conn)
|
||||
.await.expect("unable to get redis");
|
||||
|
||||
if !(redis_status == 1 || redis_status == 0) {
|
||||
return Either::Right(Ok(HttpResponse::Ok().content_type("application/json").body("none")));
|
||||
}
|
||||
let add_resp = AddLessorResponse {
|
||||
lease_result_list: vec![
|
||||
CreateLeaseResult {
|
||||
error: None,
|
||||
lease: LeaseCreateDetail {
|
||||
type_ref: new_ref,
|
||||
created: time_now,
|
||||
expires: exp_time,
|
||||
recommended_lease_renewal: config_state.lease_renewal_factor.clone(),
|
||||
offline_lease: false,
|
||||
license_type: "CONCURRENT_COUNTED_SINGLE".to_string(),
|
||||
},
|
||||
ordinal: None,
|
||||
}
|
||||
],
|
||||
prompts: None,
|
||||
result_code: None,
|
||||
sync_timestamp: time_now,
|
||||
};
|
||||
Either::Left(web::Json(add_resp))
|
||||
}
|
||||
|
||||
#[put("/v1/lease/{lease_ref}")]
|
||||
pub async fn update_lessor(
|
||||
req_path: web::Path<Uuid>,
|
||||
jwt_auth_token: Option<web::ReqData<JwtAuthToken>>,
|
||||
config_state: web::Data<AppConfigState>,
|
||||
redis_client: web::Data<redis::Client>,
|
||||
) -> Either<web::Json<UpdateLeasesResponse>, Result<HttpResponse, actix_web::Error>> {
|
||||
// env setup
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
let exp_time = time_now + time::Duration::minutes(i64::from(config_state.lease_time));
|
||||
let lease_ref = req_path.into_inner();
|
||||
let mut redis_conn = redis_client.get_async_connection().await.expect("failed to get redis connection!");
|
||||
|
||||
let mut error_resp = UpdateLeasesErrorResponse {
|
||||
code: 404,
|
||||
message: "".to_string(),
|
||||
prompts: None,
|
||||
sync_timestamp: time_now,
|
||||
};
|
||||
|
||||
// get ref from jwt
|
||||
let origin_ref: Uuid = match jwt_auth_token {
|
||||
Some(data) => data.origin_ref,
|
||||
None => {
|
||||
error_resp.code = 500;
|
||||
error_resp.message = format!("Unable to get origin_ref!");
|
||||
return Either::Right(Ok(HttpResponse::BadRequest().content_type("application/json").body(serde_json::to_string(&error_resp).unwrap())));
|
||||
}
|
||||
};
|
||||
|
||||
// get all lease by ref from redis
|
||||
let lease_data: Vec<String> = redis::cmd("ZRANGEBYSCORE")
|
||||
.arg(&[
|
||||
origin_ref.to_string().as_str(),
|
||||
format!("({}", time_now.unix_timestamp()).as_str(),
|
||||
"+inf"
|
||||
])
|
||||
.query_async(&mut redis_conn)
|
||||
.await.expect("unable to get redis");
|
||||
|
||||
// check lease_ref in db or not
|
||||
if !lease_data.contains(&lease_ref.to_string()) {
|
||||
error_resp.message = format!("no current lease found for: {}", lease_ref);
|
||||
return Either::Right(Ok(HttpResponse::NotFound().content_type("application/json").body(serde_json::to_string(&error_resp).unwrap())));
|
||||
} else {
|
||||
// update the lease
|
||||
let redis_status: i32 = redis::cmd("ZADD")
|
||||
.arg(&[
|
||||
origin_ref.to_string().as_str(),
|
||||
exp_time.unix_timestamp().to_string().as_str(),
|
||||
lease_ref.to_string().as_str()
|
||||
])
|
||||
.query_async(&mut redis_conn)
|
||||
.await.expect("unable to get redis");
|
||||
|
||||
if !(redis_status == 1 || redis_status == 0) {
|
||||
error_resp.code = 500;
|
||||
error_resp.message = format!("Failed to update lease: {}", lease_ref);
|
||||
return Either::Right(Ok(HttpResponse::InternalServerError().content_type("application/json").body(serde_json::to_string(&error_resp).unwrap())));
|
||||
}
|
||||
}
|
||||
|
||||
let update_resp = UpdateLeasesResponse {
|
||||
expires: exp_time,
|
||||
lease_ref: lease_ref,
|
||||
offline_lease: false,
|
||||
prompts: None,
|
||||
recommended_lease_renewal: config_state.lease_renewal_factor.clone(),
|
||||
sync_timestamp: time_now,
|
||||
};
|
||||
Either::Left(web::Json(update_resp))
|
||||
}
|
||||
|
||||
#[delete("/v1/lessor/leases")]
|
||||
pub async fn delete_all_leases(
|
||||
jwt_auth_token: Option<web::ReqData<JwtAuthToken>>,
|
||||
redis_client: web::Data<redis::Client>,
|
||||
) -> Either<web::Json<DeleteLeasesResponse>, Result<HttpResponse, actix_web::Error>> {
|
||||
// env setup
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
let mut redis_conn = redis_client.get_async_connection().await.expect("failed to get redis connection!");
|
||||
|
||||
// get ref from jwt
|
||||
let origin_ref: Uuid = match jwt_auth_token {
|
||||
Some(data) => data.origin_ref,
|
||||
None => {
|
||||
return Either::Right(Ok(HttpResponse::BadRequest().content_type("text/html").body("None")));
|
||||
}
|
||||
};
|
||||
|
||||
let mut del_resp = DeleteLeasesResponse {
|
||||
release_failure_list: None,
|
||||
released_lease_list: vec![],
|
||||
prompts: None,
|
||||
sync_timestamp: time_now,
|
||||
};
|
||||
|
||||
// get all lease by ref from redis
|
||||
let lease_data: Vec<String> = redis::cmd("ZRANGEBYSCORE")
|
||||
.arg(&[
|
||||
origin_ref.to_string().as_str(),
|
||||
format!("({}", time_now.unix_timestamp()).as_str(),
|
||||
"+inf"
|
||||
])
|
||||
.query_async(&mut redis_conn)
|
||||
.await.expect("unable to get redis");
|
||||
|
||||
if !lease_data.is_empty() {
|
||||
// delete the lease by ref
|
||||
let redis_status: i32 = redis::cmd("DEL")
|
||||
.arg(&[
|
||||
origin_ref.to_string().as_str(),
|
||||
])
|
||||
.query_async(&mut redis_conn)
|
||||
.await.expect("unable to get redis");
|
||||
if redis_status != 1 {
|
||||
del_resp.release_failure_list = Some(lease_data.iter().map(|x| Uuid::from_str(x.as_str()).unwrap()).collect());
|
||||
}
|
||||
|
||||
for lease in lease_data {
|
||||
del_resp.released_lease_list.push(Uuid::from_str(lease.as_str()).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Either::Left(web::Json(del_resp))
|
||||
}
|
58
src/cert_tools.rs
Normal file
58
src/cert_tools.rs
Normal file
@ -0,0 +1,58 @@
|
||||
#![allow(clippy::complexity, clippy::style, clippy::pedantic)]
|
||||
|
||||
use rsa::{RsaPrivateKey, RsaPublicKey};
|
||||
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use rcgen::{Certificate, CertificateParams, DistinguishedName, date_time_ymd, DnType};
|
||||
use std::convert::TryFrom;
|
||||
use crate::utils::MyRsaKeyPair;
|
||||
|
||||
|
||||
pub fn gen_rsa2048() -> MyRsaKeyPair {
|
||||
let mut rng = rand::thread_rng();
|
||||
let bits = 2048;
|
||||
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
|
||||
let pub_key = RsaPublicKey::from(&priv_key);
|
||||
|
||||
let pub_key_str = pub_key.to_public_key_pem(LineEnding::LF).unwrap();
|
||||
let priv_key_str = priv_key.to_pkcs8_pem(LineEnding::LF).unwrap().as_str().to_owned();
|
||||
|
||||
MyRsaKeyPair {
|
||||
public_key: pub_key_str.into_bytes(),
|
||||
private_key: priv_key_str.into_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn gen_cert_rsa2048(common_name: String) -> MyRsaKeyPair {
|
||||
let mut params: CertificateParams = Default::default();
|
||||
params.not_before = date_time_ymd(2021, 01, 01);
|
||||
params.not_after = date_time_ymd(2030, 12, 30);
|
||||
params.distinguished_name = DistinguishedName::new();
|
||||
params.distinguished_name.push(DnType::CommonName, common_name);
|
||||
params.distinguished_name.push(DnType::OrganizationalUnitName, "Server Cert");
|
||||
params.distinguished_name.push(DnType::OrganizationName, "ORG");
|
||||
params.distinguished_name.push(DnType::CountryName, "RS");
|
||||
|
||||
params.alg = &rcgen::PKCS_RSA_SHA256;
|
||||
|
||||
|
||||
let mut rng = OsRng;
|
||||
let bits = 2048;
|
||||
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to gen private key!");
|
||||
let private_key_der = private_key.to_pkcs8_der().unwrap();
|
||||
let key_pair = rcgen::KeyPair::try_from(private_key_der.as_bytes()).unwrap();
|
||||
params.key_pair = Some(key_pair);
|
||||
|
||||
let cert = Certificate::from_params(params).expect("Failed to gen cert!");
|
||||
let pem_serialized = cert.serialize_pem().unwrap();
|
||||
|
||||
let cert_str = pem_serialized;
|
||||
let cert_key = cert.serialize_private_key_pem();
|
||||
|
||||
MyRsaKeyPair {
|
||||
public_key: cert_str.into_bytes(),
|
||||
private_key: cert_key.into_bytes(),
|
||||
}
|
||||
}
|
50
src/core_struct/client_token.rs
Normal file
50
src/core_struct/client_token.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use uuid::Uuid;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
use time::serde as time_serde;
|
||||
use crate::core_struct::{NodeUrl, PortSet};
|
||||
|
||||
time_serde::format_description!(rfc3339_ms_z, OffsetDateTime, "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ClientTokenRequest {
|
||||
pub jti: Uuid,
|
||||
pub iss: String,
|
||||
pub aud: String,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub iat: OffsetDateTime,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub nbf: OffsetDateTime,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub exp: OffsetDateTime,
|
||||
// TODO should come from UI as param
|
||||
pub update_mode: String,
|
||||
pub scope_ref_list: Vec<Uuid>,
|
||||
pub fulfillment_class_ref_list: Option<Vec<String>>,
|
||||
pub service_instance_configuration: ServiceInstanceConfiguration,
|
||||
pub service_instance_public_key_configuration: ServiceInstancePublicKeyConfiguration,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ServiceInstanceConfiguration {
|
||||
pub nls_service_instance_ref: Uuid,
|
||||
pub svc_port_set_list: Vec<PortSet>,
|
||||
pub node_url_list: Vec<NodeUrl>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ServiceInstancePublicKeyConfiguration {
|
||||
pub service_instance_public_key_me: ServiceInstancePublicKeyMe,
|
||||
pub service_instance_public_key_pem: String,
|
||||
pub key_retention_mode: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ServiceInstancePublicKeyMe {
|
||||
#[serde(rename = "mod")]
|
||||
pub type_mod: String,
|
||||
pub exp: u32,
|
||||
}
|
37
src/core_struct/code.rs
Normal file
37
src/core_struct/code.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use uuid::Uuid;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
use time::serde as time_serde;
|
||||
|
||||
use crate::core_struct::LicenseProviderPrompt;
|
||||
|
||||
|
||||
time_serde::format_description!(rfc3339_ms_z, OffsetDateTime, "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct CodeRequest {
|
||||
pub code_challenge: String,
|
||||
pub origin_ref: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CodeResponse {
|
||||
// jwt
|
||||
pub auth_code: String,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AcData {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub iat: OffsetDateTime,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub exp: OffsetDateTime,
|
||||
pub challenge: String,
|
||||
pub origin_ref: Uuid,
|
||||
pub key_ref: Uuid,
|
||||
pub kid: Uuid,
|
||||
}
|
93
src/core_struct/leases.rs
Normal file
93
src/core_struct/leases.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use uuid::Uuid;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
use time::serde as time_serde;
|
||||
|
||||
use crate::core_struct::LicenseProviderPrompt;
|
||||
|
||||
|
||||
time_serde::format_description!(rfc3339_ms_z, OffsetDateTime, "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
|
||||
time_serde::format_description!(rfc3339_ms, OffsetDateTime, "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]");
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ClientLeasesResponse {
|
||||
pub active_lease_list: Vec<Uuid>,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ClientLeasesErrorResponse {
|
||||
pub detail: String,
|
||||
pub status: u32,
|
||||
pub title: String,
|
||||
#[serde(rename = "type")]
|
||||
pub error_type: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AddLessorResponse {
|
||||
pub lease_result_list: Vec<CreateLeaseResult>,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
// one of "SUCCESS", "FULFILLMENT_FAILURE", "INVALID_LEASE_PROPOSAL", "UNKNOWN_ACCESS_GROUP", "INFRASTRUCTURE_FAILURE", or None -> SUCCESS
|
||||
pub result_code: Option<String>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateLeaseResult {
|
||||
pub error: Option<String>,
|
||||
pub lease: LeaseCreateDetail,
|
||||
pub ordinal: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LeaseCreateDetail {
|
||||
#[serde(rename = "ref")]
|
||||
pub type_ref: Uuid,
|
||||
#[serde(with = "rfc3339_ms")]
|
||||
pub created: OffsetDateTime,
|
||||
#[serde(with = "rfc3339_ms")]
|
||||
pub expires: OffsetDateTime,
|
||||
pub recommended_lease_renewal: f32,
|
||||
pub offline_lease: bool,
|
||||
// default to CONCURRENT_COUNTED_SINGLE now, seem apply for all req
|
||||
pub license_type: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateLeasesResponse {
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub expires: OffsetDateTime,
|
||||
pub lease_ref: Uuid,
|
||||
pub offline_lease: bool,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
pub recommended_lease_renewal: f32,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateLeasesErrorResponse {
|
||||
pub message: String,
|
||||
pub code: u16,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct DeleteLeasesResponse {
|
||||
pub release_failure_list: Option<Vec<Uuid>>,
|
||||
pub released_lease_list: Vec<Uuid>,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
76
src/core_struct/mod.rs
Normal file
76
src/core_struct/mod.rs
Normal file
@ -0,0 +1,76 @@
|
||||
pub mod origin;
|
||||
pub mod code;
|
||||
pub mod client_token;
|
||||
pub mod token;
|
||||
pub mod leases;
|
||||
|
||||
use uuid::Uuid;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
use crate::utils::MyRsaKeyPair;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppConfigState {
|
||||
pub req_port: u16,
|
||||
pub req_host: String,
|
||||
pub scope_ref_list: Vec<Uuid>,
|
||||
pub nls_service_instance_ref: Uuid,
|
||||
pub lease_time: u16,
|
||||
pub lease_renewal_factor: f32,
|
||||
pub rsa_client_token: MyRsaKeyPair,
|
||||
pub rsa_server_jwt: MyRsaKeyPair,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RedisTaskConfig {
|
||||
pub redis_url: String,
|
||||
pub task_interval: u16,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PortSet {
|
||||
pub idx: u8,
|
||||
pub d_name: String,
|
||||
pub svc_port_map: Vec<PortMap>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PortMap {
|
||||
pub service: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct NodeUrl {
|
||||
pub idx: u8,
|
||||
pub url: String,
|
||||
pub url_qr: String,
|
||||
pub svc_port_set_idx: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LicenseProviderPrompt {
|
||||
// datetime
|
||||
pub ts: String,
|
||||
pub prompt_ref: String,
|
||||
pub operation_type: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct JwtAuthToken {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub iat: OffsetDateTime,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub nbf: OffsetDateTime,
|
||||
pub iss: String,
|
||||
pub aud: String,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub exp: OffsetDateTime,
|
||||
pub origin_ref: Uuid,
|
||||
pub key_ref: Uuid,
|
||||
pub kid: Uuid,
|
||||
}
|
43
src/core_struct/origin.rs
Normal file
43
src/core_struct/origin.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use uuid::Uuid;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
use time::serde as time_serde;
|
||||
use crate::core_struct::{LicenseProviderPrompt, NodeUrl, PortSet};
|
||||
|
||||
time_serde::format_description!(rfc3339_ms_z, OffsetDateTime, "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OriginRequest {
|
||||
pub environment: EnvironmentData,
|
||||
pub candidate_origin_ref: Uuid,
|
||||
pub registration_pending: bool,
|
||||
pub update_pending: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct EnvironmentData {
|
||||
pub fingerprint: FingerprintData,
|
||||
pub guest_driver_version: String,
|
||||
pub hostname: String,
|
||||
pub os_platform: String,
|
||||
pub os_version: String,
|
||||
pub ip_address_list: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct FingerprintData {
|
||||
pub mac_address_list: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct OriginResponse {
|
||||
pub origin_ref: Uuid,
|
||||
pub environment: EnvironmentData,
|
||||
pub svc_port_set_list: Option<Vec<PortSet>>,
|
||||
pub node_url_list: Option<Vec<NodeUrl>>,
|
||||
pub node_query_order: Option<Vec<u8>>,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
47
src/core_struct/token.rs
Normal file
47
src/core_struct/token.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
use time::serde as time_serde;
|
||||
use uuid::Uuid;
|
||||
use crate::core_struct::LicenseProviderPrompt;
|
||||
|
||||
time_serde::format_description!(rfc3339_ms_z, OffsetDateTime, "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TokenRequest {
|
||||
pub auth_code: String,
|
||||
pub code_verifier: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AuthCode {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub iat: OffsetDateTime,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub exp: OffsetDateTime,
|
||||
pub challenge: String,
|
||||
pub origin_ref: Uuid,
|
||||
pub key_ref: Uuid,
|
||||
pub kid: Uuid,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TokenResponse {
|
||||
pub auth_token: String,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub expires: OffsetDateTime,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TokenErrorResponse {
|
||||
pub code: u16,
|
||||
pub message: String,
|
||||
pub prompts: Option<Vec<LicenseProviderPrompt>>,
|
||||
#[serde(with = "rfc3339_ms_z")]
|
||||
pub sync_timestamp: OffsetDateTime,
|
||||
}
|
220
src/main.rs
Normal file
220
src/main.rs
Normal file
@ -0,0 +1,220 @@
|
||||
mod utils;
|
||||
mod cert_tools;
|
||||
mod core_struct;
|
||||
mod api;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use actix_web::{get, web, App, Error, HttpServer, Responder, HttpResponse, middleware, dev::ServiceRequest, HttpMessage};
|
||||
|
||||
use actix_web_httpauth::{
|
||||
extractors::bearer::BearerAuth,
|
||||
middleware::HttpAuthentication,
|
||||
};
|
||||
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::utils::{load_config, prepare_redis};
|
||||
use crate::core_struct::{AppConfigState, JwtAuthToken, RedisTaskConfig};
|
||||
use crate::api::{add_lessor, auth_token, code_req, delete_all_leases, gen_client_token, get_all_leases, origin_req, update_lessor};
|
||||
use crate::core_struct::leases::ClientLeasesErrorResponse;
|
||||
|
||||
|
||||
#[get("/")]
|
||||
async fn hello() -> impl Responder {
|
||||
HttpResponse::Ok().body("Hello world!")
|
||||
}
|
||||
|
||||
|
||||
// todo impl new type for json error
|
||||
async fn leases_validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (Error, ServiceRequest)> {
|
||||
let config_state = req.app_data::<web::Data<AppConfigState>>().cloned().expect("Server Error");
|
||||
|
||||
// decode jwt
|
||||
let client_auth_token = match jsonwebtoken::decode::<JwtAuthToken>(
|
||||
&credentials.token(),
|
||||
&jsonwebtoken::DecodingKey::from_rsa_pem(&config_state.rsa_server_jwt.public_key).expect("Error read jwt decode key"),
|
||||
&jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::RS256),
|
||||
) {
|
||||
Ok(token_data) => token_data,
|
||||
Err(jwt_error) => {
|
||||
let error_resp = ClientLeasesErrorResponse {
|
||||
detail: jwt_error.to_string(),
|
||||
status: 401,
|
||||
title: "Unauthorized".to_string(),
|
||||
error_type: "about:blank".to_string(),
|
||||
};
|
||||
let new_error = actix_web::error::ErrorUnauthorized(serde_json::to_string_pretty(&error_resp).unwrap());
|
||||
return Err((new_error, req));
|
||||
}
|
||||
};
|
||||
req.extensions_mut().insert(client_auth_token.claims);
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
|
||||
async fn async_clean_lease(task_config: RedisTaskConfig, shutdown_marker: Arc<AtomicBool>) -> std::io::Result<()> {
|
||||
let job_interval = i64::from(task_config.task_interval) * 60;
|
||||
let start_time = OffsetDateTime::now_utc();
|
||||
|
||||
loop {
|
||||
if shutdown_marker.load(Ordering::SeqCst) {
|
||||
log::info!("RedisTask Stop.");
|
||||
break;
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
|
||||
if ((OffsetDateTime::now_utc() - start_time).whole_seconds() % job_interval) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
let _ = match redis::Client::open(task_config.redis_url.clone().as_str()) {
|
||||
Ok(redis_client) => {
|
||||
let _ = match redis_client.get_async_connection().await {
|
||||
Ok(mut redis_connection) => {
|
||||
let _ = match redis::cmd("keys").arg(&["*"]).query_async::<_, Vec<String>>(&mut redis_connection).await {
|
||||
Ok(ref_list) => {
|
||||
let time_now = OffsetDateTime::now_utc();
|
||||
for ref_id in ref_list {
|
||||
let _ = match redis::cmd("ZREMRANGEBYSCORE")
|
||||
.arg(&[
|
||||
ref_id.as_str(),
|
||||
"0",
|
||||
time_now.unix_timestamp().to_string().as_str()
|
||||
])
|
||||
.query_async::<_, i32>(&mut redis_connection)
|
||||
.await {
|
||||
Ok(redis_status) => {
|
||||
if redis_status != 0 {
|
||||
log::info!("RedisTask: ref: {} clean with {} leases.", &ref_id, &redis_status);
|
||||
}
|
||||
}
|
||||
Err(redis_err) => {
|
||||
log::debug!("RedisTask: cannot remove ref: {}, Cause: {}", &ref_id, redis_err);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(redis_err) => {
|
||||
log::debug!("RedisTask: Cannot get keys! Cause: {}", redis_err);
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("RedisTask: Cannot open redis connection!")
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("RedisTask: Cannot open redis client!")
|
||||
}
|
||||
};
|
||||
log::debug!("RedisTask cleanup done.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
// log setting
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("debug"));
|
||||
|
||||
// startup check
|
||||
let config_data = load_config();
|
||||
let redis_client = web::Data::new(prepare_redis(config_data.redis_url.clone()).await);
|
||||
let task_config = RedisTaskConfig {
|
||||
redis_url: config_data.redis_url.clone(),
|
||||
task_interval: config_data.redis_task_interval.clone(),
|
||||
};
|
||||
|
||||
let shared_data = web::Data::new(AppConfigState {
|
||||
req_host: config_data.req_host.clone(),
|
||||
req_port: config_data.req_port.clone(),
|
||||
scope_ref_list: config_data.scope_ref_list,
|
||||
nls_service_instance_ref: config_data.nls_service_instance_ref,
|
||||
lease_time: config_data.lease_time,
|
||||
lease_renewal_factor: config_data.lease_renewal_factor,
|
||||
rsa_client_token: config_data.rsa_client_token,
|
||||
rsa_server_jwt: config_data.rsa_server_jwt,
|
||||
});
|
||||
|
||||
|
||||
// tls content
|
||||
let public_key_der = pem::parse(&config_data.cert_https.public_key).unwrap().contents;
|
||||
let private_key_der = pem::parse(&config_data.cert_https.private_key).unwrap().contents;
|
||||
|
||||
let public_key = vec![Certificate(public_key_der)];
|
||||
let private_key = PrivateKey(private_key_der);
|
||||
|
||||
// configure certificate and private key used by https
|
||||
let tls_config = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(public_key, private_key)
|
||||
.unwrap();
|
||||
|
||||
|
||||
// paste server addr and port
|
||||
let bind_addr: SocketAddr = match format!("{}:{}", &config_data.server_addr, &config_data.server_port).parse() {
|
||||
Ok(addr) => {
|
||||
log::info!("starting HTTPS server at https://{}", addr);
|
||||
addr
|
||||
}
|
||||
_ => {
|
||||
log::error!("Error read bind address and port!");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
let leases_auth = HttpAuthentication::bearer(leases_validator);
|
||||
let leasing_scope = web::scope("/leasing")
|
||||
.service(get_all_leases)
|
||||
.service(add_lessor)
|
||||
.service(update_lessor)
|
||||
.service(delete_all_leases)
|
||||
.wrap(leases_auth);
|
||||
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(hello)
|
||||
.service(gen_client_token)
|
||||
.service(origin_req)
|
||||
.service(code_req)
|
||||
.service(auth_token)
|
||||
.service(leasing_scope)
|
||||
.app_data(shared_data.clone())
|
||||
.app_data(redis_client.clone())
|
||||
})
|
||||
.keep_alive(std::time::Duration::from_secs(5))
|
||||
.bind_rustls(bind_addr, tls_config)?
|
||||
.workers(2)
|
||||
.run();
|
||||
|
||||
let server_handle = server.handle();
|
||||
let task_shutdown_marker = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// create my task
|
||||
let server_task = tokio::spawn(server);
|
||||
let worker_task = tokio::spawn(async_clean_lease(task_config, Arc::clone(&task_shutdown_marker)));
|
||||
|
||||
let shutdown = tokio::spawn(async move {
|
||||
// listen for ctrl-c
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
|
||||
// start shutdown of tasks
|
||||
let server_stop = server_handle.stop(true);
|
||||
task_shutdown_marker.store(true, Ordering::SeqCst);
|
||||
|
||||
// await shutdown of tasks
|
||||
server_stop.await;
|
||||
});
|
||||
|
||||
let _ = tokio::try_join!(server_task, worker_task, shutdown).expect("unable to join tasks");
|
||||
|
||||
Ok(())
|
||||
}
|
251
src/utils.rs
Normal file
251
src/utils.rs
Normal file
@ -0,0 +1,251 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Seek, Write};
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use obfstr::obfstr;
|
||||
use crate::cert_tools::{gen_cert_rsa2048, gen_rsa2048};
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MyRsaKeyPair {
|
||||
pub public_key: Vec<u8>,
|
||||
pub private_key: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct ConfigObj {
|
||||
pub server_addr: String,
|
||||
pub server_port: u16,
|
||||
pub domain: String,
|
||||
pub req_host: String,
|
||||
pub req_port: u16,
|
||||
pub redis_url: String,
|
||||
pub redis_task_interval: u16,
|
||||
pub scope_ref_list: Vec<Uuid>,
|
||||
pub nls_service_instance_ref: Uuid,
|
||||
pub lease_time: u16,
|
||||
pub lease_renewal_factor: f32,
|
||||
pub cert_https: MyRsaKeyPair,
|
||||
pub rsa_client_token: MyRsaKeyPair,
|
||||
pub rsa_server_jwt: MyRsaKeyPair,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct KeyPairPath {
|
||||
public_key: String,
|
||||
private_key: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ConfigData {
|
||||
server_addr: String,
|
||||
server_port: u16,
|
||||
domain: String,
|
||||
req_host: String,
|
||||
req_port: u16,
|
||||
redis_url: String,
|
||||
redis_task_interval: u16,
|
||||
scope_ref_list: Vec<Uuid>,
|
||||
nls_service_instance_ref: Uuid,
|
||||
lease_time: u16,
|
||||
lease_renewal_factor: u8,
|
||||
cert_https: KeyPairPath,
|
||||
rsa_client_token: KeyPairPath,
|
||||
}
|
||||
|
||||
|
||||
fn read_keypair(kp_path: &KeyPairPath, domain: Option<String>) -> MyRsaKeyPair {
|
||||
let public_key_path = Path::new(&kp_path.public_key);
|
||||
let private_key_path = Path::new(&kp_path.private_key);
|
||||
|
||||
if (public_key_path.exists()) && (private_key_path.exists()) {
|
||||
MyRsaKeyPair {
|
||||
public_key: std::fs::read(&public_key_path).expect("failed to read key"),
|
||||
private_key: std::fs::read(&private_key_path).expect("failed to read key"),
|
||||
}
|
||||
} else {
|
||||
let gen_keypair;
|
||||
match domain {
|
||||
Some(domain) => {
|
||||
gen_keypair = gen_cert_rsa2048(domain);
|
||||
}
|
||||
None => {
|
||||
gen_keypair = gen_rsa2048();
|
||||
}
|
||||
}
|
||||
|
||||
let mut public_key_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&public_key_path).expect("Unable to open public key file");
|
||||
public_key_file.write(&gen_keypair.public_key).expect("Unable to write public key file");
|
||||
|
||||
let mut private_key_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&private_key_path).expect("Unable to open private key file");
|
||||
private_key_file.write(&gen_keypair.private_key).expect("Unable to write private key file");
|
||||
gen_keypair
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn load_config() -> ConfigObj {
|
||||
let data_path = Path::new(".").join("data");
|
||||
let config_path = &data_path.join("config.json");
|
||||
if !&data_path.exists() {
|
||||
match std::fs::create_dir(&data_path) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
eprintln!("Failed to make data dir!");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
let config_data: ConfigData = match OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.create(true)
|
||||
.open(&config_path) {
|
||||
Ok(mut file_obj) => {
|
||||
let inner_data: ConfigData = match serde_json::from_reader(&file_obj) {
|
||||
Ok(json_data) => json_data,
|
||||
Err(deserialize_error) => {
|
||||
log::error!("Failed to parse config: {:?}", deserialize_error);
|
||||
log::warn!("Using Default Setting.");
|
||||
|
||||
let default_configs = ConfigData {
|
||||
server_addr: "127.0.0.1".to_string(),
|
||||
server_port: 443,
|
||||
req_host: "127.0.0.1".to_string(),
|
||||
req_port: 443,
|
||||
domain: "localhost".to_string(),
|
||||
redis_url: "redis://127.0.0.1/".to_string(),
|
||||
redis_task_interval: 30,
|
||||
scope_ref_list: vec![Uuid::new_v4(), Uuid::new_v4()],
|
||||
nls_service_instance_ref: Uuid::new_v4(),
|
||||
lease_time: 1440,
|
||||
lease_renewal_factor: 35,
|
||||
cert_https: KeyPairPath {
|
||||
public_key: "./data/https_cert.pem".to_string(),
|
||||
private_key: "./data/https_key.pem".to_string(),
|
||||
},
|
||||
rsa_client_token: KeyPairPath {
|
||||
public_key: "./data/token_cert.pem".to_string(),
|
||||
private_key: "./data/token_key.pem".to_string(),
|
||||
},
|
||||
};
|
||||
// set write to start and remove nul
|
||||
let _ = &file_obj.set_len(0);
|
||||
let _ = file_obj.rewind().unwrap();
|
||||
match serde_json::to_writer_pretty(&file_obj, &default_configs) {
|
||||
Ok(_) => {
|
||||
log::warn!("Saved default config data.")
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("Failed to save config data!")
|
||||
}
|
||||
};
|
||||
default_configs
|
||||
}
|
||||
};
|
||||
inner_data
|
||||
}
|
||||
Err(file_error) => {
|
||||
log::error!("Failed to open config file: {:?}", file_error);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if config_data.lease_renewal_factor > 100 {
|
||||
log::error!("Config: failed to get lease_renewal_factor, value must be between 1 and 100!");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
|
||||
obfstr! {
|
||||
let pub_key = "-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqd6EjBnZ68/Al/hQxCmz
|
||||
5qW3eMXulTFYJb2WopgepjZCDvc2q/57qoekgVQEy9OwhXoXF8VnnTaoUaN7YZWA
|
||||
r/woQ0Zkwe10FcWbT3Pju/DznqscmZPbSoru+SnUrxqZmzWeOo0q6l0w28tBZ2HC
|
||||
+9ie95WHCfst/jVwZ+slsRAy7Uv5CwXeqIXubFhGwPV7+ICB2tmJiQPJcM+Y2tTK
|
||||
FeaDyN9yKaUUjdjG80CGIKUnPdNCPEo/Cpf727rOCLl67kOd4mPmTrvyD0/nmREx
|
||||
CQUSZt1smMFHR+uA11oN12I0yIy322gozwAyjd2r9Fok133/0EVTQqZ+ZmBExfor
|
||||
3QIDAQAB
|
||||
-----END PUBLIC KEY-----";
|
||||
let priv_key = "-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAqd6EjBnZ68/Al/hQxCmz5qW3eMXulTFYJb2WopgepjZCDvc2
|
||||
q/57qoekgVQEy9OwhXoXF8VnnTaoUaN7YZWAr/woQ0Zkwe10FcWbT3Pju/Dznqsc
|
||||
mZPbSoru+SnUrxqZmzWeOo0q6l0w28tBZ2HC+9ie95WHCfst/jVwZ+slsRAy7Uv5
|
||||
CwXeqIXubFhGwPV7+ICB2tmJiQPJcM+Y2tTKFeaDyN9yKaUUjdjG80CGIKUnPdNC
|
||||
PEo/Cpf727rOCLl67kOd4mPmTrvyD0/nmRExCQUSZt1smMFHR+uA11oN12I0yIy3
|
||||
22gozwAyjd2r9Fok133/0EVTQqZ+ZmBExfor3QIDAQABAoIBAGEY7WD74eH15du4
|
||||
N8p5H/kmHoKteRvUkdM41KLqGxLdDtNpIdocY+ntEO5P7VHpFgyl5g9Tak+mD35i
|
||||
2ULFZ0Kw+v7BfRSQu3s6cfVvg+xI5ah7nKR4rK+mTMUl0QmqRcU/V8uWJ8LBNA1e
|
||||
2GrUqdS1VOCmmwLsjbSyLSdpdSkI72pZYdP4FwF4MVfWu4gFrVf/vSxjy0l5wY/1
|
||||
iCma0sjzS8npFP4Wf58PeBqbYUMJ/bCwPF+UYkx2xaRlAWWWtJsMdCQE4NBHqa1w
|
||||
ZfHC5Y7oLjdYi0EDQeinqjnDIcKD/dedtDvrK5N3yL9D3VxiE0J2irzk9tNXvezf
|
||||
S6lIqC0CgYEA333pptjB5DAZ+HihtziWSOvYPd3Ibz5BhUFqKJy9TjQh/IC7sVix
|
||||
4ieKl4uLPWvURPZeWWJi56ncWQSwMLjTsVyIt49XKCzznLdHnHijb4kGHg+ycGpu
|
||||
kT4pbjm+Dxts/ZifQHVlzmnRuBHb2S/s7Gk1XTn6AbCSZIUUPuLQ7pMCgYEAwpPg
|
||||
t6Qto5M8cKG20x0SyBpkge3SJTXm4aahm+cQUAf3ylVX6MIqSTF6zwAjlP7mL8a8
|
||||
ePFMHqwYZ2KRq/rrPwljmNHBMIx7Weh789S5q4meoa6yT2maQuA+7vRAmLqXwuLK
|
||||
gz51mfAqFTbhGxLh2RpRicOK7CfC+byT3OF4kc8CgYAodGtR91SJkKdy0as8NjMF
|
||||
+iMHd9jrQhKsI14rAcxGlqs8QLU48fwpGs08h1bqBFXFMe98MJIEqzumpXGbMCmp
|
||||
pj1dNMYrEI/8YzTEPxYef2grEt5S+QEQq3bma+9aXrWI5hKVoWqPRZpfvmPUWZeC
|
||||
Z7zwJil6GtM0/N3gUEBPnwKBgQCZeyoD0VZKtAY11emvhzxcaS0kq+JahbUUA2tw
|
||||
3YepiU9043LPX/EZARWdGL/4dERAJWRfhf6EJz2stzyuyuMrOw276qCX2ggmuFKl
|
||||
2AOJAqoFYRa3u1X6MIaT2Ejn8C9rg5c4hVkgTyfyyfIwd+l8Zd0xbPQ1KXwLoCuG
|
||||
TLfdUwKBgHboiqjd22q6y753MUOFxr4TVHSOwYNTnzFjKyyjRZYXXz7r3e/xbVuW
|
||||
Msgzaul+8rF3E83ZbR9u3Z2IURQZHKgA1e9rBMSf0dDnQ677oW/UubyoSHGwRuEV
|
||||
BdLF6msAUzkXM1R1zS9Pk/5AVO54fj/HxYnsv+THMw8FvqGUMAz+
|
||||
-----END RSA PRIVATE KEY-----";
|
||||
}
|
||||
|
||||
let config_return = ConfigObj {
|
||||
server_addr: config_data.server_addr.clone(),
|
||||
server_port: config_data.server_port.clone(),
|
||||
domain: config_data.domain.clone(),
|
||||
req_host: config_data.req_host.clone(),
|
||||
req_port: config_data.req_port.clone(),
|
||||
redis_url: config_data.redis_url.clone(),
|
||||
redis_task_interval: config_data.redis_task_interval.clone(),
|
||||
scope_ref_list: config_data.scope_ref_list.clone(),
|
||||
nls_service_instance_ref: config_data.nls_service_instance_ref.clone(),
|
||||
lease_time: config_data.lease_time.clone(),
|
||||
lease_renewal_factor: f32::from(config_data.lease_renewal_factor.clone()) / 100.0,
|
||||
cert_https: read_keypair(&config_data.cert_https, Option::from(config_data.domain.clone())),
|
||||
rsa_client_token: read_keypair(&config_data.rsa_client_token, None),
|
||||
rsa_server_jwt: MyRsaKeyPair {
|
||||
public_key: pub_key.as_bytes().to_vec(),
|
||||
private_key: priv_key.as_bytes().to_vec(),
|
||||
},
|
||||
};
|
||||
config_return
|
||||
}
|
||||
|
||||
pub async fn prepare_redis(connection_url: String) -> redis::Client {
|
||||
match redis::Client::open(connection_url) {
|
||||
Ok(redis_client) => {
|
||||
let _ = match redis_client.get_async_connection().await {
|
||||
Ok(conn)=> conn,
|
||||
Err(connection_error) =>{
|
||||
log::error!("Failed to get redis connection! Cause: {}", connection_error);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
redis_client
|
||||
},
|
||||
Err(client_err) => {
|
||||
log::error!("Failed to open redis client! Cause: {}", client_err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user