Skip to main content

v0.20.0 Release Notes

EnerOS v0.20.0

Release Date: June 1, 2025 Codename: ZeroTrust Git Tag: v0.20.0 Support Status: Stable Total Crates: 56 (4 new) Test Cases: 8020+ (540 new)

Version Overview

EnerOS v0.20.0 “ZeroTrust” is the security capstone release for EnerOS’s “Internal Iteration” phase (v0.1.0 to v0.39.0). The primary goal is to introduce a Zero Trust Architecture, including mTLS mutual authentication, service mesh, automatic certificate rotation, and network policies, enabling EnerOS clusters to implement a “never trust, always verify” security model across three layers: inter-node communication, inter-service invocation, and client access, meeting NERC CIP, IEC 62443, and other power industry security compliance requirements.

The traditional security model adopts a “castle and moat” approach—internal networks are considered trusted, with protection only at the external perimeter. Once an attacker breaches the perimeter, they can move laterally within the internal network. As national critical information infrastructure, power systems face multiple risks including APT attacks, supply chain attacks, and internal threats—traditional perimeter protection can no longer meet requirements. Zero Trust Architecture assumes “the network is always untrusted,” with all communication requiring mutual authentication, least privilege, and continuous verification, fundamentally eliminating internal trust assumptions.

This version introduces four new crates: eneros-trust (zero trust core), eneros-trust-mtls (mTLS mutual authentication), eneros-trust-mesh (service mesh), and eneros-trust-cert (certificate management). The design philosophy is “identity as perimeter, encryption as default”—all services and nodes possess X.509 certificate identities, all communication is mTLS-encrypted by default, access control is identity-based rather than network-location-based, and certificate lifecycle is fully automated.

Key Metrics

Metricv0.19.0v0.20.0Improvement
Communication encryption coverage30%100%Full encryption
Certificate rotation automationManualFully automaticZero ops
Inter-service authenticationOne-wayMutual mTLSStrong identity
Network policy granularityIP-levelService-levelFine-grained
Compliance rate80%99%+19pp

New Features

1. Zero Trust Architecture

Introduced the eneros-trust crate as the unified entry point for the zero trust subsystem, coordinating identity authentication, access control, communication encryption, and policy enforcement. The zero trust architecture follows the NIST SP 800-207 standard, implementing the “Subject → Resource → Policy” access model.

Zero Trust Model

use eneros_trust::{ZeroTrust, TrustConfig, Identity, Policy};

let trust = ZeroTrust::new(TrustConfig {
    mtls_required: true,
    cert_authority: CertAuthority::internal("https://ca.internal:8200"),
    policy_engine: PolicyEngine::opa(),
    audit_all_access: true,
    default_deny: true,
})?;

// Register service identity
trust.register_identity(Identity::service("eneros-scheduler")
    .namespace("default")
    .permissions(&["topology:read", "dispatch:command"]))?;

// Enable zero trust
trust.enable().await?;

Zero Trust Principles

PrincipleImplementationDescription
Never trustDefault denyAll access denied by default
Always verifyPer-request authenticationNo cached trust
Least privilegeRBAC + ABACOnly necessary permissions granted
Continuous verificationReal-time policy evaluationPolicy changes take effect immediately
Encrypted communicationFull mTLS coverageAll traffic encrypted
Assume breachComprehensive auditingAll access recorded

2. mTLS Mutual Authentication

Introduced the eneros-trust-mtls crate, enforcing mTLS mutual authentication for all EnerOS internal communication (inter-node, inter-service, client-server). Both communicating parties must present valid X.509 certificates, with certificates issued by an internal CA and identity based on the certificate SAN (Subject Alternative Name) field.

mTLS Configuration

use eneros_trust_mtls::{MtlsConfig, MtlsManager};

let mtls = MtlsManager::new(MtlsConfig {
    ca_cert: "/etc/eneros/certs/ca.pem",
    server_cert: "/etc/eneros/certs/server.pem",
    server_key: "/etc/eneros/certs/server.key",
    client_cert: "/etc/eneros/certs/client.pem",
    client_key: "/etc/eneros/certs/client.key",
    min_tls_version: TlsVersion::V1_3,
    cipher_suites: CipherSuite::modern(),
    cert_rotation: RotationConfig::auto(Duration::days(30)),
})?;

