mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-06 08:07:53 +02:00
feat(ios): Mac-side daemon (bun/TS) for Tailscale identity gating + USB proxy
On-demand daemon spawns when /ios-qa needs it (single-instance flock + readiness protocol). Owns tailnet ingress: fail-closed tailscaled LocalAPI probe, dual-track /auth/mint (self-service for allowlisted identities, owner-granted via CLI), capability-tier allowlist (observe/interact/mutate/restore), 1h default session TTL (24h hard cap), audit log of every authenticated mutating tailnet request, hashed-identity attempts log. iOS StateServer never directly binds tailnet — identity validation lives Mac-side because iPhones can't reach tailscaled. 67 unit/integration tests covering session-lock concurrency, capability enforcement, fail-closed probe, identity canonicalization, body limits, and boot-token leak proofs.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
// /auth/mint endpoint handler. Two trust models, kept distinct:
|
||||
//
|
||||
// 1. Self-service mint: caller's tailnet identity (from WhoIs) must already
|
||||
// be in the allowlist. NEVER auto-allowlists.
|
||||
// 2. Owner-granted mint: not on /auth/mint at all — that's the CLI
|
||||
// `gstack-ios-qa-mint --remote <identity>` writing to the allowlist file.
|
||||
|
||||
import { SessionTokenStore } from './session-tokens';
|
||||
import { hasCapability, loadAllowlist } from './allowlist';
|
||||
import { writeAttempt } from './audit';
|
||||
import type { Capability } from './types';
|
||||
import { capabilityCovers } from './types';
|
||||
|
||||
export interface MintRequest {
|
||||
capability?: Capability; // requested tier; default 'interact'
|
||||
device_udid?: string;
|
||||
}
|
||||
|
||||
export interface MintResponse {
|
||||
session_token: string;
|
||||
expires_at: number;
|
||||
capability: Capability;
|
||||
}
|
||||
|
||||
export interface MintError {
|
||||
error: 'identity_not_allowed' | 'capability_insufficient' | 'rate_limited';
|
||||
}
|
||||
|
||||
export async function mintForCaller(opts: {
|
||||
callerIdentity: string;
|
||||
request: MintRequest;
|
||||
tokenStore: SessionTokenStore;
|
||||
allowlistPath?: string;
|
||||
endpoint?: string;
|
||||
}): Promise<MintResponse | MintError> {
|
||||
const allowlist = await loadAllowlist(opts.allowlistPath);
|
||||
const wantedCap: Capability = opts.request.capability ?? 'interact';
|
||||
|
||||
// Must be in the allowlist.
|
||||
if (!hasCapability(allowlist, opts.callerIdentity, 'observe')) {
|
||||
await writeAttempt({
|
||||
rawIdentity: opts.callerIdentity,
|
||||
endpoint: opts.endpoint ?? '/auth/mint',
|
||||
reason: 'identity_not_allowed',
|
||||
});
|
||||
return { error: 'identity_not_allowed' };
|
||||
}
|
||||
|
||||
// Must have at least the requested capability.
|
||||
if (!hasCapability(allowlist, opts.callerIdentity, wantedCap)) {
|
||||
await writeAttempt({
|
||||
rawIdentity: opts.callerIdentity,
|
||||
endpoint: opts.endpoint ?? '/auth/mint',
|
||||
reason: 'capability_insufficient',
|
||||
});
|
||||
return { error: 'capability_insufficient' };
|
||||
}
|
||||
|
||||
// Find the entry to determine the highest tier they can hold.
|
||||
const entry = allowlist.entries.find(e => e.identity === opts.callerIdentity);
|
||||
// Mint at the requested tier, capped at the highest granted tier.
|
||||
const grantedTier = entry?.capabilities.find(c => capabilityCovers(c, wantedCap)) ?? wantedCap;
|
||||
|
||||
const result = opts.tokenStore.mint({
|
||||
identity: opts.callerIdentity,
|
||||
capability: grantedTier,
|
||||
deviceUdid: opts.request.device_udid ?? null,
|
||||
origin: 'self_service',
|
||||
});
|
||||
|
||||
if ('error' in result) {
|
||||
await writeAttempt({
|
||||
rawIdentity: opts.callerIdentity,
|
||||
endpoint: opts.endpoint ?? '/auth/mint',
|
||||
reason: 'rate_limited',
|
||||
});
|
||||
return { error: 'rate_limited' };
|
||||
}
|
||||
|
||||
return {
|
||||
session_token: result.token,
|
||||
expires_at: result.expires_at,
|
||||
capability: result.capability,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user