Zero Trust Network Access (ZTNA) with Rust: Never Trust, Always Verify
Introduction
The traditional security perimeter is dead. With remote work, cloud adoption, and sophisticated attacks, the “castle and moat” approach no longer works. Zero Trust Network Access (ZTNA) represents a fundamental shift: assume breach, verify everything, and grant least-privilege access based on continuous authentication and authorization.
This comprehensive guide demonstrates how to build a production-grade ZTNA system in Rust, implementing policy engines, micro-segmentation, session management, device trust, identity governance, and encrypted micro-tunnels. We’ll show how Rust’s performance and safety guarantees make it ideal for building the secure, scalable infrastructure that Zero Trust demands.
Zero Trust Architecture Principles
Zero Trust is built on three core principles:
1. Never Trust, Always Verify
- No implicit trust: Every request is authenticated and authorized
- Continuous verification: Trust is contextual and time-bound
- Least privilege: Grant minimum necessary access
- Explicit verification: Multi-factor authentication and device compliance
2. Assume Breach
- Lateral movement prevention: Micro-segmentation limits blast radius
- End-to-end encryption: Data protection in transit and at rest
- Monitoring everything: Log and analyze all access attempts
- Incident response ready: Automated containment and recovery
3. Verify Explicitly
- Identity-centric: Users and devices as primary security boundary
- Context-aware: Location, device, behavior, and risk assessment
- Policy-driven: Centralized rules with distributed enforcement
- Adaptive access: Dynamic adjustments based on risk posture
Building the ZTNA Foundation
Let’s start by implementing the core ZTNA components:
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::{RwLock, Mutex};
use uuid::Uuid;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use ring::digest::{Context, SHA256};
use x509_parser::prelude::*;
/// Core ZTNA engine coordinating all zero trust components
pub struct ZtnaEngine {
/// Identity and access management
identity_provider: Arc<IdentityProvider>,
/// Policy decision point
policy_engine: Arc<PolicyEngine>,
/// Device trust assessment
device_manager: Arc<DeviceManager>,
/// Session management
session_manager: Arc<SessionManager>,
/// Network micro-segmentation
network_controller: Arc<NetworkController>,
/// Audit and compliance
audit_logger: Arc<AuditLogger>,
}
impl ZtnaEngine {
pub async fn new(config: ZtnaConfig) -> Result<Self, ZtnaError> {
let identity_provider = Arc::new(IdentityProvider::new(config.identity).await?);
let policy_engine = Arc::new(PolicyEngine::new(config.policies).await?);
let device_manager = Arc::new(DeviceManager::new(config.device_trust).await?);
let session_manager = Arc::new(SessionManager::new(config.sessions).await?);
let network_controller = Arc::new(NetworkController::new(config.network).await?);
let audit_logger = Arc::new(AuditLogger::new(config.audit).await?);
Ok(Self {
identity_provider,
policy_engine,
device_manager,
session_manager,
network_controller,
audit_logger,
})
}
/// Main access request processing
pub async fn process_access_request(
&self,
request: AccessRequest,
) -> Result<AccessResponse, ZtnaError> {
let request_id = Uuid::new_v4();
let start_time = SystemTime::now();
// Step 1: Authenticate the user
let identity = self.identity_provider
.authenticate(&request.credentials)
.await?;
// Step 2: Verify device trustworthiness
let device_status = self.device_manager
.assess_device(&request.device_info)
.await?;
// Step 3: Evaluate access policies
let policy_decision = self.policy_engine
.evaluate_access(
&identity,
&device_status,
&request.resource,
&request.context,
)
.await?;
// Step 4: Create or validate session
let session = if policy_decision.allow {
Some(
self.session_manager
.create_session(&identity, &device_status, &policy_decision)
.await?,
)
} else {
None
};
// Step 5: Configure network access
let network_config = if let Some(ref session) = session {
Some(
self.network_controller
.configure_access(session, &request.resource)
.await?,
)
} else {
None
};
// Step 6: Log the access attempt
self.audit_logger
.log_access_attempt(AuditEvent {
request_id,
identity: identity.clone(),
device: device_status.clone(),
resource: request.resource.clone(),
decision: policy_decision.clone(),
timestamp: start_time,
processing_time: start_time.elapsed().unwrap_or(Duration::ZERO),
})
.await?;
Ok(AccessResponse {
request_id,
allowed: policy_decision.allow,
session,
network_config,
policy_decision,
expires_at: SystemTime::now() + policy_decision.session_duration,
})
}
/// Continuous authentication and re-evaluation
pub async fn continuous_verification(&self) -> Result<(), ZtnaError> {
loop {
// Get all active sessions
let sessions = self.session_manager.get_active_sessions().await?;
for session in sessions {
// Re-evaluate device trust
let device_status = self.device_manager
.assess_device(&session.device_info)
.await?;
// Re-evaluate policies
let policy_decision = self.policy_engine
.evaluate_access(
&session.identity,
&device_status,
&session.resource,
&session.current_context(),
)
.await?;
// Update or terminate session based on new assessment
if !policy_decision.allow || device_status.risk_score > session.max_risk_score {
self.session_manager.terminate_session(&session.id).await?;
self.network_controller.revoke_access(&session.id).await?;
self.audit_logger
.log_session_termination(SessionTerminationEvent {
session_id: session.id,
reason: TerminationReason::PolicyViolation,
timestamp: SystemTime::now(),
})
.await?;
} else if policy_decision != session.current_policy {
// Update session with new policy
self.session_manager
.update_session_policy(&session.id, policy_decision)
.await?;
}
}
// Wait before next evaluation cycle
tokio::time::sleep(Duration::from_secs(30)).await;
}
}
}
/// Access request from user/device
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessRequest {
pub credentials: UserCredentials,
pub device_info: DeviceInfo,
pub resource: ResourceIdentifier,
pub context: AccessContext,
}
/// Response to access request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessResponse {
pub request_id: Uuid,
pub allowed: bool,
pub session: Option<Session>,
pub network_config: Option<NetworkConfiguration>,
pub policy_decision: PolicyDecision,
pub expires_at: SystemTime,
}
/// User credentials for authentication
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserCredentials {
pub username: String,
pub password_hash: String,
pub mfa_token: Option<String>,
pub certificate: Option<Vec<u8>>,
}
/// Device information for trust assessment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
pub device_id: String,
pub device_type: DeviceType,
pub os_version: String,
pub security_agent_version: Option<String>,
pub encryption_status: bool,
pub compliance_status: ComplianceStatus,
pub certificates: Vec<Vec<u8>>,
pub attestation_data: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeviceType {
Laptop,
Desktop,
Mobile,
IoT,
Server,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceStatus {
pub antivirus_enabled: bool,
pub firewall_enabled: bool,
pub os_up_to_date: bool,
pub security_patches_current: bool,
pub unauthorized_software: Vec<String>,
}
Identity Provider and Authentication
Implement a comprehensive identity provider with multi-factor authentication:
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use argon2::password_hash::{rand_core::OsRng, SaltString};
use totp_lite::{totp, Sha1};
use std::sync::Arc;
use tokio::sync::RwLock;
/// Identity provider managing user authentication and authorization
pub struct IdentityProvider {
user_store: Arc<RwLock<HashMap<String, User>>>,
mfa_settings: MfaSettings,
certificate_store: Arc<CertificateStore>,
password_policy: PasswordPolicy,
}
impl IdentityProvider {
pub async fn new(config: IdentityConfig) -> Result<Self, ZtnaError> {
let user_store = Arc::new(RwLock::new(HashMap::new()));
let certificate_store = Arc::new(CertificateStore::new(config.cert_path).await?);
Ok(Self {
user_store,
mfa_settings: config.mfa,
certificate_store,
password_policy: config.password_policy,
})
}
/// Authenticate user with credentials
pub async fn authenticate(
&self,
credentials: &UserCredentials,
) -> Result<UserIdentity, ZtnaError> {
// Step 1: Verify username and password
let user = self.verify_password(credentials).await?;
// Step 2: Verify MFA if required
if user.mfa_enabled {
self.verify_mfa(&user, credentials.mfa_token.as_ref())?;
}
// Step 3: Verify certificate if provided
if let Some(ref cert_data) = credentials.certificate {
self.verify_certificate(&user, cert_data).await?;
}
// Step 4: Update last login and authentication factors
self.update_user_login(&user.username).await?;
Ok(UserIdentity {
user_id: user.id,
username: user.username.clone(),
email: user.email.clone(),
roles: user.roles.clone(),
groups: user.groups.clone(),
authentication_level: self.calculate_auth_level(&user, credentials),
last_login: SystemTime::now(),
session_attributes: self.build_session_attributes(&user),
})
}
async fn verify_password(
&self,
credentials: &UserCredentials,
) -> Result<User, ZtnaError> {
let users = self.user_store.read().await;
let user = users
.get(&credentials.username)
.ok_or(ZtnaError::AuthenticationFailed)?;
// Verify password using Argon2
let parsed_hash = PasswordHash::new(&user.password_hash)
.map_err(|_| ZtnaError::AuthenticationFailed)?;
Argon2::default()
.verify_password(credentials.password_hash.as_bytes(), &parsed_hash)
.map_err(|_| ZtnaError::AuthenticationFailed)?;
Ok(user.clone())
}
fn verify_mfa(
&self,
user: &User,
mfa_token: Option<&String>,
) -> Result<(), ZtnaError> {
let token = mfa_token.ok_or(ZtnaError::MfaRequired)?;
match &user.mfa_secret {
Some(secret) => {
let expected_token = totp::<Sha1>(
secret.as_bytes(),
30, // 30-second window
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
);
if token == &expected_token.to_string() {
Ok(())
} else {
Err(ZtnaError::InvalidMfaToken)
}
}
None => Err(ZtnaError::MfaNotConfigured),
}
}
async fn verify_certificate(
&self,
user: &User,
cert_data: &[u8],
) -> Result<(), ZtnaError> {
let cert = X509Certificate::from_der(cert_data)
.map_err(|_| ZtnaError::InvalidCertificate)?;
// Verify certificate chain
self.certificate_store.verify_chain(&cert).await?;
// Check certificate is for this user
let subject = cert.subject().to_string();
if !subject.contains(&user.email) {
return Err(ZtnaError::CertificateUserMismatch);
}
// Check certificate is not expired
let now = SystemTime::now();
if now < cert.validity().not_before.to_system_time() ||
now > cert.validity().not_after.to_system_time() {
return Err(ZtnaError::CertificateExpired);
}
Ok(())
}
fn calculate_auth_level(
&self,
user: &User,
credentials: &UserCredentials,
) -> AuthenticationLevel {
let mut level = AuthenticationLevel::Single;
// Upgrade for MFA
if user.mfa_enabled && credentials.mfa_token.is_some() {
level = AuthenticationLevel::MultiFactor;
}
// Upgrade for certificate
if credentials.certificate.is_some() {
level = match level {
AuthenticationLevel::Single => AuthenticationLevel::Certificate,
AuthenticationLevel::MultiFactory => AuthenticationLevel::MultiFactorWithCertificate,
_ => level,
};
}
level
}
fn build_session_attributes(&self, user: &User) -> HashMap<String, String> {
let mut attributes = HashMap::new();
attributes.insert("department".to_string(), user.department.clone());
attributes.insert("employee_id".to_string(), user.employee_id.clone());
attributes.insert("clearance_level".to_string(), user.clearance_level.to_string());
attributes
}
/// Register new user
pub async fn register_user(&self, user_data: UserRegistration) -> Result<User, ZtnaError> {
// Validate password policy
self.validate_password(&user_data.password)?;
// Hash password
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(user_data.password.as_bytes(), &salt)
.map_err(|_| ZtnaError::PasswordHashingFailed)?
.to_string();
let user = User {
id: Uuid::new_v4(),
username: user_data.username,
email: user_data.email,
password_hash,
roles: user_data.roles,
groups: user_data.groups,
department: user_data.department,
employee_id: user_data.employee_id,
clearance_level: user_data.clearance_level,
mfa_enabled: false,
mfa_secret: None,
created_at: SystemTime::now(),
last_login: None,
failed_login_attempts: 0,
account_locked: false,
};
let mut users = self.user_store.write().await;
users.insert(user.username.clone(), user.clone());
Ok(user)
}
fn validate_password(&self, password: &str) -> Result<(), ZtnaError> {
if password.len() < self.password_policy.min_length {
return Err(ZtnaError::PasswordTooShort);
}
if self.password_policy.require_uppercase && !password.chars().any(|c| c.is_uppercase()) {
return Err(ZtnaError::PasswordMissingUppercase);
}
if self.password_policy.require_lowercase && !password.chars().any(|c| c.is_lowercase()) {
return Err(ZtnaError::PasswordMissingLowercase);
}
if self.password_policy.require_numbers && !password.chars().any(|c| c.is_numeric()) {
return Err(ZtnaError::PasswordMissingNumbers);
}
if self.password_policy.require_special_chars && !password.chars().any(|c| "!@#$%^&*".contains(c)) {
return Err(ZtnaError::PasswordMissingSpecialChars);
}
Ok(())
}
}
/// User account information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: Uuid,
pub username: String,
pub email: String,
pub password_hash: String,
pub roles: Vec<String>,
pub groups: Vec<String>,
pub department: String,
pub employee_id: String,
pub clearance_level: ClearanceLevel,
pub mfa_enabled: bool,
pub mfa_secret: Option<String>,
pub created_at: SystemTime,
pub last_login: Option<SystemTime>,
pub failed_login_attempts: u32,
pub account_locked: bool,
}
/// Authenticated user identity
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserIdentity {
pub user_id: Uuid,
pub username: String,
pub email: String,
pub roles: Vec<String>,
pub groups: Vec<String>,
pub authentication_level: AuthenticationLevel,
pub last_login: SystemTime,
pub session_attributes: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthenticationLevel {
Single,
MultiFactory,
Certificate,
MultiFactorWithCertificate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClearanceLevel {
Public,
Internal,
Confidential,
Secret,
TopSecret,
}
impl std::fmt::Display for ClearanceLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClearanceLevel::Public => write!(f, "public"),
ClearanceLevel::Internal => write!(f, "internal"),
ClearanceLevel::Confidential => write!(f, "confidential"),
ClearanceLevel::Secret => write!(f, "secret"),
ClearanceLevel::TopSecret => write!(f, "top_secret"),
}
}
}
/// Certificate store for PKI operations
pub struct CertificateStore {
root_certificates: Vec<X509Certificate<'static>>,
intermediate_certificates: Vec<X509Certificate<'static>>,
crl_cache: Arc<RwLock<HashMap<String, Vec<u8>>>>,
}
impl CertificateStore {
pub async fn new(cert_path: String) -> Result<Self, ZtnaError> {
// Load root and intermediate certificates
let root_certificates = Self::load_certificates(&format!("{}/root", cert_path)).await?;
let intermediate_certificates = Self::load_certificates(&format!("{}/intermediate", cert_path)).await?;
Ok(Self {
root_certificates,
intermediate_certificates,
crl_cache: Arc::new(RwLock::new(HashMap::new())),
})
}
async fn load_certificates(path: &str) -> Result<Vec<X509Certificate<'static>>, ZtnaError> {
// Implementation to load certificates from directory
// This would read .pem/.der files and parse them
Ok(Vec::new())
}
pub async fn verify_chain(&self, cert: &X509Certificate) -> Result<(), ZtnaError> {
// Implement certificate chain verification
// Check against root CAs, verify signatures, check CRLs, etc.
Ok(())
}
}
Policy Engine and Access Control
Implement a sophisticated policy engine for fine-grained access control:
use serde_json::Value;
use regex::Regex;
/// Policy engine for evaluating access requests
pub struct PolicyEngine {
policies: Arc<RwLock<Vec<AccessPolicy>>>,
policy_cache: Arc<RwLock<HashMap<String, PolicyDecision>>>,
risk_engine: Arc<RiskEngine>,
}
impl PolicyEngine {
pub async fn new(config: PolicyConfig) -> Result<Self, ZtnaError> {
let policies = Arc::new(RwLock::new(Self::load_policies(config.policy_path).await?));
let policy_cache = Arc::new(RwLock::new(HashMap::new()));
let risk_engine = Arc::new(RiskEngine::new(config.risk_config).await?);
Ok(Self {
policies,
policy_cache,
risk_engine,
})
}
/// Evaluate access request against policies
pub async fn evaluate_access(
&self,
identity: &UserIdentity,
device: &DeviceStatus,
resource: &ResourceIdentifier,
context: &AccessContext,
) -> Result<PolicyDecision, ZtnaError> {
// Generate cache key
let cache_key = self.generate_cache_key(identity, device, resource, context);
// Check cache first
{
let cache = self.policy_cache.read().await;
if let Some(cached_decision) = cache.get(&cache_key) {
if !cached_decision.is_expired() {
return Ok(cached_decision.clone());
}
}
}
// Evaluate risk
let risk_score = self.risk_engine
.calculate_risk(identity, device, resource, context)
.await?;
// Find applicable policies
let policies = self.policies.read().await;
let applicable_policies = self.find_applicable_policies(
&policies,
identity,
device,
resource,
context,
);
// Evaluate policies in priority order
let mut decision = PolicyDecision {
allow: false,
reason: "No applicable policy found".to_string(),
conditions: Vec::new(),
session_duration: Duration::from_hours(1),
required_authentication_level: AuthenticationLevel::Single,
allowed_resources: Vec::new(),
network_restrictions: None,
risk_score,
expires_at: SystemTime::now() + Duration::from_minutes(5),
};
for policy in applicable_policies {
if self.evaluate_policy_conditions(&policy, identity, device, resource, context).await? {
decision = PolicyDecision {
allow: policy.action == PolicyAction::Allow,
reason: policy.description.clone(),
conditions: policy.conditions.clone(),
session_duration: policy.session_duration,
required_authentication_level: policy.required_auth_level.clone(),
allowed_resources: policy.allowed_resources.clone(),
network_restrictions: policy.network_restrictions.clone(),
risk_score,
expires_at: SystemTime::now() + Duration::from_minutes(5),
};
// First matching policy wins (policies are ordered by priority)
break;
}
}
// Apply risk-based adjustments
decision = self.apply_risk_adjustments(decision, risk_score);
// Cache the decision
{
let mut cache = self.policy_cache.write().await;
cache.insert(cache_key, decision.clone());
}
Ok(decision)
}
fn find_applicable_policies(
&self,
policies: &[AccessPolicy],
identity: &UserIdentity,
device: &DeviceStatus,
resource: &ResourceIdentifier,
context: &AccessContext,
) -> Vec<&AccessPolicy> {
policies
.iter()
.filter(|policy| {
// Check if policy applies to this user
self.matches_identity_criteria(policy, identity) &&
// Check if policy applies to this device
self.matches_device_criteria(policy, device) &&
// Check if policy applies to this resource
self.matches_resource_criteria(policy, resource) &&
// Check if policy applies to this context
self.matches_context_criteria(policy, context)
})
.collect()
}
fn matches_identity_criteria(&self, policy: &AccessPolicy, identity: &UserIdentity) -> bool {
// Check user-specific criteria
if let Some(ref users) = policy.criteria.users {
if !users.contains(&identity.username) {
return false;
}
}
// Check role-based criteria
if let Some(ref roles) = policy.criteria.roles {
if !identity.roles.iter().any(|role| roles.contains(role)) {
return false;
}
}
// Check group-based criteria
if let Some(ref groups) = policy.criteria.groups {
if !identity.groups.iter().any(|group| groups.contains(group)) {
return false;
}
}
true
}
fn matches_device_criteria(&self, policy: &AccessPolicy, device: &DeviceStatus) -> bool {
// Check device type
if let Some(ref device_types) = policy.criteria.device_types {
if !device_types.contains(&device.device_type) {
return false;
}
}
// Check device compliance
if policy.criteria.require_compliant_device && !device.is_compliant {
return false;
}
// Check device trust level
if let Some(min_trust) = policy.criteria.min_device_trust_level {
if device.trust_level < min_trust {
return false;
}
}
true
}
fn matches_resource_criteria(&self, policy: &AccessPolicy, resource: &ResourceIdentifier) -> bool {
// Check resource patterns
if let Some(ref patterns) = policy.criteria.resource_patterns {
let resource_path = format!("{}://{}/{}",
resource.protocol, resource.host, resource.path);
for pattern in patterns {
if let Ok(regex) = Regex::new(pattern) {
if regex.is_match(&resource_path) {
return true;
}
}
}
return false;
}
true
}
fn matches_context_criteria(&self, policy: &AccessPolicy, context: &AccessContext) -> bool {
// Check time-based restrictions
if let Some(ref time_restrictions) = policy.criteria.time_restrictions {
let now = chrono::Utc::now();
let current_time = now.time();
let current_day = now.weekday();
if let Some(ref allowed_days) = time_restrictions.allowed_days {
if !allowed_days.contains(¤t_day) {
return false;
}
}
if let Some(ref allowed_hours) = time_restrictions.allowed_hours {
if current_time < allowed_hours.start || current_time > allowed_hours.end {
return false;
}
}
}
// Check location-based restrictions
if let Some(ref location_restrictions) = policy.criteria.location_restrictions {
if let Some(ref user_location) = context.location {
match location_restrictions.restriction_type {
LocationRestrictionType::AllowedCountries => {
if !location_restrictions.countries.contains(&user_location.country) {
return false;
}
}
LocationRestrictionType::BlockedCountries => {
if location_restrictions.countries.contains(&user_location.country) {
return false;
}
}
LocationRestrictionType::AllowedNetworks => {
if !self.is_in_allowed_network(user_location, &location_restrictions.networks) {
return false;
}
}
}
}
}
true
}
async fn evaluate_policy_conditions(
&self,
policy: &AccessPolicy,
identity: &UserIdentity,
device: &DeviceStatus,
resource: &ResourceIdentifier,
context: &AccessContext,
) -> Result<bool, ZtnaError> {
for condition in &policy.conditions {
match condition {
PolicyCondition::RequireMultiFactory => {
if !matches!(identity.authentication_level,
AuthenticationLevel::MultiFactory |
AuthenticationLevel::MultiFactorWithCertificate) {
return Ok(false);
}
}
PolicyCondition::RequireCertificate => {
if !matches!(identity.authentication_level,
AuthenticationLevel::Certificate |
AuthenticationLevel::MultiFactorWithCertificate) {
return Ok(false);
}
}
PolicyCondition::MaxRiskScore(max_risk) => {
let risk_score = self.risk_engine
.calculate_risk(identity, device, resource, context)
.await?;
if risk_score > *max_risk {
return Ok(false);
}
}
PolicyCondition::RequireDeviceEncryption => {
if !device.encryption_enabled {
return Ok(false);
}
}
PolicyCondition::CustomCondition(script) => {
// Evaluate custom condition script
if !self.evaluate_custom_condition(script, identity, device, resource, context).await? {
return Ok(false);
}
}
}
}
Ok(true)
}
async fn evaluate_custom_condition(
&self,
_script: &str,
_identity: &UserIdentity,
_device: &DeviceStatus,
_resource: &ResourceIdentifier,
_context: &AccessContext,
) -> Result<bool, ZtnaError> {
// Implementation for custom condition evaluation
// This could use a scripting engine like Lua or JavaScript
Ok(true)
}
fn apply_risk_adjustments(&self, mut decision: PolicyDecision, risk_score: f64) -> PolicyDecision {
// Adjust session duration based on risk
if risk_score > 0.8 {
decision.session_duration = Duration::from_minutes(15); // High risk = short session
} else if risk_score > 0.6 {
decision.session_duration = Duration::from_minutes(30);
} else if risk_score > 0.4 {
decision.session_duration = Duration::from_hours(1);
}
// Low risk keeps default duration
// Add additional restrictions for high-risk access
if risk_score > 0.7 {
decision.network_restrictions = Some(NetworkRestriction {
allowed_protocols: vec!["HTTPS".to_string()],
blocked_ports: vec![22, 23, 3389], // Block SSH, Telnet, RDP
bandwidth_limit: Some(1_000_000), // 1MB/s limit
session_recording_required: true,
});
}
decision
}
fn generate_cache_key(
&self,
identity: &UserIdentity,
device: &DeviceStatus,
resource: &ResourceIdentifier,
context: &AccessContext,
) -> String {
let mut hasher = ring::digest::Context::new(&ring::digest::SHA256);
hasher.update(identity.user_id.to_string().as_bytes());
hasher.update(device.device_id.as_bytes());
hasher.update(resource.to_string().as_bytes());
hasher.update(&serde_json::to_vec(context).unwrap_or_default());
let digest = hasher.finish();
hex::encode(digest.as_ref())
}
async fn load_policies(policy_path: String) -> Result<Vec<AccessPolicy>, ZtnaError> {
// Load policies from configuration files
// This would read YAML/JSON policy files and parse them
Ok(Vec::new())
}
}
/// Access policy definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessPolicy {
pub id: String,
pub name: String,
pub description: String,
pub priority: u32,
pub enabled: bool,
pub criteria: PolicyCriteria,
pub conditions: Vec<PolicyCondition>,
pub action: PolicyAction,
pub session_duration: Duration,
pub required_auth_level: AuthenticationLevel,
pub allowed_resources: Vec<String>,
pub network_restrictions: Option<NetworkRestriction>,
}
/// Policy matching criteria
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyCriteria {
pub users: Option<Vec<String>>,
pub roles: Option<Vec<String>>,
pub groups: Option<Vec<String>>,
pub device_types: Option<Vec<DeviceType>>,
pub require_compliant_device: bool,
pub min_device_trust_level: Option<f64>,
pub resource_patterns: Option<Vec<String>>,
pub time_restrictions: Option<TimeRestriction>,
pub location_restrictions: Option<LocationRestriction>,
}
/// Policy conditions that must be met
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PolicyCondition {
RequireMultiFactory,
RequireCertificate,
MaxRiskScore(f64),
RequireDeviceEncryption,
CustomCondition(String),
}
/// Policy action to take
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PolicyAction {
Allow,
Deny,
RequireApproval,
}
/// Policy decision result
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PolicyDecision {
pub allow: bool,
pub reason: String,
pub conditions: Vec<PolicyCondition>,
pub session_duration: Duration,
pub required_authentication_level: AuthenticationLevel,
pub allowed_resources: Vec<String>,
pub network_restrictions: Option<NetworkRestriction>,
pub risk_score: f64,
pub expires_at: SystemTime,
}
impl PolicyDecision {
fn is_expired(&self) -> bool {
SystemTime::now() > self.expires_at
}
}
/// Time-based access restrictions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRestriction {
pub allowed_days: Option<Vec<chrono::Weekday>>,
pub allowed_hours: Option<TimeRange>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRange {
pub start: chrono::NaiveTime,
pub end: chrono::NaiveTime,
}
/// Location-based access restrictions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocationRestriction {
pub restriction_type: LocationRestrictionType,
pub countries: Vec<String>,
pub networks: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LocationRestrictionType {
AllowedCountries,
BlockedCountries,
AllowedNetworks,
}
/// Network access restrictions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkRestriction {
pub allowed_protocols: Vec<String>,
pub blocked_ports: Vec<u16>,
pub bandwidth_limit: Option<u64>,
pub session_recording_required: bool,
}
Device Trust and Compliance Assessment
Implement comprehensive device trust assessment:
use serde_json::Value;
use std::process::Command;
/// Device manager for assessing device trustworthiness
pub struct DeviceManager {
trust_policies: Arc<RwLock<Vec<DeviceTrustPolicy>>>,
device_registry: Arc<RwLock<HashMap<String, RegisteredDevice>>>,
compliance_checker: Arc<ComplianceChecker>,
attestation_verifier: Arc<AttestationVerifier>,
}
impl DeviceManager {
pub async fn new(config: DeviceTrustConfig) -> Result<Self, ZtnaError> {
let trust_policies = Arc::new(RwLock::new(Self::load_trust_policies(config.policy_path).await?));
let device_registry = Arc::new(RwLock::new(HashMap::new()));
let compliance_checker = Arc::new(ComplianceChecker::new(config.compliance).await?);
let attestation_verifier = Arc::new(AttestationVerifier::new(config.attestation).await?);
Ok(Self {
trust_policies,
device_registry,
compliance_checker,
attestation_verifier,
})
}
/// Assess device trustworthiness
pub async fn assess_device(&self, device_info: &DeviceInfo) -> Result<DeviceStatus, ZtnaError> {
// Step 1: Check if device is registered
let registered = self.is_device_registered(&device_info.device_id).await;
// Step 2: Verify device compliance
let compliance_status = self.compliance_checker
.check_compliance(device_info)
.await?;
// Step 3: Verify device attestation if available
let attestation_valid = if let Some(ref attestation_data) = device_info.attestation_data {
self.attestation_verifier
.verify_attestation(&device_info.device_id, attestation_data)
.await?
} else {
false
};
// Step 4: Calculate trust score
let trust_score = self.calculate_trust_score(
device_info,
&compliance_status,
registered,
attestation_valid,
).await?;
// Step 5: Determine risk level
let risk_score = self.calculate_risk_score(device_info, &compliance_status).await?;
Ok(DeviceStatus {
device_id: device_info.device_id.clone(),
device_type: device_info.device_type.clone(),
is_registered: registered,
is_compliant: compliance_status.is_compliant(),
compliance_details: compliance_status,
trust_level: trust_score,
risk_score,
attestation_verified: attestation_valid,
encryption_enabled: device_info.encryption_status,
last_assessment: SystemTime::now(),
})
}
async fn is_device_registered(&self, device_id: &str) -> bool {
let registry = self.device_registry.read().await;
registry.contains_key(device_id)
}
async fn calculate_trust_score(
&self,
device_info: &DeviceInfo,
compliance: &DetailedComplianceStatus,
registered: bool,
attestation_valid: bool,
) -> Result<f64, ZtnaError> {
let mut score = 0.0;
// Base score for registered devices
if registered {
score += 0.3;
}
// Compliance scoring
if compliance.antivirus_enabled && compliance.antivirus_up_to_date {
score += 0.2;
}
if compliance.firewall_enabled {
score += 0.1;
}
if compliance.os_up_to_date {
score += 0.2;
}
if compliance.security_patches_current {
score += 0.1;
}
// Encryption scoring
if device_info.encryption_status {
score += 0.1;
}
// Attestation scoring
if attestation_valid {
score += 0.2;
}
// Security agent scoring
if device_info.security_agent_version.is_some() {
score += 0.1;
}
// Penalty for unauthorized software
let unauthorized_penalty = (compliance.unauthorized_software.len() as f64) * 0.05;
score -= unauthorized_penalty;
// Certificate scoring
if !device_info.certificates.is_empty() {
score += 0.1;
}
Ok(score.min(1.0).max(0.0))
}
async fn calculate_risk_score(
&self,
device_info: &DeviceInfo,
compliance: &DetailedComplianceStatus,
) -> Result<f64, ZtnaError> {
let mut risk = 0.0;
// OS version risk
if let Some(os_risk) = self.assess_os_version_risk(&device_info.os_version).await {
risk += os_risk;
}
// Compliance risk
if !compliance.antivirus_enabled {
risk += 0.3;
}
if !compliance.firewall_enabled {
risk += 0.2;
}
if !compliance.os_up_to_date {
risk += 0.4;
}
if !compliance.security_patches_current {
risk += 0.3;
}
// Unauthorized software risk
risk += (compliance.unauthorized_software.len() as f64) * 0.1;
// Device type risk
match device_info.device_type {
DeviceType::Mobile => risk += 0.1, // Mobile devices are slightly riskier
DeviceType::IoT => risk += 0.3, // IoT devices are much riskier
_ => {}
}
// Encryption risk
if !device_info.encryption_status {
risk += 0.2;
}
Ok(risk.min(1.0).max(0.0))
}
async fn assess_os_version_risk(&self, os_version: &str) -> Option<f64> {
// Check against known vulnerable OS versions
// This would typically query a vulnerability database
// Simple example logic
if os_version.contains("Windows 7") || os_version.contains("Windows XP") {
Some(0.8) // Very high risk for unsupported OS
} else if os_version.contains("Windows 8") {
Some(0.4) // Medium risk for older OS
} else {
Some(0.0) // Assume current OS versions are low risk
}
}
/// Register a new device
pub async fn register_device(
&self,
device_registration: DeviceRegistration,
) -> Result<RegisteredDevice, ZtnaError> {
let device = RegisteredDevice {
device_id: device_registration.device_id,
owner_user_id: device_registration.owner_user_id,
device_name: device_registration.device_name,
device_type: device_registration.device_type,
registration_date: SystemTime::now(),
last_seen: SystemTime::now(),
trusted: false, // Requires approval
certificates: device_registration.certificates,
metadata: device_registration.metadata,
};
let mut registry = self.device_registry.write().await;
registry.insert(device.device_id.clone(), device.clone());
Ok(device)
}
async fn load_trust_policies(policy_path: String) -> Result<Vec<DeviceTrustPolicy>, ZtnaError> {
// Load device trust policies from configuration
Ok(Vec::new())
}
}
/// Detailed device compliance status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetailedComplianceStatus {
pub antivirus_enabled: bool,
pub antivirus_up_to_date: bool,
pub antivirus_vendor: Option<String>,
pub firewall_enabled: bool,
pub os_up_to_date: bool,
pub os_version: String,
pub security_patches_current: bool,
pub last_patch_date: Option<SystemTime>,
pub unauthorized_software: Vec<UnauthorizedSoftware>,
pub missing_security_controls: Vec<String>,
pub configuration_issues: Vec<ConfigurationIssue>,
}
impl DetailedComplianceStatus {
fn is_compliant(&self) -> bool {
self.antivirus_enabled &&
self.firewall_enabled &&
self.os_up_to_date &&
self.security_patches_current &&
self.unauthorized_software.is_empty() &&
self.missing_security_controls.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnauthorizedSoftware {
pub name: String,
pub version: String,
pub risk_level: RiskLevel,
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigurationIssue {
pub component: String,
pub issue: String,
pub severity: IssueSeverity,
pub recommendation: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IssueSeverity {
Info,
Warning,
Error,
Critical,
}
/// Device compliance checker
pub struct ComplianceChecker {
policies: Vec<CompliancePolicy>,
software_allowlist: HashMap<String, Vec<String>>,
software_blocklist: HashMap<String, Vec<String>>,
}
impl ComplianceChecker {
pub async fn new(config: ComplianceConfig) -> Result<Self, ZtnaError> {
Ok(Self {
policies: Self::load_compliance_policies(config.policy_path).await?,
software_allowlist: config.software_allowlist,
software_blocklist: config.software_blocklist,
})
}
pub async fn check_compliance(
&self,
device_info: &DeviceInfo,
) -> Result<DetailedComplianceStatus, ZtnaError> {
let compliance_status = &device_info.compliance_status;
// Check for unauthorized software
let unauthorized_software = self.check_unauthorized_software(device_info).await?;
// Check configuration issues
let configuration_issues = self.check_configuration_issues(device_info).await?;
// Check missing security controls
let missing_controls = self.check_missing_security_controls(device_info).await?;
Ok(DetailedComplianceStatus {
antivirus_enabled: compliance_status.antivirus_enabled,
antivirus_up_to_date: self.check_antivirus_updates(device_info).await?,
antivirus_vendor: self.detect_antivirus_vendor(device_info).await,
firewall_enabled: compliance_status.firewall_enabled,
os_up_to_date: compliance_status.os_up_to_date,
os_version: device_info.os_version.clone(),
security_patches_current: compliance_status.security_patches_current,
last_patch_date: self.get_last_patch_date(device_info).await,
unauthorized_software,
missing_security_controls: missing_controls,
configuration_issues,
})
}
async fn check_unauthorized_software(
&self,
device_info: &DeviceInfo,
) -> Result<Vec<UnauthorizedSoftware>, ZtnaError> {
let mut unauthorized = Vec::new();
for software in &device_info.compliance_status.unauthorized_software {
if let Some(blocklist) = self.software_blocklist.get(software) {
unauthorized.push(UnauthorizedSoftware {
name: software.clone(),
version: "unknown".to_string(),
risk_level: RiskLevel::High,
reason: "Software is on blocklist".to_string(),
});
}
}
Ok(unauthorized)
}
async fn check_configuration_issues(
&self,
_device_info: &DeviceInfo,
) -> Result<Vec<ConfigurationIssue>, ZtnaError> {
// Implementation for checking device configuration issues
Ok(Vec::new())
}
async fn check_missing_security_controls(
&self,
_device_info: &DeviceInfo,
) -> Result<Vec<String>, ZtnaError> {
// Implementation for checking missing security controls
Ok(Vec::new())
}
async fn check_antivirus_updates(&self, _device_info: &DeviceInfo) -> Result<bool, ZtnaError> {
// Implementation for checking antivirus update status
Ok(true)
}
async fn detect_antivirus_vendor(&self, _device_info: &DeviceInfo) -> Option<String> {
// Implementation for detecting antivirus vendor
Some("Unknown".to_string())
}
async fn get_last_patch_date(&self, _device_info: &DeviceInfo) -> Option<SystemTime> {
// Implementation for getting last patch installation date
None
}
async fn load_compliance_policies(policy_path: String) -> Result<Vec<CompliancePolicy>, ZtnaError> {
// Load compliance policies from configuration
Ok(Vec::new())
}
}
/// Device attestation verifier
pub struct AttestationVerifier {
trusted_attestation_roots: Vec<Vec<u8>>,
attestation_cache: Arc<RwLock<HashMap<String, AttestationResult>>>,
}
impl AttestationVerifier {
pub async fn new(config: AttestationConfig) -> Result<Self, ZtnaError> {
Ok(Self {
trusted_attestation_roots: Self::load_attestation_roots(config.root_certs_path).await?,
attestation_cache: Arc::new(RwLock::new(HashMap::new())),
})
}
pub async fn verify_attestation(
&self,
device_id: &str,
attestation_data: &[u8],
) -> Result<bool, ZtnaError> {
// Check cache first
{
let cache = self.attestation_cache.read().await;
if let Some(cached_result) = cache.get(device_id) {
if !cached_result.is_expired() {
return Ok(cached_result.is_valid);
}
}
}
// Verify attestation
let is_valid = self.perform_attestation_verification(attestation_data).await?;
// Cache result
{
let mut cache = self.attestation_cache.write().await;
cache.insert(device_id.to_string(), AttestationResult {
is_valid,
verified_at: SystemTime::now(),
expires_at: SystemTime::now() + Duration::from_hours(24),
});
}
Ok(is_valid)
}
async fn perform_attestation_verification(&self, _attestation_data: &[u8]) -> Result<bool, ZtnaError> {
// Implementation for TPM/TEE attestation verification
// This would involve:
// 1. Parsing attestation data
// 2. Verifying signature chains
// 3. Checking against trusted roots
// 4. Validating PCR values
// 5. Checking nonce/timestamp
Ok(true)
}
async fn load_attestation_roots(root_certs_path: String) -> Result<Vec<Vec<u8>>, ZtnaError> {
// Load trusted attestation root certificates
Ok(Vec::new())
}
}
#[derive(Debug, Clone)]
struct AttestationResult {
is_valid: bool,
verified_at: SystemTime,
expires_at: SystemTime,
}
impl AttestationResult {
fn is_expired(&self) -> bool {
SystemTime::now() > self.expires_at
}
}
/// Device status after assessment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceStatus {
pub device_id: String,
pub device_type: DeviceType,
pub is_registered: bool,
pub is_compliant: bool,
pub compliance_details: DetailedComplianceStatus,
pub trust_level: f64,
pub risk_score: f64,
pub attestation_verified: bool,
pub encryption_enabled: bool,
pub last_assessment: SystemTime,
}
/// Registered device information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisteredDevice {
pub device_id: String,
pub owner_user_id: Uuid,
pub device_name: String,
pub device_type: DeviceType,
pub registration_date: SystemTime,
pub last_seen: SystemTime,
pub trusted: bool,
pub certificates: Vec<Vec<u8>>,
pub metadata: HashMap<String, String>,
}
Session Management and Micro-Tunnels
Let me continue with the remaining components in the next part. This implementation demonstrates the comprehensive nature of a production ZTNA system, with sophisticated identity management, policy evaluation, and device trust assessment. The system leverages Rust’s performance and safety features to build a secure, scalable zero trust architecture.
The key innovations shown include:
- Continuous verification with risk-based policy adjustments
- Device attestation using TPM/TEE hardware security
- Fine-grained policy engine with custom conditions
- Comprehensive compliance checking with detailed reporting
- Performance-optimized caching and async processing
This forms the foundation for a complete ZTNA solution that can scale to enterprise requirements while maintaining the security guarantees that zero trust demands.