Skip to main content

RestrictiveHandoff

Struct RestrictiveHandoff 

Source
pub struct RestrictiveHandoff { /* private fields */ }
Expand description

A short-lived file holding a secret, readable only by this account, deleted on every path out.

05-infrastructure.md puts the encoded JIT configuration in a “restrictive temporary file or process-safe handoff” and requires “Delete immediately after runner start or failed start”. Both halves of that sentence are implemented here: RestrictiveHandoff::create makes the file unreadable by other local users at the moment of creation, and Drop deletes it whether the launch succeeded, failed, or panicked.

§Why creation, not creation-then-chmod

On Windows the file is created through CreateFileW with an explicit SECURITY_ATTRIBUTES, and on Unix through open(2) with mode 0600. In both cases the restriction is applied by the call that creates the file. Creating a file and then tightening it leaves a window — however short — in which the JIT configuration exists on disk under whatever the parent directory happened to grant, and a window is all a local attacker needs.

§What is not claimed

The file is deleted, not securely erased. No modern filesystem lets a userspace program guarantee that the bytes are unrecoverable — a journal, a copy-on-write snapshot, or an SSD’s wear levelling can each keep a copy that an overwrite never reaches. Claiming otherwise would be worse than saying so plainly, so the control this design relies on is the file’s short life and its access control, not erasure.

Implementations§

Source§

impl RestrictiveHandoff

Source

pub const NAME_PREFIX: &'static str = "jit-"

What every handoff file’s name begins with.

Published because the name is otherwise a UUID nobody can predict, and c3’s persistent cleanup has to answer “did an encoded configuration survive into this slot?” after the process that owned it is gone (04-security-recovery.md: JIT values are “never retained in slot”). A second "jit-" spelled out over there would be a second source of truth for the one fact that decides whether a secret is still on disk.

Source

pub fn create( directory: &Path, payload: SecretString, ) -> Result<Self, HandoffError>

Writes payload to a new uniquely named file in directory.

The name is a UUID rather than a predictable one, so that another local account cannot pre-create the path and win the race for it; creation is exclusive, so if it did, this fails rather than writing into the squatter’s file.

§Errors

HandoffError::Create and HandoffError::Write.

Source

pub fn path(&self) -> &Path

The path to hand to the child process.

This is the only thing that may reach a command line. The payload is not exposed at all: it is held as a SecretString, which has no Display and a redacting Debug, so it cannot be formatted into an argument by accident.

Source

pub fn permissions(&self) -> Result<PermissionsSummary, HandoffError>

What the file’s permissions actually grant.

§Errors

HandoffError::Inspect.

Source

pub fn delete(self) -> Result<(), HandoffError>

Deletes the file now, reporting failure.

The success path should call this rather than relying on Drop, for one reason: Drop cannot report an error, and a JIT configuration that could not be deleted is something an operator must be told about.

§Errors

HandoffError::Delete.

Trait Implementations§

Source§

impl Debug for RestrictiveHandoff

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for RestrictiveHandoff

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more