1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Auth proxy: the agent pod never holds the Claude credential.
//!
//! With `K8sEphemeralProvider::auth_proxy` (feature `transport-k8s`), the worker asks the
//! `ironflow-auth-proxy` service for an opaque token bound to one run and one
//! step, and the agent pod receives only that token and the proxy URL
//! ([`POD_BASE_URL_ENV`], [`POD_TOKEN_ENV`]). Claude Code sends the opaque
//! token to the proxy, which swaps it for the real credential and relays the
//! request to `api.anthropic.com`. At the end of the step the worker revokes
//! the token; its expiry is the backstop.
//!
//! This module holds what both sides share:
//!
//! * [`AuthProxyRegistry`] - the opaque token registry (proxy side);
//! * [`GrantBackend`] - where the registry keeps its grants, and
//! [`MemoryGrantBackend`], the in-process default;
//! * [`extract_opaque_token`], [`is_allowed_path`], [`upstream_headers`],
//! [`downstream_headers`] - the relay policy (proxy side);
//! * [`resolve_credential`] / [`ProxyCredential`] - which credential the
//! worker hands to the proxy;
//! * [`AuthProxyClient`] - the admin client the worker uses to issue and
//! revoke tokens.
//!
//! # Examples
//!
//! ```
//! use ironflow_core::auth_proxy::{
//! AuthProxyRegistry, CredentialKind, ProxyCredential, TokenRequest,
//! };
//!
//! # async fn example() -> Result<(), ironflow_core::auth_proxy::AuthProxyError> {
//! let registry = AuthProxyRegistry::default();
//! let issued = registry
//! .issue(
//! TokenRequest {
//! run_id: "run-1".to_string(),
//! step: "review".to_string(),
//! expires_at: 1_000 + 600,
//! credential: ProxyCredential::new(CredentialKind::OauthToken, "sk-ant-oat01-x".to_string()),
//! },
//! 1_000,
//! )
//! .await?;
//! assert!(issued.token.starts_with("ifap_"));
//! let step = registry.resolve(&issued.token, 1_001).await.map(|grant| grant.step);
//! assert_eq!(step, Ok("review".to_string()));
//! # Ok(())
//! # }
//! ```
use Duration;
use Error;
pub use ;
pub use AuthProxyClient;
pub use ;
pub use ;
pub use ;
/// The only upstream the proxy binary relays to.
pub const DEFAULT_UPSTREAM: &str = "https://api.anthropic.com";
/// Environment variable holding the admin key shared by the worker and the proxy.
pub const ADMIN_KEY_ENV: &str = "IRONFLOW_AUTH_PROXY_ADMIN_KEY";
/// Prefix of every opaque token. It never looks like an Anthropic credential.
pub const TOKEN_PREFIX: &str = "ifap_";
/// Longest lifetime a token may be issued for.
pub const MAX_TOKEN_LIFETIME: Duration = from_secs;
/// `anthropic-beta` flag required by the API when authenticating with an OAuth token.
pub const OAUTH_BETA: &str = "oauth-2025-04-20";
/// Environment variable carrying the opaque token in the agent pod.
pub const POD_TOKEN_ENV: &str = "ANTHROPIC_AUTH_TOKEN";
/// Environment variable carrying the proxy URL in the agent pod.
pub const POD_BASE_URL_ENV: &str = "ANTHROPIC_BASE_URL";
/// Errors of the auth proxy. No variant ever carries a credential or token value.
///
/// # Examples
///
/// ```
/// use ironflow_core::auth_proxy::AuthProxyError;
///
/// let err = AuthProxyError::Admin { status: 401, message: "denied".to_string() };
/// assert!(err.to_string().contains("401"));
/// ```