feat: Implement SSH Agent Connector and gRPC server
- Added `AgentConnectorTrait` and `AgentConnector` for managing agent connections. - Introduced `SshAgentConnector` to handle SSH-related functionalities and start a gRPC server. - Created database entities for `agents`, `certificates`, `organizations`, `public_key_revocations`, `setup_tokens`, `upstreams`, `users`, `virtual_hosts`, and `workspaces` using SeaORM. - Developed `CertificateService` for managing certificate generation and retrieval. - Implemented the main server logic to initialize the database connection and start the agent server. - Configured development settings in `development.toml` for server and database connections.
This commit is contained in:
40
apps/nxmesh-master/src/connector/agent/mod.rs
Normal file
40
apps/nxmesh-master/src/connector/agent/mod.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tonic::transport::Server;
|
||||
|
||||
pub mod ssh;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait AgentConnectorTrait: Send + Sync {
|
||||
async fn start_server(
|
||||
&mut self,
|
||||
settings: &crate::config::settings::Settings,
|
||||
cert_service: Arc<dyn crate::service::certificate::CertificateService>,
|
||||
connection: DatabaseConnection,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
}
|
||||
|
||||
pub struct AgentConnector {
|
||||
connector: Box<dyn AgentConnectorTrait>,
|
||||
}
|
||||
|
||||
impl AgentConnector {
|
||||
pub fn new(connector: Box<dyn AgentConnectorTrait>) -> Self {
|
||||
Self { connector }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AgentConnectorTrait for AgentConnector {
|
||||
async fn start_server(
|
||||
&mut self,
|
||||
settings: &crate::config::settings::Settings,
|
||||
cert_service: Arc<dyn crate::service::certificate::CertificateService>,
|
||||
connection: DatabaseConnection,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.connector
|
||||
.start_server(settings, cert_service, connection)
|
||||
.await
|
||||
}
|
||||
}
|
||||
110
apps/nxmesh-master/src/connector/agent/ssh.rs
Normal file
110
apps/nxmesh-master/src/connector/agent/ssh.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use nxmesh_proto::{
|
||||
agent_service_server::AgentServiceServer,
|
||||
auth::ssh_auth::{CertificateValidationProvider, create_ssh_auth_interceptor},
|
||||
};
|
||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
|
||||
use tonic::transport::Server;
|
||||
|
||||
use crate::{db::entities::public_key_revocations, service::agent::AgentServerService};
|
||||
|
||||
use super::AgentConnectorTrait;
|
||||
|
||||
const MAX_CERTS_TO_CHECK: usize = 50;
|
||||
|
||||
pub struct SshAgentConnector {
|
||||
// router: Router<Stack<AsyncInterceptorLayer<SshAuthInterceptor>, Identity>>,
|
||||
settings: Arc<crate::config::settings::Settings>,
|
||||
}
|
||||
|
||||
impl SshAgentConnector {
|
||||
pub fn new(
|
||||
settings: impl Into<Arc<crate::config::settings::Settings>>,
|
||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(Self {
|
||||
settings: settings.into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_tls_config(
|
||||
cert_service: Arc<dyn crate::service::certificate::CertificateService>,
|
||||
) -> Result<tonic::transport::ServerTlsConfig, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let (san_ips, san_dns) =
|
||||
cert_service.get_sans(crate::service::certificate::ConnectionType::GRPC);
|
||||
let (cert_pem, key_pem) = cert_service
|
||||
.generate_pub_cert_pair(san_ips, san_dns)
|
||||
.await?;
|
||||
let (ca_cert_path, _) = cert_service.get_ca_cert().await?;
|
||||
let ca_cert_pem = std::fs::read_to_string(&ca_cert_path)?;
|
||||
|
||||
let tls_config = tonic::transport::ServerTlsConfig::new()
|
||||
.identity(tonic::transport::Identity::from_pem(cert_pem, key_pem))
|
||||
.client_ca_root(tonic::transport::Certificate::from_pem(ca_cert_pem));
|
||||
Ok(tls_config)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AgentConnectorTrait for SshAgentConnector {
|
||||
async fn start_server(
|
||||
&mut self,
|
||||
settings: &crate::config::settings::Settings,
|
||||
cert_service: Arc<dyn crate::service::certificate::CertificateService>,
|
||||
connection: DatabaseConnection,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let addr = settings.grpc.bind_address.clone().parse()?;
|
||||
let port = settings.grpc.port;
|
||||
let addr = std::net::SocketAddr::new(addr, port);
|
||||
|
||||
// Create the gRPC server
|
||||
let cert_validation_provider = Arc::new(CertificateValidationProviderImpl::new(connection));
|
||||
let ssh_interceptor = create_ssh_auth_interceptor(cert_validation_provider);
|
||||
let agent_server_service = AgentServiceServer::new(AgentServerService::default());
|
||||
|
||||
let tls_config = Self::get_tls_config(cert_service.clone()).await?;
|
||||
|
||||
let router = Server::builder()
|
||||
.tls_config(tls_config)?
|
||||
.layer(ssh_interceptor)
|
||||
.add_service(agent_server_service);
|
||||
|
||||
router.serve(addr).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct CertificateValidationProviderImpl {
|
||||
connection: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl CertificateValidationProviderImpl {
|
||||
pub fn new(connection: DatabaseConnection) -> Self {
|
||||
CertificateValidationProviderImpl { connection }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CertificateValidationProvider for CertificateValidationProviderImpl {
|
||||
async fn is_authorized(
|
||||
&self,
|
||||
certs: &Arc<Vec<tonic::transport::CertificateDer<'_>>>,
|
||||
) -> Result<bool, tonic::Status> {
|
||||
// check if the certificate's public key matches any agent's public key in the database
|
||||
let found = public_key_revocations::Entity::find()
|
||||
.filter(public_key_revocations::Column::PublicKeyHash.is_in(
|
||||
certs.iter().take(MAX_CERTS_TO_CHECK).map(|cert| {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(cert.as_ref());
|
||||
hex::encode(hasher.finalize())
|
||||
}),
|
||||
))
|
||||
.one(&self.connection)
|
||||
.await
|
||||
.map_err(|e| tonic::Status::internal(format!("Database query failed: {}", e)))?
|
||||
.is_some();
|
||||
|
||||
Ok(!found)
|
||||
}
|
||||
}
|
||||
1
apps/nxmesh-master/src/connector/mod.rs
Normal file
1
apps/nxmesh-master/src/connector/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod agent;
|
||||
Reference in New Issue
Block a user