|
1 | 1 | #![allow(clippy::disallowed_types)]
|
2 | 2 |
|
3 | 3 | use crate::common::{
|
4 |
| - READ_ONLY_GITHUB_TOKEN, TestContext, apply_filters, decode_token, uv_snapshot, |
| 4 | + READ_ONLY_GITHUB_TOKEN, SSH_DEPLOY_KEY, TestContext, apply_filters, decode_token, uv_snapshot, |
5 | 5 | };
|
6 | 6 | use anyhow::{Ok, Result};
|
7 | 7 | use assert_cmd::assert::OutputAssertExt;
|
8 | 8 | use assert_fs::prelude::*;
|
9 | 9 | use indoc::{formatdoc, indoc};
|
10 | 10 | use insta::assert_snapshot;
|
| 11 | +use std::path::Path; |
11 | 12 | use std::process::Stdio;
|
| 13 | +use uv_fs::Simplified; |
12 | 14 |
|
13 | 15 | #[test]
|
14 | 16 | fn requirements_txt_dependency() -> Result<()> {
|
@@ -1174,6 +1176,122 @@ fn requirements_txt_https_git_credentials() -> Result<()> {
|
1174 | 1176 | Ok(())
|
1175 | 1177 | }
|
1176 | 1178 |
|
| 1179 | +/// SSH blocks too permissive key files. |
| 1180 | +fn reduce_key_permissions(key_file: &Path) -> Result<()> { |
| 1181 | + #[cfg(unix)] |
| 1182 | + { |
| 1183 | + use std::fs::Permissions; |
| 1184 | + use std::os::unix::fs::PermissionsExt; |
| 1185 | + fs_err::set_permissions(key_file, Permissions::from_mode(0o400))?; |
| 1186 | + } |
| 1187 | + #[cfg(windows)] |
| 1188 | + { |
| 1189 | + use std::process::Command; |
| 1190 | + |
| 1191 | + // https://superuser.com/a/1489152 |
| 1192 | + Command::new("icacls") |
| 1193 | + .arg(key_file) |
| 1194 | + .arg("/inheritance:r") |
| 1195 | + .assert() |
| 1196 | + .success(); |
| 1197 | + Command::new("icacls") |
| 1198 | + .arg(key_file) |
| 1199 | + .arg("/grant:r") |
| 1200 | + .arg(format!("{}:R", whoami::username())) |
| 1201 | + .assert() |
| 1202 | + .success(); |
| 1203 | + } |
| 1204 | + Ok(()) |
| 1205 | +} |
| 1206 | + |
| 1207 | +/// Don't redact the username `git` in SSH URLs. |
| 1208 | +#[cfg(feature = "git")] |
| 1209 | +#[test] |
| 1210 | +fn requirements_txt_ssh_git_username() -> Result<()> { |
| 1211 | + let context = TestContext::new("3.12"); |
| 1212 | + |
| 1213 | + let pyproject_toml = context.temp_dir.child("pyproject.toml"); |
| 1214 | + pyproject_toml.write_str( |
| 1215 | + r#" |
| 1216 | + [project] |
| 1217 | + name = "debug" |
| 1218 | + version = "0.1.0" |
| 1219 | + requires-python = ">=3.12" |
| 1220 | + dependencies = ["uv-private-pypackage @ git+ssh://[email protected]/astral-test/uv-private-pypackage.git@d780faf0ac91257d4d5a4f0c5a0e4509608c0071"] |
| 1221 | + "#, |
| 1222 | + )?; |
| 1223 | + |
| 1224 | + let fake_deploy_key = context.temp_dir.child("fake_deploy_key"); |
| 1225 | + fake_deploy_key.write_str("not a key")?; |
| 1226 | + reduce_key_permissions(&fake_deploy_key)?; |
| 1227 | + |
| 1228 | + // Ensure that we're loading the key and fail if it isn't present |
| 1229 | + let failing_git_ssh_command = format!( |
| 1230 | + "ssh -i {} -o IdentitiesOnly=yes -F /dev/null -o StrictHostKeyChecking=no", |
| 1231 | + fake_deploy_key.portable_display() |
| 1232 | + ); |
| 1233 | + let mut filters = context.filters(); |
| 1234 | + filters.push(( |
| 1235 | + "process didn't exit successfully: .*", |
| 1236 | + "process didn't exit successfully: [GIT_COMMAND_ERROR]", |
| 1237 | + )); |
| 1238 | + let load_key_error = regex::escape(r#"Load key "[TEMP_DIR]/fake_deploy_key":"#) + ".*"; |
| 1239 | + filters.push(( |
| 1240 | + &load_key_error, |
| 1241 | + r#"Load key "[TEMP_DIR]/fake_deploy_key": [ERROR]"#, |
| 1242 | + )); |
| 1243 | + filters.push(( |
| 1244 | + " *Warning: Permanently added 'github.com' \\(ED25519\\) to the list of known hosts.*\n", |
| 1245 | + "", |
| 1246 | + )); |
| 1247 | + filters.push(("failed to clone into: .*", "failed to clone into: [PATH]")); |
| 1248 | + uv_snapshot!(filters, context.export().env("GIT_SSH_COMMAND", failing_git_ssh_command), @r#" |
| 1249 | + success: false |
| 1250 | + exit_code: 1 |
| 1251 | + ----- stdout ----- |
| 1252 | +
|
| 1253 | + ----- stderr ----- |
| 1254 | + × Failed to download and build `uv-private-pypackage @ git+ssh://[email protected]/astral-test/uv-private-pypackage.git@d780faf0ac91257d4d5a4f0c5a0e4509608c0071` |
| 1255 | + ├─▶ Git operation failed |
| 1256 | + ├─▶ failed to clone into: [PATH] |
| 1257 | + ├─▶ failed to fetch branch, tag, or commit `d780faf0ac91257d4d5a4f0c5a0e4509608c0071` |
| 1258 | + ╰─▶ process didn't exit successfully: [GIT_COMMAND_ERROR] |
| 1259 | + --- stderr |
| 1260 | + Load key "[TEMP_DIR]/fake_deploy_key": [ERROR] |
| 1261 | + [email protected]: Permission denied (publickey). |
| 1262 | + fatal: Could not read from remote repository. |
| 1263 | +
|
| 1264 | + Please make sure you have the correct access rights |
| 1265 | + and the repository exists. |
| 1266 | + "#); |
| 1267 | + |
| 1268 | + let ssh_deploy_key = context.temp_dir.child("uv_test_key"); |
| 1269 | + ssh_deploy_key.write_str((decode_token(&[SSH_DEPLOY_KEY]) + "\n").as_str())?; |
| 1270 | + reduce_key_permissions(&ssh_deploy_key)?; |
| 1271 | + |
| 1272 | + // Use the specified SSH key, and only that key, ignore `~/.ssh/config`, disable host key |
| 1273 | + // verification for Windows. |
| 1274 | + let git_ssh_command = format!( |
| 1275 | + "ssh -i {} -o IdentitiesOnly=yes -F /dev/null -o StrictHostKeyChecking=no", |
| 1276 | + ssh_deploy_key.portable_display() |
| 1277 | + ); |
| 1278 | + |
| 1279 | + uv_snapshot!(context.filters(), context.export().env("GIT_SSH_COMMAND", git_ssh_command), @r" |
| 1280 | + success: true |
| 1281 | + exit_code: 0 |
| 1282 | + ----- stdout ----- |
| 1283 | + # This file was autogenerated by uv via the following command: |
| 1284 | + # uv export --cache-dir [CACHE_DIR] |
| 1285 | + uv-private-pypackage @ git+ssh://[email protected]/astral-test/uv-private-pypackage.git@d780faf0ac91257d4d5a4f0c5a0e4509608c0071 |
| 1286 | + # via debug |
| 1287 | +
|
| 1288 | + ----- stderr ----- |
| 1289 | + Resolved 2 packages in [TIME] |
| 1290 | + "); |
| 1291 | + |
| 1292 | + Ok(()) |
| 1293 | +} |
| 1294 | + |
1177 | 1295 | #[test]
|
1178 | 1296 | fn requirements_txt_https_credentials() -> Result<()> {
|
1179 | 1297 | let context = TestContext::new("3.12");
|
|
0 commit comments