// Apply to all gRPC / HTTP servers
ctx.apply_mtls(&mtls).await?;

// Apply to all outbound connections
ctx.apply_mtls_client(&mtls).await?;

Certificate Identity Model

use eneros_trust_mtls::{CertificateIdentity, SpiffeId};

// Service identity based on SPIFFE ID
let identity = CertificateIdentity::from_spiffe(
    SpiffeId::new("spiffe://eneros/default/scheduler")
)?;

// Extract identity from certificate
fn verify_peer(cert: &X509) -> Result<Identity> {
    let spiffe = cert.spiffe_id()?;
    let identity = Identity::from_spiffe(spiffe)?;
    
    // Verify certificate validity
    cert.verify_chain(&ca_cert)?;
    cert.verify_not_expired()?;
    cert.verify_not_revoked()?;
    
    Ok(identity)
}

mTLS Handshake Performance

PhaseDurationDescription
TCP handshake0.5msBasic connection
TLS handshake1.2msIncluding certificate verification
Session resumption0.3ms0-RTT
Total (new connection)1.7msAcceptable
Total (reused)0.3msVery low

3. Service Mesh

Introduced the eneros-trust-mesh crate, implementing a lightweight service mesh providing transparent mTLS encryption, load balancing, circuit breaking and rate limiting, and observability for inter-service communication within EnerOS. The service mesh uses the Sidecar pattern, transparent to applications without requiring business code modifications.

Service Mesh Architecture

use eneros_trust_mesh::{ServiceMesh, MeshConfig};

let mesh = ServiceMesh::new(MeshConfig {
    sidecar_image: "eneros/mesh-sidecar:0.20",
    control_plane: "mesh-control:15000",
    data_plane_port: 15001,
    mtls_enabled: true,
    auto_injection: true,
    tracing: true,
    metrics: true,
})?;

// Register services
mesh.register_service(Service::new("eneros-scheduler")
    .port(8080)
    .namespace("default")
    .mtls_required(true))?;

mesh.register_service(Service::new("eneros-twin")
    .port(8081)
    .namespace("default")
    .mtls_required(true))?;

mesh.start().await?;

Service Mesh Capabilities

CapabilityDescriptionTransparency
mTLS encryptionAuto-encrypt inter-service communicationFully transparent
Load balancingMulti-instance request distributionFully transparent
Circuit breakingAutomatic isolation of failed instancesFully transparent
Rate limitingService-level rate limitingConfiguration only
RetryAutomatic retry of failed requestsConfiguration only
TracingFull-chain tracingFully transparent
MetricsService-level metric exposureFully transparent

Traffic Policy

use eneros_trust_mesh::{TrafficPolicy, Rule};

// Define traffic policy
let policy = TrafficPolicy::new("scheduler-to-twin")
    .source("eneros-scheduler")
    .destination("eneros-twin")
    .mtls(MtlsMode::Strict)  // Enforce mTLS
    .load_balancing(LoadBalancing::round_robin())
    .circuit_breaker(CircuitBreaker::new()
        .error_threshold(0.1)
        .consecutive_errors(5)
        .cooldown(Duration::seconds(30)))
    .rate_limit(RateLimit::new(1000, Duration::seconds(1)))
    .retry(RetryPolicy::new()
        .max_attempts(3)
        .backoff(Backoff::exponential(Duration::milliseconds(50))));

mesh.apply_policy(policy).await?;

4. Automatic Certificate Rotation

Introduced the eneros-trust-cert crate, implementing full lifecycle automation for X.509 certificates, including issuance, distribution, rotation, and revocation. Certificate rotation requires no service restart, achieving zero-downtime updates through hot reloading.

Certificate Management

use eneros_trust_cert::{CertManager, CertConfig, CertSpec};

let cert_manager = CertManager::new(CertConfig {
    ca_endpoint: "https://ca.internal:8200",
    ca_token: env::var("CA_TOKEN")?,
    storage: CertStorage::disk("/etc/eneros/certs"),
    rotation_threshold: Duration::days(7),  // Rotate 7 days before expiry
    rotation_check: Duration::hours(1),
})?;

