Skip to main content

22_identity_providers_federation/
22_identity_providers_federation.rs

1//! Example 22: Identity Providers for Federation
2//!
3//! Demonstrates setting up SAML and OIDC identity providers for federated authentication.
4//!
5//! This example shows:
6//! - Creating SAML providers for enterprise IdPs (Okta, Azure AD)
7//! - Creating OIDC providers for modern IdPs (Google, Auth0)
8//! - Updating provider configurations (thumbprints, client IDs)
9//! - Listing and managing providers
10//! - Tagging providers for organization
11//! - Tracking provider usage
12//!
13//! Run with: cargo run --example 22_identity_providers_federation
14
15use std::sync::Arc;
16use tokio::sync::RwLock;
17use wami::arn::{TenantPath, WamiArn};
18use wami::context::WamiContext;
19use wami::service::IdentityProviderService;
20use wami::store::memory::InMemoryWamiStore;
21use wami::types::Tag;
22use wami::wami::identity::identity_provider::{
23    AddClientIDToOpenIDConnectProviderRequest, CreateOpenIDConnectProviderRequest,
24    CreateSAMLProviderRequest, ListOpenIDConnectProvidersRequest, ListSAMLProvidersRequest,
25    RemoveClientIDFromOpenIDConnectProviderRequest, UpdateOpenIDConnectProviderThumbprintRequest,
26    UpdateSAMLProviderRequest,
27};
28
29#[tokio::main]
30async fn main() -> Result<(), Box<dyn std::error::Error>> {
31    env_logger::init();
32
33    println!("=== WAMI Example 22: Identity Providers for Federation ===\n");
34
35    // Create in-memory store
36    let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
37
38    // Create context
39    let context = WamiContext::builder()
40        .instance_id("123456789012")
41        .tenant_path(TenantPath::single(0))
42        .caller_arn(
43            WamiArn::builder()
44                .service(wami::arn::Service::Iam)
45                .tenant_path(TenantPath::single(0))
46                .wami_instance("123456789012")
47                .resource("user", "admin")
48                .build()?,
49        )
50        .is_root(false)
51        .build()?;
52
53    // Create identity provider service
54    let service = IdentityProviderService::new(store.clone());
55
56    println!("๐Ÿ“‹ Setting up federated authentication with SAML and OIDC providers\n");
57
58    // ===========================
59    // Part 1: SAML Providers
60    // ===========================
61
62    println!("๐Ÿ” Part 1: SAML Providers (Enterprise Federation)\n");
63
64    // Create Okta SAML provider
65    println!("Creating Okta SAML provider...");
66    let okta_metadata = r#"<?xml version="1.0" encoding="UTF-8"?>
67<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
68                  entityID="http://www.okta.com/exampleid"
69                  validUntil="2025-12-31T23:59:59Z">
70    <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
71        <KeyDescriptor use="signing">
72            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
73                <ds:X509Data>
74                    <ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAWk...</ds:X509Certificate>
75                </ds:X509Data>
76            </ds:KeyInfo>
77        </KeyDescriptor>
78        <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
79                             Location="https://example.okta.com/app/exampleid/sso/saml"/>
80    </IDPSSODescriptor>
81</EntityDescriptor>"#;
82
83    let okta_request = CreateSAMLProviderRequest {
84        name: "OktaEnterpriseProvider".to_string(),
85        saml_metadata_document: okta_metadata.to_string(),
86        tags: Some(vec![
87            Tag {
88                key: "Environment".to_string(),
89                value: "Production".to_string(),
90            },
91            Tag {
92                key: "IdP".to_string(),
93                value: "Okta".to_string(),
94            },
95        ]),
96    };
97
98    let okta_provider = service.create_saml_provider(&context, okta_request).await?;
99    println!("โœ… Created Okta SAML provider: {}", okta_provider.arn);
100    println!("   Name: {}", okta_provider.saml_provider_name);
101    if let Some(valid_until) = okta_provider.valid_until {
102        println!("   Valid until: {}", valid_until);
103    }
104    println!("   Tags: {:?}\n", okta_provider.tags);
105
106    // Create Azure AD SAML provider
107    println!("Creating Azure AD SAML provider...");
108    let azure_metadata = r#"<?xml version="1.0" encoding="UTF-8"?>
109<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
110                  entityID="https://sts.windows.net/tenant-id/">
111    <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
112        <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
113                             Location="https://login.microsoftonline.com/tenant-id/saml2"/>
114    </IDPSSODescriptor>
115</EntityDescriptor>"#;
116
117    let azure_request = CreateSAMLProviderRequest {
118        name: "AzureAD-Enterprise".to_string(),
119        saml_metadata_document: azure_metadata.to_string(),
120        tags: Some(vec![Tag {
121            key: "IdP".to_string(),
122            value: "AzureAD".to_string(),
123        }]),
124    };
125
126    let azure_provider = service
127        .create_saml_provider(&context, azure_request)
128        .await?;
129    println!("โœ… Created Azure AD SAML provider: {}", azure_provider.arn);
130    println!("   Name: {}\n", azure_provider.saml_provider_name);
131
132    // Update SAML provider metadata (certificate rotation scenario)
133    println!("Updating Okta SAML provider metadata (certificate rotation)...");
134    let updated_metadata =
135        okta_metadata.replace("MIIDpDCCAoygAwIBAgIGAWk...", "MIIDnEWCERTIFICATE...");
136    let update_request = UpdateSAMLProviderRequest {
137        arn: okta_provider.arn.clone(),
138        saml_metadata_document: updated_metadata,
139    };
140    let updated_okta = service.update_saml_provider(update_request).await?;
141    println!("โœ… Updated Okta SAML provider metadata\n");
142
143    // List SAML providers
144    println!("Listing all SAML providers:");
145    let (saml_providers, is_truncated, _) = service
146        .list_saml_providers(ListSAMLProvidersRequest::default())
147        .await?;
148    for (i, provider) in saml_providers.iter().enumerate() {
149        println!(
150            "  {}. {} ({})",
151            i + 1,
152            provider.saml_provider_name,
153            provider.arn
154        );
155    }
156    println!("  Total: {} provider(s)", saml_providers.len());
157    if is_truncated {
158        println!("  (more available)");
159    }
160    println!();
161
162    // ===========================
163    // Part 2: OIDC Providers
164    // ===========================
165
166    println!("๐ŸŒ Part 2: OIDC Providers (Modern Federation)\n");
167
168    // Create Google OIDC provider
169    println!("Creating Google OIDC provider...");
170    let google_request = CreateOpenIDConnectProviderRequest {
171        url: "https://accounts.google.com".to_string(),
172        client_id_list: vec!["1234567890-abcdefghijklmnop.apps.googleusercontent.com".to_string()],
173        thumbprint_list: vec![
174            // Google's certificate thumbprint (example)
175            "c3846bf24b9e93ca64274c0ec67c1ecc5e024ffb".to_string(),
176        ],
177        tags: Some(vec![
178            Tag {
179                key: "IdP".to_string(),
180                value: "Google".to_string(),
181            },
182            Tag {
183                key: "Usage".to_string(),
184                value: "WebApp".to_string(),
185            },
186        ]),
187    };
188
189    let google_provider = service
190        .create_oidc_provider(&context, google_request)
191        .await?;
192    println!("โœ… Created Google OIDC provider: {}", google_provider.arn);
193    println!("   URL: {}", google_provider.url);
194    println!("   Client IDs: {:?}", google_provider.client_id_list);
195    println!("   Thumbprints: {:?}\n", google_provider.thumbprint_list);
196
197    // Create Auth0 OIDC provider
198    println!("Creating Auth0 OIDC provider...");
199    let auth0_request = CreateOpenIDConnectProviderRequest {
200        url: "https://myapp.us.auth0.com".to_string(),
201        client_id_list: vec!["Auth0ClientIdExample123456".to_string()],
202        thumbprint_list: vec!["9e99a48a9960b14926bb7f3b02e22da2b0ab7280".to_string()],
203        tags: Some(vec![Tag {
204            key: "IdP".to_string(),
205            value: "Auth0".to_string(),
206        }]),
207    };
208
209    let auth0_provider = service
210        .create_oidc_provider(&context, auth0_request)
211        .await?;
212    println!("โœ… Created Auth0 OIDC provider: {}", auth0_provider.arn);
213    println!("   URL: {}\n", auth0_provider.url);
214
215    // ===========================
216    // Part 3: Managing OIDC Providers
217    // ===========================
218
219    println!("๐Ÿ”ง Part 3: Managing OIDC Provider Configuration\n");
220
221    // Add additional client ID to Google provider
222    println!("Adding additional client ID to Google provider...");
223    let add_client_request = AddClientIDToOpenIDConnectProviderRequest {
224        arn: google_provider.arn.clone(),
225        client_id: "0987654321-zyxwvutsrqpon.apps.googleusercontent.com".to_string(),
226    };
227    let updated_google = service.add_client_id(add_client_request).await?;
228    println!(
229        "โœ… Added client ID. Total client IDs: {}",
230        updated_google.client_id_list.len()
231    );
232    println!("   Client IDs: {:?}\n", updated_google.client_id_list);
233
234    // Update thumbprints (certificate rotation)
235    println!("Updating Auth0 thumbprints (certificate rotation)...");
236    let update_thumbprint_request = UpdateOpenIDConnectProviderThumbprintRequest {
237        arn: auth0_provider.arn.clone(),
238        thumbprint_list: vec![
239            "9e99a48a9960b14926bb7f3b02e22da2b0ab7280".to_string(),
240            "a053375bfe84e8b748782c7cee15827a6af5a405".to_string(), // New certificate
241        ],
242    };
243    let updated_auth0 = service
244        .update_oidc_thumbprints(update_thumbprint_request)
245        .await?;
246    println!(
247        "โœ… Updated thumbprints. Total: {}",
248        updated_auth0.thumbprint_list.len()
249    );
250    println!("   Thumbprints: {:?}\n", updated_auth0.thumbprint_list);
251
252    // Remove a client ID
253    println!("Removing original Google client ID...");
254    let remove_client_request = RemoveClientIDFromOpenIDConnectProviderRequest {
255        arn: google_provider.arn.clone(),
256        client_id: "1234567890-abcdefghijklmnop.apps.googleusercontent.com".to_string(),
257    };
258    let final_google = service.remove_client_id(remove_client_request).await?;
259    println!(
260        "โœ… Removed client ID. Remaining client IDs: {}",
261        final_google.client_id_list.len()
262    );
263    println!("   Client IDs: {:?}\n", final_google.client_id_list);
264
265    // List OIDC providers
266    println!("Listing all OIDC providers:");
267    let (oidc_providers, is_truncated, _) = service
268        .list_oidc_providers(ListOpenIDConnectProvidersRequest::default())
269        .await?;
270    for (i, provider) in oidc_providers.iter().enumerate() {
271        println!("  {}. {} ({})", i + 1, provider.url, provider.arn);
272    }
273    println!("  Total: {} provider(s)", oidc_providers.len());
274    if is_truncated {
275        println!("  (more available)");
276    }
277    println!();
278
279    // ===========================
280    // Part 4: Tagging Operations
281    // ===========================
282
283    println!("๐Ÿท๏ธ  Part 4: Tagging Identity Providers\n");
284
285    // Add tags to a provider
286    println!("Adding tags to Google OIDC provider...");
287    service
288        .tag_identity_provider(
289            &google_provider.arn,
290            vec![
291                Tag {
292                    key: "CostCenter".to_string(),
293                    value: "Engineering".to_string(),
294                },
295                Tag {
296                    key: "Compliance".to_string(),
297                    value: "SOC2".to_string(),
298                },
299            ],
300        )
301        .await?;
302    println!("โœ… Added tags\n");
303
304    // List tags
305    println!("Listing tags for Google provider:");
306    let tags = service
307        .list_identity_provider_tags(&google_provider.arn)
308        .await?;
309    for tag in &tags {
310        println!("  - {}: {}", tag.key, tag.value);
311    }
312    println!();
313
314    // Remove a tag
315    println!("Removing 'Usage' tag...");
316    service
317        .untag_identity_provider(&google_provider.arn, vec!["Usage".to_string()])
318        .await?;
319    println!("โœ… Removed tag\n");
320
321    // ===========================
322    // Part 5: Usage Tracking
323    // ===========================
324
325    println!("๐Ÿ“Š Part 5: Provider Usage Tracking\n");
326
327    // Retrieve provider and check usage
328    let google_final = service.get_oidc_provider(&google_provider.arn).await?;
329    if let Some(provider) = google_final {
330        println!("Google OIDC Provider:");
331        println!("  URL: {}", provider.url);
332        println!("  Usage count: {} principals", provider.usage_count);
333        println!("  Created: {}", provider.create_date);
334        println!("  Tags: {} tag(s)", provider.tags.len());
335    }
336    println!();
337
338    let okta_final = service.get_saml_provider(&updated_okta.arn).await?;
339    if let Some(provider) = okta_final {
340        println!("Okta SAML Provider:");
341        println!("  Name: {}", provider.saml_provider_name);
342        println!("  Usage count: {} principals", provider.usage_count);
343        println!("  Created: {}", provider.create_date);
344        if let Some(valid_until) = provider.valid_until {
345            println!("  Valid until: {}", valid_until);
346        }
347    }
348    println!();
349
350    // ===========================
351    // Part 6: Cleanup
352    // ===========================
353
354    println!("๐Ÿงน Part 6: Cleanup (Optional)\n");
355
356    println!("Note: In production, you'd typically keep identity providers configured.");
357    println!("For this demo, we'll list what we've created:\n");
358
359    let (all_saml, _, _) = service
360        .list_saml_providers(ListSAMLProvidersRequest::default())
361        .await?;
362    let (all_oidc, _, _) = service
363        .list_oidc_providers(ListOpenIDConnectProvidersRequest::default())
364        .await?;
365
366    println!("Summary:");
367    println!("  - {} SAML providers configured", all_saml.len());
368    println!("  - {} OIDC providers configured", all_oidc.len());
369    println!(
370        "  - Total: {} identity providers",
371        all_saml.len() + all_oidc.len()
372    );
373    println!();
374
375    // Uncomment to actually delete providers:
376    // println!("Cleaning up providers...");
377    // service.delete_saml_provider(&okta_provider.arn).await?;
378    // service.delete_saml_provider(&azure_provider.arn).await?;
379    // service.delete_oidc_provider(&google_provider.arn).await?;
380    // service.delete_oidc_provider(&auth0_provider.arn).await?;
381    // println!("โœ… All providers deleted\n");
382
383    println!("=== Example 22 Complete ===");
384    println!();
385    println!("Key Takeaways:");
386    println!("  1. SAML providers are ideal for enterprise SSO (Okta, Azure AD)");
387    println!("  2. OIDC providers are perfect for modern web apps (Google, Auth0)");
388    println!("  3. Both support certificate rotation via update operations");
389    println!("  4. Tagging helps organize providers by environment, compliance, etc.");
390    println!("  5. Usage tracking shows which providers are actively used");
391    println!();
392    println!("Next Steps:");
393    println!("  - Configure trust relationships in your roles to use these providers");
394    println!("  - Set up AssumeRoleWithSAML or AssumeRoleWithWebIdentity for authentication");
395    println!("  - Monitor provider usage and update certificates before expiration");
396
397    Ok(())
398}