mirror of
https://github.com/hanzoai/egress.git
synced 2026-10-30 15:08:38 +00:00
Egress writes no file, refuses core dumps, mounts no writable path and hides /proc — and none of that governs the kernel, which pages anonymous memory to swap whenever it likes. A key held only in RAM is still a key on a disk once it has been swapped, and it stays there after the process is gone. That was the last way a credential reached one. It now calls mlockall(MCL_CURRENT|MCL_FUTURE) BEFORE opening the store, because the credential does not exist yet at that point and locking after a secret is in memory locks it too late. MCL_FUTURE is the half that matters for the same reason. The unit grants LimitMEMLOCK=infinity; without it the process refuses to start rather than serving without the guarantee, since "best effort" means it holds on some hosts and not others with nothing to say which. Memory encryption is reported rather than assumed: pinning keeps a key off the disk and does nothing about the DRAM, so the boot line names sev-snp, tdx or none and a deployment that needs one can be held to it. Off Linux these pages cannot be pinned, so egress refuses there rather than serving quietly without the property it is built on. The deploy doc states the other two guarantees that were true and unwritten: nothing is kept, so replicas are interchangeable and a suspect one is deleted rather than investigated; and --with-key is an operator's choice between host and tpm2 that egress never learns, with a cloud KMS or PKCS#11 HSM named as the integration it would take rather than a flag that exists.
65 lines
2.3 KiB
Go
65 lines
2.3 KiB
Go
// Copyright 2026 Hanzo AI, Inc.
|
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
//go:build linux
|
|
|
|
package egress
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// LockMemory pins every page this process has and every page it will get, so no
|
|
// provider key is ever written to swap.
|
|
//
|
|
// THIS IS THE LAST WAY A CREDENTIAL REACHED DISK. The unit already refuses core
|
|
// dumps, mounts no writable path and hides /proc, and this service writes no
|
|
// file — but none of that governs the kernel, which may page anonymous memory
|
|
// out whenever it likes. A key held only in RAM is still a key on a disk once it
|
|
// has been swapped, and it stays there after the process is gone.
|
|
//
|
|
// MCL_FUTURE matters as much as MCL_CURRENT: the credential does not exist yet
|
|
// when this runs. It is called before the store is opened for exactly that
|
|
// reason — locking after a secret is in memory locks it too late.
|
|
//
|
|
// Failing here is fatal by design. "Best effort" would mean the guarantee this
|
|
// service is built on holds on some hosts and not others, with nothing to say
|
|
// which, and the operator finding out from a disk forensics report.
|
|
func LockMemory() error {
|
|
if err := unix.Mlockall(unix.MCL_CURRENT | unix.MCL_FUTURE); err != nil {
|
|
return fmt.Errorf("lock memory: %w — a provider key would be swappable. "+
|
|
"Grant the lock (LimitMEMLOCK=infinity in the unit, or CAP_IPC_LOCK) rather than "+
|
|
"running without it", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MemoryEncryption names the CPU's memory encryption, or "none".
|
|
//
|
|
// Locking keeps a key off the disk; it does nothing about the DRAM itself, which
|
|
// root on a live box, a DMA-capable port or a cold-boot attack still reaches.
|
|
// SEV-SNP and TDX close that, and this reports which is in force so a deployment
|
|
// can be held to it rather than assumed.
|
|
func MemoryEncryption() string {
|
|
// The guest view: the kernel publishes what confidential computing is active.
|
|
if b, err := os.ReadFile("/sys/kernel/coco/status"); err == nil {
|
|
if s := strings.TrimSpace(string(b)); s != "" {
|
|
return s
|
|
}
|
|
}
|
|
if b, err := os.ReadFile("/proc/cpuinfo"); err == nil {
|
|
flags := string(b)
|
|
switch {
|
|
case strings.Contains(flags, "sev_snp"):
|
|
return "sev-snp"
|
|
case strings.Contains(flags, "tdx_guest"):
|
|
return "tdx"
|
|
}
|
|
}
|
|
return "none"
|
|
}
|