// Issue certificate for service
let cert = cert_manager.issue(CertSpec::new("eneros-scheduler")
    .namespace("default")
    .san_dns("scheduler.eneros.svc")
    .san_spiffe("spiffe://eneros/default/scheduler")
    .validity(Duration::days(90))
    .key_type(KeyType::EcdsaP256)
    .key_usage(&[KeyUsage::DigitalSignature, KeyUsage::KeyEncipherment]))?;

Automatic Rotation Flow

// Certificate rotation monitoring
cert_manager.on_rotation(|event| {
    log::info!("Certificate rotation: service={}", event.service);

    match event.phase {
        RotationPhase::Started => {
            log::info!("Starting certificate rotation for {}", event.service);
        }
        RotationPhase::NewCertIssued => {
            log::info!("New certificate issued, starting hot reload");
        }
        RotationPhase::HotReloaded => {
            log::info!("Certificate hot reloaded, new connections use new certificate");
        }
        RotationPhase::Completed => {
            log::info!("Old certificate revoked, rotation completed");
        }
    }
});

// Start automatic rotation
cert_manager.start_auto_rotation().await?;

Certificate Lifecycle

PhaseTriggerDurationImpact
IssuanceService registration500msNew certificate
DistributionAfter issuance100msCertificate to node
Rotation7 days before expiry2sZero downtime
Hot reloadAfter rotation200msNew connections use new cert
Revocation24h after rotation100msOld certificate invalidated

Certificate Revocation Check

use eneros_trust_cert::{RevocationList, OcspResponder};

// Configure revocation check
let revocation = RevocationList::new()
    .ocsp_responder(OcspResponder::new("http://ocsp.internal"))
    .crl_check(CrlCheck::enabled())
    .cache(Duration::minutes(5));

// Check revocation during mTLS handshake
mtls.with_revocation_check(revocation)?;

5. Network Policies

Introduced identity-based network policies, replacing traditional IP-based firewall rules. Network policies use service identity as granularity, declaratively defining allowed communication paths, denying all traffic not explicitly allowed by default.

Network Policy Definition

use eneros_trust::network::{NetworkPolicy, Rule, Direction};

let policy = NetworkPolicy::new("scheduler-allow-list")
    .namespace("default")
    .pod_selector(PodSelector::service("eneros-scheduler"))
    .ingress(Rule::allow()
        .from(IdentitySelector::service("eneros-gateway"))
        .to_ports(&[Port::tcp(8080)]))
    .ingress(Rule::allow()
        .from(IdentitySelector::service("eneros-dashboard"))
        .to_ports(&[Port::tcp(8080)]))
    .egress(Rule::allow()
        .to(IdentitySelector::service("eneros-twin"))
        .to_ports(&[Port::tcp(8081)]))
    .egress(Rule::allow()
        .to(IdentitySelector::service("eneros-timeseries"))
        .to_ports(&[Port::tcp(9090)]))
    .default_deny(Direction::Both);

trust.apply_network_policy(policy).await?;

Network Policy Example Table

Source ServiceTarget ServicePortProtocolAction
eneros-gatewayeneros-scheduler8080TCPAllow
eneros-dashboardeneros-scheduler8080TCPAllow
eneros-schedulereneros-twin8081TCPAllow
eneros-schedulereneros-timeseries9090TCPAllow
*eneros-scheduler**Deny

Policy Audit

use eneros_trust::audit::PolicyAudit;

let audit = PolicyAudit::new(&trust);

// Query denied connections
let denied = audit.query()
    .action(Action::Deny)
    .range(now() - Duration::hours(24), now())
    .execute()?;

for entry in denied {
    println!("{:?} {} -> {} denied: {}",
        entry.timestamp, entry.source, entry.destination, entry.reason);
}

// Generate compliance report
let report = audit.compliance_report(Standard::NercCip)
    .period(Q2_2025)
    .generate()?;

