AWS SDK

AWS SDK

rev. fa0785761a24d090a787b647e6daec820214052b (ignoring whitespace)

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-config/Cargo.toml

@@ -163,163 +0,194 @@
  183    183   
version = "0.3.16"
  184    184   
features = ["fmt", "json"]
  185    185   
  186    186   
[dev-dependencies.tokio]
  187    187   
version = "1.23.1"
  188    188   
features = ["full", "test-util"]
  189    189   
  190    190   
[dev-dependencies.serde]
  191    191   
version = "1"
  192    192   
features = ["derive"]
         193  +
[target."cfg(windows)".dev-dependencies]
         194  +
tempfile = "3.16.0"

tmp-codegen-diff/aws-sdk/sdk/aws-config/src/credential_process.rs

@@ -58,58 +122,137 @@
   78     78   
    }
   79     79   
   80     80   
    pub(crate) fn builder() -> Builder {
   81     81   
        Builder::default()
   82     82   
    }
   83     83   
   84     84   
    async fn credentials(&self) -> provider::Result {
   85     85   
        // Security: command arguments must be redacted at debug level
   86     86   
        tracing::debug!(command = %self.command, "loading credentials from external process");
   87     87   
   88         -
        let command = if cfg!(windows) {
          88  +
        // On Windows, the command runs through `cmd.exe /C`. The command string
          89  +
        // is appended with `raw_arg` rather than as a normal argument so that
          90  +
        // Rust does not apply its own C runtime style escaping (which `cmd.exe`
          91  +
        // does not understand), and the whole command is wrapped in an extra
          92  +
        // pair of quotes as `cmd.exe` requires. This preserves a quoted first
          93  +
        // token containing spaces. Ex: for an executable installed under
          94  +
        // `C:\Program Files\...`, such as AppStream 2.0's machine-role provider.
          95  +
        // Previously the entire string was passed as a single normal argument,
          96  +
        // whose escaping combined with `cmd.exe`'s quote-stripping to mangle
          97  +
        // such paths.
          98  +
        #[cfg(windows)]
          99  +
        let command = {
         100  +
            use std::os::windows::process::CommandExt;
   89    101   
            let mut command = Command::new("cmd.exe");
   90         -
            command.args(["/C", self.command.unredacted()]);
         102  +
            command.arg("/C");
         103  +
            command.raw_arg(format!("\"{}\"", self.command.unredacted()));
   91    104   
            command
   92         -
        } else {
         105  +
        };
         106  +
        #[cfg(not(windows))]
         107  +
        let command = {
   93    108   
            let mut command = Command::new("sh");
   94    109   
            command.args(["-c", self.command.unredacted()]);
   95    110   
            command
   96    111   
        };
   97    112   
        let output = tokio::process::Command::from(command)
   98    113   
            .output()
   99    114   
            .await
  100    115   
            .map_err(|e| {
  101    116   
                CredentialsError::provider_error(format!(
  102    117   
                    "Error retrieving credentials from external process: {e}",
@@ -329,344 +0,457 @@
  349    364   
            )))
  350    365   
            .account_id("012345678901")
  351    366   
            .build();
  352    367   
        let creds = provider.provide_credentials().await.unwrap();
  353    368   
        assert_eq!(
  354    369   
            &vec![AwsCredentialFeature::CredentialsProcess],
  355    370   
            creds.get_property::<Vec<AwsCredentialFeature>>().unwrap()
  356    371   
        );
  357    372   
    }
  358    373   
}
         374  +
         375  +
// Integration tests that actually spawn a process from a path containing a
         376  +
// space. These run only on Windows: they are the regression tests for the
         377  +
// `credential_process` quoting bug (internal: P491659165). The pre-existing
         378  +
// `credential_process` tests above use the Unix `echo` builtin and are
         379  +
// skipped on Windows.
         380  +
#[cfg(all(test, windows))]
         381  +
mod windows_tests {
         382  +
    use crate::credential_process::CredentialProcessProvider;
         383  +
    use aws_credential_types::provider::ProvideCredentials;
         384  +
    use std::path::{Path, PathBuf};
         385  +
         386  +
    const CREDS_JSON: &str = "{\"Version\":1,\"AccessKeyId\":\"ASIARTESTID\",\"SecretAccessKey\":\"TESTSECRETKEY\",\"SessionToken\":\"TESTSESSIONTOKEN\",\"Expiration\":\"2035-01-01T00:00:00Z\"}";
         387  +
         388  +
    // Write a `.cmd` provider that prints valid credential JSON to stdout into
         389  +
    // `dir`, returning the path to the script. `@echo off` keeps stdout clean so
         390  +
    // the only thing emitted is the JSON document.
         391  +
    fn write_provider(dir: &Path) -> PathBuf {
         392  +
        std::fs::create_dir_all(dir).unwrap();
         393  +
        let script = dir.join("provider.cmd");
         394  +
        std::fs::write(&script, format!("@echo off\r\necho {CREDS_JSON}\r\n")).unwrap();
         395  +
        script
         396  +
    }
         397  +
         398  +
    #[tokio::test]
         399  +
    async fn spaced_path_with_argument_resolves() {
         400  +
        let tmp = tempfile::TempDir::new().unwrap();
         401  +
        let script = write_provider(&tmp.path().join("Program Space"));
         402  +
        assert!(
         403  +
            script.to_string_lossy().contains(' '),
         404  +
            "test fixture path must contain a space: {}",
         405  +
            script.display()
         406  +
        );
         407  +
         408  +
        // Quote the path as a real config would, and pass an argument — exactly
         409  +
        // the AppStream shape: `"...PhotonRoleCredentialProvider.exe" --role=Machine`.
         410  +
        let command = format!("\"{}\" --role=Machine", script.display());
         411  +
        let provider = CredentialProcessProvider::new(command);
         412  +
         413  +
        let creds = provider
         414  +
            .provide_credentials()
         415  +
            .await
         416  +
            .expect("credentials should resolve from a quoted spaced path with an argument");
         417  +
        assert_eq!(creds.access_key_id(), "ASIARTESTID");
         418  +
        assert_eq!(creds.secret_access_key(), "TESTSECRETKEY");
         419  +
        assert_eq!(creds.session_token(), Some("TESTSESSIONTOKEN"));
         420  +
    }
         421  +
         422  +
    #[tokio::test]
         423  +
    async fn spaced_path_without_argument_resolves() {
         424  +
        let tmp = tempfile::TempDir::new().unwrap();
         425  +
        let script = write_provider(&tmp.path().join("Program Space"));
         426  +
         427  +
        let command = format!("\"{}\"", script.display());
         428  +
        let provider = CredentialProcessProvider::new(command);
         429  +
         430  +
        let creds = provider
         431  +
            .provide_credentials()
         432  +
            .await
         433  +
            .expect("credentials should resolve from a quoted spaced path with no argument");
         434  +
        assert_eq!(creds.access_key_id(), "ASIARTESTID");
         435  +
    }
         436  +
         437  +
    #[tokio::test]
         438  +
    async fn unquoted_unspaced_path_still_resolves() {
         439  +
        // Control: an unquoted path with no spaces continues to work.
         440  +
        let tmp = tempfile::TempDir::new().unwrap();
         441  +
        let script = write_provider(&tmp.path().join("nospace"));
         442  +
        // Only meaningful if no path component (including the temp root) has a
         443  +
        // space; otherwise the unquoted form is not a valid no-space control.
         444  +
        if script.to_string_lossy().contains(' ') {
         445  +
            return;
         446  +
        }
         447  +
         448  +
        let command = script.display().to_string();
         449  +
        let provider = CredentialProcessProvider::new(command);
         450  +
         451  +
        let creds = provider
         452  +
            .provide_credentials()
         453  +
            .await
         454  +
            .expect("control (unquoted, no spaces) should resolve");
         455  +
        assert_eq!(creds.access_key_id(), "ASIARTESTID");
         456  +
    }
         457  +
}