Skip to main content

alien_core/
client_config.rs

1//! Client configuration structures for different cloud platforms
2//!
3//! This module contains the configuration structs for all supported cloud platforms.
4//! These structs define the authentication and platform-specific settings needed
5//! to connect to cloud services, but do not contain implementation logic (which
6//! remains in the respective client crates).
7
8use crate::Platform;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Service endpoint overrides for testing AWS services
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
15#[serde(rename_all = "camelCase", deny_unknown_fields)]
16pub struct AwsServiceOverrides {
17    /// Override endpoints for specific AWS services
18    /// Key is the service name (e.g., "lambda", "s3"), value is the base URL
19    pub endpoints: HashMap<String, String>,
20}
21
22/// Configuration for AWS role impersonation
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
25#[serde(rename_all = "camelCase", deny_unknown_fields)]
26pub struct AwsImpersonationConfig {
27    /// The ARN of the role to assume
28    pub role_arn: String,
29    /// Optional session name for the assumed role session
30    pub session_name: Option<String>,
31    /// Optional duration for the assumed role credentials (in seconds)
32    pub duration_seconds: Option<i32>,
33    /// Optional external ID for the assume role operation
34    pub external_id: Option<String>,
35}
36
37/// Configuration for AWS Web Identity Token authentication
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
40#[serde(rename_all = "camelCase", deny_unknown_fields)]
41pub struct AwsWebIdentityConfig {
42    /// The ARN of the role to assume
43    pub role_arn: String,
44    /// Optional session name for the assumed role session
45    pub session_name: Option<String>,
46    /// The path to the web identity token file
47    pub web_identity_token_file: String,
48    /// Optional duration for the assumed role credentials (in seconds)
49    pub duration_seconds: Option<i32>,
50}
51
52/// Supported AWS authentication methods
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
55#[serde(rename_all = "camelCase", tag = "type")]
56pub enum AwsCredentials {
57    /// Direct access keys
58    AccessKeys {
59        /// AWS Access Key ID
60        access_key_id: String,
61        /// AWS Secret Access Key
62        secret_access_key: String,
63        /// Optional AWS Session Token
64        session_token: Option<String>,
65    },
66    /// Web Identity Token for OIDC authentication
67    WebIdentity {
68        /// Web identity configuration
69        config: AwsWebIdentityConfig,
70    },
71}
72
73/// AWS client configuration
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
76#[serde(rename_all = "camelCase", deny_unknown_fields)]
77pub struct AwsClientConfig {
78    /// The AWS Account ID.
79    pub account_id: String,
80    /// The AWS region.
81    pub region: String,
82    /// AWS authentication credentials.
83    pub credentials: AwsCredentials,
84    /// Service endpoint overrides for testing
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub service_overrides: Option<AwsServiceOverrides>,
87}
88
89/// Service endpoint overrides for testing GCP services
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
92#[serde(rename_all = "camelCase", deny_unknown_fields)]
93pub struct GcpServiceOverrides {
94    /// Override endpoints for specific GCP services
95    /// Key is the service name (e.g., "cloudrun", "storage"), value is the base URL
96    pub endpoints: HashMap<String, String>,
97}
98
99/// Authentication options for talking to GCP APIs.
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
102#[serde(rename_all = "camelCase", tag = "type")]
103pub enum GcpCredentials {
104    /// Use an already-minted OAuth2 access token.
105    AccessToken { token: String },
106
107    /// Use a full Service Account JSON key (as string). A short-lived JWT will
108    /// be created and exchanged for a bearer token automatically.
109    ServiceAccountKey { json: String },
110
111    /// Use GCP metadata server for authentication (for instances running on GCP)
112    ServiceMetadata,
113
114    /// Use projected service account token (for Kubernetes workload identity)
115    ProjectedServiceAccount {
116        /// Path to the projected service account token
117        token_file: String,
118        /// Service account email
119        service_account_email: String,
120    },
121
122    /// Use gcloud Application Default Credentials (authorized_user).
123    /// Exchanges refresh_token for an access_token via Google's OAuth2 endpoint.
124    AuthorizedUser {
125        /// OAuth2 client ID
126        client_id: String,
127        /// OAuth2 client secret
128        client_secret: String,
129        /// OAuth2 refresh token
130        refresh_token: String,
131    },
132}
133
134/// Configuration for GCP service account impersonation
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
137#[serde(rename_all = "camelCase", deny_unknown_fields)]
138pub struct GcpImpersonationConfig {
139    /// The email of the service account to impersonate
140    pub service_account_email: String,
141    /// The OAuth 2.0 scopes that define the access token's permissions
142    pub scopes: Vec<String>,
143    /// Optional sequence of service accounts in a delegation chain
144    pub delegates: Option<Vec<String>>,
145    /// Optional desired lifetime duration of the access token (max 3600s)
146    pub lifetime: Option<String>,
147}
148
149impl Default for GcpImpersonationConfig {
150    fn default() -> Self {
151        Self {
152            service_account_email: String::new(),
153            scopes: vec!["https://www.googleapis.com/auth/cloud-platform".to_string()],
154            delegates: None,
155            lifetime: Some("3600s".to_string()),
156        }
157    }
158}
159
160/// GCP client configuration
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
163#[serde(rename_all = "camelCase", deny_unknown_fields)]
164pub struct GcpClientConfig {
165    /// The GCP Project ID.
166    pub project_id: String,
167    /// The GCP region for resources.
168    pub region: String,
169    /// GCP authentication credentials.
170    pub credentials: GcpCredentials,
171    /// Service endpoint overrides for testing
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub service_overrides: Option<GcpServiceOverrides>,
174}
175
176/// Service endpoint overrides for testing Azure services
177#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
179#[serde(rename_all = "camelCase", deny_unknown_fields)]
180pub struct AzureServiceOverrides {
181    /// Override endpoints for specific Azure services
182    /// Key is the service name (e.g., "management", "storage", "containerApps"), value is the base URL
183    pub endpoints: HashMap<String, String>,
184}
185
186/// Represents Azure authentication credentials
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
189#[serde(rename_all = "camelCase", tag = "type")]
190pub enum AzureCredentials {
191    /// Service principal with client secret
192    ServicePrincipal {
193        /// The client ID (application ID)
194        client_id: String,
195        /// The client secret
196        client_secret: String,
197    },
198    /// Direct access token
199    AccessToken {
200        /// The bearer token to use for authentication
201        token: String,
202    },
203    /// Azure AD Workload Identity (federated identity)
204    WorkloadIdentity {
205        /// The client ID of the managed identity or application
206        client_id: String,
207        /// The tenant ID for authentication
208        tenant_id: String,
209        /// Path to the federated token file
210        federated_token_file: String,
211        /// The authority host URL
212        authority_host: String,
213    },
214}
215
216/// Configuration for Azure managed identity impersonation
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
219#[serde(rename_all = "camelCase", deny_unknown_fields)]
220pub struct AzureImpersonationConfig {
221    /// The client ID of the managed identity or service principal to impersonate
222    pub client_id: String,
223    /// The scope for the access token (e.g., "https://management.azure.com/.default")
224    pub scope: String,
225    /// Optional tenant ID for cross-tenant impersonation
226    pub tenant_id: Option<String>,
227}
228
229impl Default for AzureImpersonationConfig {
230    fn default() -> Self {
231        Self {
232            client_id: String::new(),
233            scope: "https://management.azure.com/.default".to_string(),
234            tenant_id: None,
235        }
236    }
237}
238
239/// Azure client configuration
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
241#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
242#[serde(rename_all = "camelCase", deny_unknown_fields)]
243pub struct AzureClientConfig {
244    /// The Azure Subscription ID where resources will be deployed.
245    pub subscription_id: String,
246    /// The customer's Azure Tenant ID.
247    pub tenant_id: String,
248    /// Azure region for resources.
249    pub region: Option<String>,
250    /// Azure authentication credentials.
251    pub credentials: AzureCredentials,
252    /// Service endpoint overrides for testing
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub service_overrides: Option<AzureServiceOverrides>,
255}
256
257/// Configuration mode for Kubernetes access
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
260#[serde(rename_all = "camelCase", tag = "mode")]
261pub enum KubernetesClientConfig {
262    /// Use in-cluster configuration (service account tokens, etc.)
263    InCluster {
264        /// The namespace to operate in
265        #[serde(skip_serializing_if = "Option::is_none")]
266        namespace: Option<String>,
267        /// Additional headers to include in requests
268        #[serde(skip_serializing_if = "Option::is_none")]
269        additional_headers: Option<HashMap<String, String>>,
270    },
271    /// Use kubeconfig file for configuration
272    Kubeconfig {
273        /// Path to kubeconfig file (optional, defaults to standard locations)
274        #[serde(skip_serializing_if = "Option::is_none")]
275        kubeconfig_path: Option<String>,
276        /// Context name to use (optional, defaults to current-context)
277        #[serde(skip_serializing_if = "Option::is_none")]
278        context: Option<String>,
279        /// Cluster name to use (optional, defaults to context's cluster)
280        #[serde(skip_serializing_if = "Option::is_none")]
281        cluster: Option<String>,
282        /// User name to use (optional, defaults to context's user)
283        #[serde(skip_serializing_if = "Option::is_none")]
284        user: Option<String>,
285        /// The namespace to operate in
286        #[serde(skip_serializing_if = "Option::is_none")]
287        namespace: Option<String>,
288        /// Additional headers to include in requests
289        #[serde(skip_serializing_if = "Option::is_none")]
290        additional_headers: Option<HashMap<String, String>>,
291    },
292    /// Manual configuration with explicit values
293    Manual {
294        /// The Kubernetes cluster server URL
295        server_url: String,
296        /// The cluster certificate authority data (base64 encoded)
297        certificate_authority_data: Option<String>,
298        /// Skip TLS verification (insecure)
299        insecure_skip_tls_verify: Option<bool>,
300        /// Client certificate data (base64 encoded) for mutual TLS
301        client_certificate_data: Option<String>,
302        /// Client key data (base64 encoded) for mutual TLS
303        client_key_data: Option<String>,
304        /// Bearer token for authentication
305        token: Option<String>,
306        /// Username for basic authentication
307        username: Option<String>,
308        /// Password for basic authentication
309        password: Option<String>,
310        /// The namespace to operate in
311        namespace: Option<String>,
312        /// Additional headers to include in requests
313        additional_headers: HashMap<String, String>,
314    },
315}
316
317/// Cloud-agnostic impersonation configuration
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
319#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
320#[serde(rename_all = "camelCase", tag = "platform")]
321pub enum ImpersonationConfig {
322    Aws(AwsImpersonationConfig),
323    Gcp(GcpImpersonationConfig),
324    Azure(AzureImpersonationConfig),
325    // Kubernetes doesn't support impersonation, so we don't include it here
326}
327
328/// Configuration for different cloud platform clients
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
331#[serde(rename_all = "camelCase", tag = "platform")]
332pub enum ClientConfig {
333    Aws(Box<AwsClientConfig>),
334    Gcp(Box<GcpClientConfig>),
335    Azure(Box<AzureClientConfig>),
336    Kubernetes(Box<KubernetesClientConfig>),
337    Local {
338        /// State directory for local resources and deployment state
339        state_directory: String,
340        /// Optional artifact registry configuration for pulling container images.
341        /// When present, the local platform will fetch credentials from the agent manager
342        /// before pulling images, enabling centralized registry access control.
343        #[serde(skip_serializing_if = "Option::is_none")]
344        artifact_registry_config: Option<crate::ArtifactRegistryConfig>,
345    },
346    /// Test platform - uses mock controllers without real cloud APIs
347    #[serde(skip)]
348    Test,
349}
350
351impl ClientConfig {
352    /// Returns the platform enum for this configuration.
353    pub fn platform(&self) -> Platform {
354        match self {
355            ClientConfig::Aws(_) => Platform::Aws,
356            ClientConfig::Gcp(_) => Platform::Gcp,
357            ClientConfig::Azure(_) => Platform::Azure,
358            ClientConfig::Kubernetes(_) => Platform::Kubernetes,
359            ClientConfig::Local { .. } => Platform::Local,
360            ClientConfig::Test => Platform::Test,
361        }
362    }
363
364    /// Returns the AWS configuration if this is an AWS client config.
365    pub fn aws_config(&self) -> Option<&AwsClientConfig> {
366        match self {
367            ClientConfig::Aws(config) => Some(config),
368            _ => None,
369        }
370    }
371
372    /// Returns the GCP configuration if this is a GCP client config.
373    pub fn gcp_config(&self) -> Option<&GcpClientConfig> {
374        match self {
375            ClientConfig::Gcp(config) => Some(config),
376            _ => None,
377        }
378    }
379
380    /// Returns the Azure configuration if this is an Azure client config.
381    pub fn azure_config(&self) -> Option<&AzureClientConfig> {
382        match self {
383            ClientConfig::Azure(config) => Some(config),
384            _ => None,
385        }
386    }
387
388    /// Returns the Kubernetes configuration if this is a Kubernetes client config.
389    pub fn kubernetes_config(&self) -> Option<&KubernetesClientConfig> {
390        match self {
391            ClientConfig::Kubernetes(config) => Some(config),
392            _ => None,
393        }
394    }
395}