Improvements

  • API Gateway: All REST/GraphQL endpoints enforce mTLS, removing HTTP plaintext support
  • Raft Cluster: Inter-node communication enables mTLS, preventing unauthorized nodes from joining
  • SCADA: IEC 104 and IEC 61850 connections support TLS encapsulation
  • Dashboard: Frontend communicates with backend via mTLS, WebSocket over TLS

Bug Fixes

  • Fixed eneros-trust-mtls occasional handshake failure during certificate rotation (#2008)
  • Fixed eneros-trust-mesh Sidecar injection memory leak under high concurrency (#2014)
  • Fixed eneros-trust-cert OCSP response cache expiration not refreshing (#2020)
  • Fixed eneros-trust network policy brief full-access window during policy updates (#2026)

Breaking Changes

  • All HTTP endpoints enable mTLS by default: Original HTTP endpoints migrated to HTTPS, requiring client certificate configuration
  • RaftNode::new: New tls_config parameter, inter-node communication requires mTLS
  • Gateway::listen: Removed plaintext listening, unified to listen_tls

Performance Improvements

  • mTLS handshake latency stable at 1.7ms (new connection) / 0.3ms (session resumption)
  • Certificate rotation zero-downtime, hot reload in 200ms
  • Service mesh Sidecar additional latency < 0.5ms
  • Network policy evaluation latency < 50μs

Contributors

This version was jointly completed by 27 contributors with 468 commits. Special thanks to:

  • @zero-trust-arch: Zero trust architecture and policy engine
  • @mtls-implementer: mTLS mutual authentication and TLS 1.3
  • @mesh-builder: Service mesh and Sidecar design
  • @cert-automation: Certificate automatic rotation and hot reload

Upgrade Guide

Upgrading from v0.19.0

1. Update Dependencies

# Cargo.toml
[dependencies]
eneros-trust = { version = "0.20" }
eneros-trust-mtls = { version = "0.20" }
eneros-trust-mesh = { version = "0.20" }
eneros-trust-cert = { version = "0.20" }

2. Deploy Internal CA

# Deploy internal certificate authority
eneros-ca init --endpoint https://ca.internal:8200
eneros-ca issue-root --validity 3650
eneros-ca issue-intermediate --validity 365

3. Enable Zero Trust

# eneros.toml
[trust]
enabled = true
mtls_required = true
default_deny = true

[trust.ca]
endpoint = "https://ca.internal:8200"
token = "${CA_TOKEN}"

[trust.cert]
rotation_threshold_days = 7
rotation_check_hours = 1
key_type = "ecdsa-p256"

[trust.mesh]
enabled = true
auto_injection = true

[trust.network_policy]
enabled = true
default_deny = "both"

4. Migrate Clients to mTLS

// v0.19.0 old approach: plaintext HTTP
let client = HttpClient::new("http://eneros-scheduler:8080")?;

// v0.20.0 new approach: mTLS HTTPS
let client = HttpClient::builder("https://eneros-scheduler:8080")
    .mtls(MtlsConfig::load("/etc/eneros/certs")?)
    .build()?;

5. Configure Network Policies

Recommend adopting a “whitelist” approach—first allow necessary communication, then deny all others by default:

// Allow business-critical communication
trust.apply_network_policy(NetworkPolicy::new("business-allow")
    .ingress(Rule::allow().from(IdentitySelector::service("eneros-gateway")))
    .egress(Rule::allow().to(IdentitySelector::service("eneros-twin")))
    .egress(Rule::allow().to(IdentitySelector::service("eneros-timeseries")))
    .default_deny(Direction::Both))?;

// Default deny
trust.apply_network_policy(NetworkPolicy::new("default-deny")
    .default_deny(Direction::Both))?;

6. Compliance Verification

After enabling zero trust, recommend performing compliance verification to ensure NERC CIP and IEC 62443 requirements are met:

eneros-trust verify --standard nerc-cip
eneros-trust verify --standard iec-62443
eneros-trust report --period 2025-Q2 --output compliance.pdf

Zero Trust is the security capstone for EnerOS’s internal iteration phase (v0.1.0 to v0.39.0). With this, EnerOS has completed full-stack capability building from underlying architecture to upper-layer security, laying a solid foundation for the v0.40.0 first public release.