Skip to main content

fluidattacks_core_git/clone/
codecommit.rs

1use std::path::Path;
2use tokio::process::Command;
3
4use super::{build_clone_args, reason, redirect_config, CloneOpts};
5use crate::aws::{self, AwsTempCreds};
6use crate::errors::{CloneError, GitCallError};
7use crate::types::CredentialKind;
8
9pub async fn clone_codecommit(opts: &CloneOpts, dest: &Path) -> anyhow::Result<()> {
10    let Some(CredentialKind::AwsRole {
11        arn,
12        external_id,
13        via,
14    }) = opts.credentials.as_ref().map(|c| &c.kind)
15    else {
16        anyhow::bail!("expected AwsRole credentials for CodeCommit clone");
17    };
18    Ok(run_git_clone(opts, dest, arn, external_id, via.as_deref()).await?)
19}
20
21pub(super) async fn run_git_clone(
22    opts: &CloneOpts,
23    dest: &Path,
24    arn: &str,
25    external_id: &str,
26    via: Option<&str>,
27) -> Result<(), CloneError> {
28    let creds = aws::assume_role(arn, external_id, &opts.repo_url, via).await?;
29    Ok(clone_with(opts, dest, &creds).await?)
30}
31
32async fn clone_with(
33    opts: &CloneOpts,
34    dest: &Path,
35    creds: &AwsTempCreds,
36) -> Result<(), GitCallError> {
37    let mut command = Command::new("git");
38    command
39        .args(clone_args(opts, dest))
40        .env("AWS_ACCESS_KEY_ID", &creds.access_key)
41        .env("AWS_SECRET_ACCESS_KEY", &creds.secret_key)
42        .env("AWS_SESSION_TOKEN", &creds.session_token)
43        .env("AWS_DEFAULT_REGION", &creds.region);
44    reason::run(&mut command, &[&creds.secret_key, &creds.session_token]).await
45}
46
47fn clone_args(opts: &CloneOpts, dest: &Path) -> Vec<std::ffi::OsString> {
48    build_clone_args(
49        opts,
50        &opts.repo_url,
51        dest,
52        &redirect_config(opts.follow_redirects),
53    )
54}
55
56#[cfg(test)]
57mod tests {
58    use std::path::Path;
59
60    use super::{clone_args, clone_with};
61    use crate::aws::AwsTempCreds;
62    use crate::clone::tests::opts;
63    use crate::clone::{CloneOpts, CloneShape};
64    use crate::errors::GitCallError;
65
66    #[test]
67    fn a_redirect_is_followed_only_when_the_root_asks_for_one() {
68        for follow_redirects in [false, true] {
69            let opts = CloneOpts {
70                follow_redirects,
71                ..opts(CloneShape::SingleBranch)
72            };
73
74            let args = clone_args(&opts, Path::new("/tmp/a_root"));
75
76            let config = format!("http.followRedirects={follow_redirects}");
77            assert!(args.windows(2).any(|pair| pair == ["-c", config.as_str()]));
78        }
79    }
80
81    #[test]
82    fn git_checks_the_certificate() {
83        let args = clone_args(&opts(CloneShape::SingleBranch), Path::new("/tmp/a_root"));
84
85        assert!(!args.iter().any(|arg| arg == "http.sslVerify=false"));
86    }
87
88    #[tokio::test]
89    async fn the_session_stays_out_of_what_git_says() {
90        let tmp = tempfile::tempdir().unwrap();
91        // git names the repository it could not find, so the url carries the session into stderr.
92        let opts = CloneOpts {
93            repo_url: format!("{}/a-secret-key/a-token", tmp.path().display()),
94            ..opts(CloneShape::SingleBranch)
95        };
96        let creds = AwsTempCreds {
97            access_key: "an-access-key".to_owned(),
98            secret_key: "a-secret-key".to_owned(),
99            session_token: "a-token".to_owned(),
100            region: "us-east-1".to_owned(),
101        };
102
103        let outcome = clone_with(&opts, &tmp.path().join("a_root"), &creds).await;
104
105        let Err(GitCallError::Refused { stderr, .. }) = outcome else {
106            panic!("{outcome:?}");
107        };
108        assert!(stderr.contains("[REDACTED]/[REDACTED]"), "{stderr}");
109    }
110}