-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathextractors.rs
More file actions
235 lines (204 loc) · 8.01 KB
/
Copy pathextractors.rs
File metadata and controls
235 lines (204 loc) · 8.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! CodeQL Extractor Fetcher
use anyhow::{Context, Result};
use ghactions_core::repository::reference::RepositoryReference as Repository;
use octocrab::models::repos::{Asset, Release};
use std::{os::unix::fs::PermissionsExt, path::PathBuf};
async fn fetch_releases(client: &octocrab::Octocrab, repository: &Repository) -> Result<Release> {
let release = if let Some(rel) = &repository.reference {
log::info!("Fetching release by tag: {}", rel);
client
.repos(repository.owner.clone(), repository.name.clone())
.releases()
.get_by_tag(&rel)
.await?
} else {
log::info!("Fetching latest release",);
// Get Latest Release
client
.repos(repository.owner.clone(), repository.name.clone())
.releases()
.get_latest()
.await?
};
log::info!("Release :: {} - {:?}", release.tag_name, release.created_at);
Ok(release)
}
/// Fetch the CodeQL Extractor from the repository
///
/// Finds the correct asset based on ending in `.tar.gz`.
pub async fn fetch_extractor(
client: &octocrab::Octocrab,
repository: &Repository,
attest: bool,
output: &PathBuf,
) -> Result<PathBuf> {
let extractor_tarball = output.join(format!("{}.tar.gz", &repository.name));
let extractor_zip = output.join(format!("{}.zip", &repository.name));
log::debug!("Extractor Tarball :: {extractor_tarball:?}");
let extractor_pack = output.join(&repository.name);
log::info!("Extractor Path :: {extractor_pack:?}");
let toolcache = ghactions::ToolCache::new();
let extractor_archive = if !extractor_tarball.exists() && !extractor_zip.exists() {
log::info!("Downloading asset to {extractor_tarball:?}");
let release = fetch_releases(client, repository).await?;
let (release_asset, file_format) = match release
.assets
.iter()
.find(|a| a.name.ends_with(".tar.gz") || a.name.ends_with(".zip"))
{
Some(asset) if asset.name.ends_with(".tar.gz") => (asset, "tar"),
Some(asset) if asset.name.ends_with(".zip") => (asset, "zip"),
_ => {
return Err(anyhow::anyhow!("No suitable asset found for extractor"));
}
};
log::info!("Asset URL :: {}", release_asset.browser_download_url);
let asset: Asset = client.get(release_asset.url.clone(), None::<&()>).await?;
let extractor_archive = if file_format == "tar" {
extractor_tarball.clone()
} else {
extractor_zip.clone()
};
toolcache
.download_asset(&asset, &extractor_archive)
.await
.context(format!("Extractor Archive: {extractor_tarball:?}"))
.context("Failed to download extractor")?;
extractor_archive
} else {
if extractor_tarball.exists() {
extractor_tarball.clone()
} else {
extractor_zip.clone()
}
};
// Get and log the size of the extractor archive
if let Ok(metadata) = std::fs::metadata(&extractor_archive) {
let size_bytes = metadata.len();
let size_mb = size_bytes as f64 / 1_048_576.0; // Convert to MB (1 MB = 1,048,576 bytes)
log::info!(
"Extractor archive size: {:.2} MB ({} bytes)",
size_mb,
size_bytes
);
} else {
log::warn!("Unable to get size information for the extractor archive");
}
if attest {
log::info!("Attesting asset {extractor_tarball:?}");
let output = tokio::process::Command::new("gh")
.arg("attestation")
.arg("verify")
.arg("--owner")
.arg(repository.owner.clone())
.arg(&extractor_tarball)
.output()
.await?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"Attestation failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
log::info!("Attestation successful");
} else {
log::info!("No attestation requested");
}
log::debug!("Extractor Archive :: {extractor_archive:?}");
if !extractor_pack.exists() {
log::info!("Extracting asset to {extractor_pack:?}");
toolcache
.extract_archive(&extractor_archive, &extractor_pack)
.await
.context(format!("Extractor Archive: {extractor_tarball:?}"))
.context("Failed to extract extractor")?;
}
// Find `codeql-extractor.yml` in the extracted directory using glob
for glob in glob::glob(
&extractor_pack
.join("**/codeql-extractor.yml")
.to_string_lossy(),
)? {
match glob {
Ok(path) => {
// TODO: Load and check the extractor configuration
log::debug!("Extractor Path :: {path:?}");
let full_path = path.parent().unwrap().to_path_buf().canonicalize()?;
// Linux and Macos
#[cfg(unix)]
{
update_tools_permisisons(&full_path)?;
}
return Ok(full_path);
}
Err(e) => {
log::error!("Failed to find extractor: {e}");
return Err(anyhow::anyhow!("Failed to find extractor: {e}"));
}
}
}
Ok(extractor_pack)
}
/// Update the SARIF file with the extractor information (CodeQL ${language})
///
/// Update only the `runs.0.tool.driver` section of the SARIF file
pub fn update_sarif(path: &PathBuf, extractor: String) -> Result<()> {
let sarif_content =
std::fs::read_to_string(path).context(format!("Failed to read SARIF file: {:?}", path))?;
let mut sarif_json: serde_json::Value = serde_json::from_str(&sarif_content)
.context(format!("Failed to parse SARIF file: {:?}", path))?;
log::debug!("SARIF JSON :: {sarif_json:#?}");
if let Some(tool) = sarif_json
.get_mut("runs")
.and_then(|runs| runs.get_mut(0))
.and_then(|run| run.get_mut("tool"))
{
if let Some(driver) = tool.get_mut("driver") {
driver["name"] = serde_json::Value::String(format!("CodeQL - {}", extractor));
log::info!("Updated SARIF file with extractor: {extractor}");
} else {
log::warn!("No 'driver' field found in SARIF file");
}
} else {
log::warn!("No 'runs' or 'tool' field found in SARIF file");
}
let data = serde_json::to_string(&sarif_json)
.context(format!("Failed to serialize SARIF JSON: {:?}", path))?;
// Write the updated SARIF back to the file
std::fs::write(path, data).context(format!("Failed to write SARIF file: {:?}", path))?;
Ok(())
}
/// Update the permissions for tool scripts (*.sh) and the extractor (extractor)
fn update_tools_permisisons(path: &PathBuf) -> Result<()> {
let tools_path = path.join("tools");
log::info!("Tools :: {tools_path:?}");
if tools_path.exists() {
log::debug!("Found tools directory at {tools_path:?}");
// Linux
let linux_extractor = tools_path.join("linux64").join("extractor");
if linux_extractor.exists() {
set_permissions(&linux_extractor)?;
}
// Macos
let macos_extractor = tools_path.join("osx64").join("extractor");
if macos_extractor.exists() {
set_permissions(&macos_extractor)?;
}
for file in std::fs::read_dir(&tools_path)? {
let file = file?;
let path = file.path();
if path.is_file() && path.extension().map_or(false, |ext| ext == "sh") {
log::debug!("Setting executable permissions for {path:?}");
set_permissions(&path)?;
}
}
}
Ok(())
}
/// Sets the file permissions to be executable
fn set_permissions(path: &PathBuf) -> Result<()> {
log::info!("Setting permissions for :: {:?}", path);
let perms = std::fs::Permissions::from_mode(0o555);
std::fs::set_permissions(&path, perms)?;
Ok(())
}