spire-api 0.6.0

Rust gRPC client for the SPIRE APIs
Documentation
//! These tests requires a running SPIRE server and agent with workloads registered (see script `scripts/run-spire.sh`).
//! In addition it requires the admin endpoint to be exposed, and the running user to registered
//! as an authorized delegate.

#![expect(
    clippy::tests_outside_test_module,
    reason = "https://github.com/rust-lang/rust-clippy/issues/11024"
)]
#![expect(
    clippy::expect_used,
    clippy::unwrap_used,
    reason = "https://github.com/rust-lang/rust-clippy/issues/11119"
)]
#![expect(unused_crate_dependencies, reason = "used in the library target")]

mod integration_tests_delegate_identity_api_client {
    use futures::StreamExt as _;
    use spiffe::bundle::BundleSource as _;
    use spiffe::{JwtBundleSet, TrustDomain};
    use spire_api::{selectors, DelegateAttestationRequest, DelegatedIdentityClient};
    use std::process::Command;
    use std::sync::LazyLock;

    static TRUST_DOMAIN: LazyLock<TrustDomain> =
        LazyLock::new(|| TrustDomain::new("example.org").unwrap());

    fn get_uid() -> u16 {
        let mut uid = String::from_utf8(
            Command::new("id")
                .arg("-u")
                .output()
                .expect("could not get UID")
                .stdout,
        )
        .expect("could not parse to string");
        uid.truncate(uid.len() - 1);
        uid.parse().expect("could not parse uid to number")
    }

    async fn get_client() -> DelegatedIdentityClient {
        DelegatedIdentityClient::connect_env()
            .await
            .expect("failed to create client")
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn fetch_delegate_jwt_svid() {
        let client = get_client().await;
        let svid = client
            .fetch_jwt_svids(
                &["my_audience"],
                DelegateAttestationRequest::Selectors(vec![selectors::Selector::Unix(
                    selectors::Unix::Uid(get_uid() + 1),
                )]),
            )
            .await
            .expect("Failed to fetch JWT SVID");
        match svid.as_slice() {
            [svid] => {
                assert_eq!(svid.audience(), &["my_audience"]);
            }
            svid => {
                panic!("expected 1 SVID, got {svid:?}");
            }
        }
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn fetch_delegate_x509_svid() {
        let client = get_client().await;
        let response: spiffe::svid::x509::X509Svid = client
            .fetch_x509_svid(DelegateAttestationRequest::Selectors(vec![
                selectors::Selector::Unix(selectors::Unix::Uid(get_uid() + 1)),
            ]))
            .await
            .expect("Failed to fetch delegate SVID");
        // Not checking the chain as the root is generated by spire.
        // In the future we could look in the downloaded spire directory for the keys.
        assert_eq!(response.cert_chain().len(), 1);
        assert_eq!(
            response.spiffe_id().to_string(),
            "spiffe://example.org/different-process"
        );
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn stream_delegate_x509_svid() {
        let test_duration = std::time::Duration::from_secs(60);
        let client = get_client().await;
        let mut stream = client
            .stream_x509_svids(DelegateAttestationRequest::Selectors(vec![
                selectors::Selector::Unix(selectors::Unix::Uid(get_uid() + 1)),
            ]))
            .await
            .expect("Failed to fetch delegate SVID");

        let result = tokio::time::timeout(test_duration, stream.next())
            .await
            .expect("Test did not complete in the expected duration");
        let response = result.expect("empty result").expect("error in stream");
        // Not checking the chain as the root is generated by spire.
        // In the future we could look in the downloaded spire directory for the keys.
        assert_eq!(response.cert_chain().len(), 1);
        assert_eq!(
            response.spiffe_id().to_string(),
            "spiffe://example.org/different-process"
        );
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn fetch_delegated_x509_trust_bundles() {
        let client = get_client().await;
        let response = client
            .fetch_x509_bundles()
            .await
            .expect("Failed to fetch trust bundles");
        let _bundle: std::sync::Arc<_> = response.get(&TRUST_DOMAIN).expect("Failed to get bundle");
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn stream_delegated_x509_trust_bundles() {
        let test_duration = std::time::Duration::from_secs(60);
        let client = get_client().await;
        let mut stream = client
            .stream_x509_bundles()
            .await
            .expect("Failed to fetch trust bundles");

        let result = tokio::time::timeout(test_duration, stream.next())
            .await
            .expect("Test did not complete in the expected duration");
        let response = result.expect("empty result").expect("error in stream");
        let _bundle: std::sync::Arc<_> = response.get(&TRUST_DOMAIN).expect("Failed to get bundle");
    }

    async fn verify_jwt(client: &DelegatedIdentityClient, bundles: JwtBundleSet) {
        let svids = client
            .fetch_jwt_svids(
                &["my_audience"],
                DelegateAttestationRequest::Selectors(vec![selectors::Selector::Unix(
                    selectors::Unix::Uid(get_uid() + 1),
                )]),
            )
            .await
            .expect("Failed to fetch JWT SVID");
        let svid = svids.first().expect("no items in jwt bundle list");
        let key_id = svid.key_id();

        let bundle = bundles.bundle_for_trust_domain(&TRUST_DOMAIN);
        let bundle = bundle
            .expect("Bundle was None")
            .expect("Failed to unwrap bundle");
        assert_eq!(bundle.trust_domain().as_ref(), TRUST_DOMAIN.as_ref());
        assert_eq!(bundle.find_jwt_authority(key_id).unwrap().key_id(), key_id);
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn fetch_delegated_jwt_trust_bundles() {
        let client = get_client().await;
        let response = client
            .fetch_jwt_bundles()
            .await
            .expect("Failed to fetch trust bundles");

        verify_jwt(&client, response).await;
    }

    #[tokio::test]
    #[ignore = "requires running SPIFFE Workload API"]
    async fn stream_delegated_jwt_trust_bundles() {
        let client = get_client().await;
        let test_duration = std::time::Duration::from_secs(60);
        let mut stream = client
            .stream_jwt_bundles()
            .await
            .expect("Failed to fetch trust bundles");

        let result = tokio::time::timeout(test_duration, stream.next())
            .await
            .expect("Test did not complete in the expected duration");

        verify_jwt(
            &client,
            result.expect("empty result").expect("error in stream"),
        )
        .await;
    }
}