From a7d71b189c1d63905f690f0315aca10e32e23697 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 12 Sep 2024 15:32:04 -0400 Subject: [PATCH] handle --offline, verbosity (#16) --- Cargo.lock | 11 ++++++++++ Cargo.toml | 1 + src/audit/artipacked.rs | 2 +- src/audit/excessive_permissions.rs | 4 ++-- src/audit/hardcoded_container_credentials.rs | 4 ++-- src/audit/impostor_commit.rs | 15 +++++++++++--- src/audit/mod.rs | 3 ++- src/audit/pull_request_target.rs | 3 ++- src/audit/ref_confusion.rs | 15 +++++++++++--- src/audit/template_injection.rs | 2 +- src/audit/use_trusted_publishing.rs | 2 +- src/main.rs | 21 ++++++++++++++++---- src/models.rs | 6 ------ 13 files changed, 64 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79c2b173..1c56145a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,16 @@ dependencies = [ "clap_derive", ] +[[package]] +name = "clap-verbosity-flag" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d19864d6b68464c59f7162c9914a0b569ddc2926b4a2d71afe62a9738eff53" +dependencies = [ + "clap", + "log", +] + [[package]] name = "clap_builder" version = "4.5.15" @@ -1708,6 +1718,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "clap-verbosity-flag", "env_logger", "github-actions-models", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 1449ea82..9549fb0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ homepage = "https://github.com/woodruffw/zizmor" [dependencies] anyhow = "1.0.86" clap = { version = "4.5.16", features = ["derive", "env"] } +clap-verbosity-flag = "2.2.1" env_logger = "0.11.5" github-actions-models = "0.6.0" itertools = "0.13.0" diff --git a/src/audit/artipacked.rs b/src/audit/artipacked.rs index bc3c6272..786e64b3 100644 --- a/src/audit/artipacked.rs +++ b/src/audit/artipacked.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use super::WorkflowAudit; use crate::{ finding::{Confidence, Finding, Severity}, - models::AuditConfig, + AuditConfig, }; use crate::{models::Workflow, utils::split_patterns}; diff --git a/src/audit/excessive_permissions.rs b/src/audit/excessive_permissions.rs index 91b834a5..9da9ca9d 100644 --- a/src/audit/excessive_permissions.rs +++ b/src/audit/excessive_permissions.rs @@ -8,7 +8,7 @@ use github_actions_models::{ use super::WorkflowAudit; use crate::{ finding::{Confidence, Severity}, - models::AuditConfig, + AuditConfig, }; // Subjective mapping of permissions to severities, when given `write` access. @@ -45,7 +45,7 @@ impl<'a> WorkflowAudit<'a> for ExcessivePermissions<'a> { "excessive-permissions" } - fn new(config: crate::models::AuditConfig<'a>) -> anyhow::Result + fn new(config: AuditConfig<'a>) -> anyhow::Result where Self: Sized, { diff --git a/src/audit/hardcoded_container_credentials.rs b/src/audit/hardcoded_container_credentials.rs index e78121e6..639287ce 100644 --- a/src/audit/hardcoded_container_credentials.rs +++ b/src/audit/hardcoded_container_credentials.rs @@ -11,7 +11,7 @@ use github_actions_models::{ use super::WorkflowAudit; use crate::{ finding::{Confidence, Severity}, - models::AuditConfig, + AuditConfig, }; pub(crate) struct HardcodedContainerCredentials<'a> { @@ -26,7 +26,7 @@ impl<'a> WorkflowAudit<'a> for HardcodedContainerCredentials<'a> { "hardcoded-container-credentials" } - fn new(config: crate::models::AuditConfig<'a>) -> anyhow::Result + fn new(config: AuditConfig<'a>) -> anyhow::Result where Self: Sized, { diff --git a/src/audit/impostor_commit.rs b/src/audit/impostor_commit.rs index e8ead305..138299b3 100644 --- a/src/audit/impostor_commit.rs +++ b/src/audit/impostor_commit.rs @@ -10,14 +10,15 @@ use std::{ ops::Deref, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use github_actions_models::workflow::{job::StepBody, Job}; use super::WorkflowAudit; use crate::{ finding::{Confidence, Finding, Severity}, github_api::{self, Branch, ComparisonStatus, Tag}, - models::{AuditConfig, Uses, Workflow}, + models::{Uses, Workflow}, + AuditConfig, }; pub const IMPOSTOR_ANNOTATION: &str = "uses a commit that doesn't belong to the specified org/repo"; @@ -139,7 +140,15 @@ impl<'a> WorkflowAudit<'a> for ImpostorCommit<'a> { } fn new(config: AuditConfig<'a>) -> Result { - let client = github_api::Client::new(config.gh_token); + if config.offline { + return Err(anyhow!("offline audits only requested")); + } + + let Some(gh_token) = config.gh_token else { + return Err(anyhow!("can't audit without a GitHub API token")); + }; + + let client = github_api::Client::new(gh_token); Ok(ImpostorCommit { _config: config, diff --git a/src/audit/mod.rs b/src/audit/mod.rs index 599effaa..cf5cb53c 100644 --- a/src/audit/mod.rs +++ b/src/audit/mod.rs @@ -2,7 +2,8 @@ use anyhow::Result; use crate::{ finding::{Finding, FindingBuilder}, - models::{AuditConfig, Workflow}, + models::Workflow, + AuditConfig, }; pub(crate) mod artipacked; diff --git a/src/audit/pull_request_target.rs b/src/audit/pull_request_target.rs index 6aa433f0..4a839dbf 100644 --- a/src/audit/pull_request_target.rs +++ b/src/audit/pull_request_target.rs @@ -4,7 +4,8 @@ use github_actions_models::workflow::Trigger; use super::WorkflowAudit; use crate::finding::{Confidence, Finding, Severity}; -use crate::models::{AuditConfig, Workflow}; +use crate::models::Workflow; +use crate::AuditConfig; pub(crate) struct PullRequestTarget<'a> { pub(crate) _config: AuditConfig<'a>, diff --git a/src/audit/ref_confusion.rs b/src/audit/ref_confusion.rs index bd01c422..4f648250 100644 --- a/src/audit/ref_confusion.rs +++ b/src/audit/ref_confusion.rs @@ -8,14 +8,15 @@ use std::ops::Deref; -use anyhow::Result; +use anyhow::{anyhow, Result}; use github_actions_models::workflow::{job::StepBody, Job}; use super::WorkflowAudit; use crate::{ finding::{Confidence, Severity}, github_api, - models::{AuditConfig, Uses}, + models::Uses, + AuditConfig, }; const REF_CONFUSION_ANNOTATION: &str = @@ -62,9 +63,17 @@ impl<'a> WorkflowAudit<'a> for RefConfusion<'a> { where Self: Sized, { + if config.offline { + return Err(anyhow!("offline audits only requested")); + } + + let Some(gh_token) = config.gh_token else { + return Err(anyhow!("can't audit without a GitHub API token")); + }; + Ok(Self { _config: config, - client: github_api::Client::new(config.gh_token), + client: github_api::Client::new(gh_token), }) } diff --git a/src/audit/template_injection.rs b/src/audit/template_injection.rs index 996704f9..28ef66aa 100644 --- a/src/audit/template_injection.rs +++ b/src/audit/template_injection.rs @@ -12,8 +12,8 @@ use github_actions_models::workflow::{job::StepBody, Job}; use super::WorkflowAudit; use crate::{ finding::{Confidence, Severity}, - models::AuditConfig, utils::iter_expressions, + AuditConfig, }; pub(crate) struct TemplateInjection<'a> { diff --git a/src/audit/use_trusted_publishing.rs b/src/audit/use_trusted_publishing.rs index e9ce25da..3efcadaa 100644 --- a/src/audit/use_trusted_publishing.rs +++ b/src/audit/use_trusted_publishing.rs @@ -8,7 +8,7 @@ use github_actions_models::{ use super::WorkflowAudit; use crate::{ finding::{Confidence, Severity}, - models::AuditConfig, + AuditConfig, }; const USES_MANUAL_CREDENTIAL: &str = diff --git a/src/main.rs b/src/main.rs index 41cdb9e7..fe0b2129 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use std::{io::stdout, path::PathBuf}; use anyhow::{anyhow, Result}; use audit::WorkflowAudit; use clap::{Parser, ValueEnum}; -use models::AuditConfig; use registry::Registry; mod audit; @@ -25,9 +24,12 @@ struct Args { #[arg(short, long)] offline: bool, + #[command(flatten)] + verbose: clap_verbosity_flag::Verbosity, + /// The GitHub API token to use. #[arg(long, env)] - gh_token: String, + gh_token: Option, /// The output format to emit. By default, plain text will be emitted /// on an interactive terminal and JSON otherwise. @@ -45,19 +47,30 @@ pub(crate) enum OutputFormat { Sarif, } +#[derive(Copy, Clone)] +pub(crate) struct AuditConfig<'a> { + pub(crate) pedantic: bool, + pub(crate) offline: bool, + pub(crate) gh_token: Option<&'a str>, +} + impl<'a> From<&'a Args> for AuditConfig<'a> { fn from(value: &'a Args) -> Self { Self { pedantic: value.pedantic, - gh_token: &value.gh_token, + offline: value.offline, + gh_token: value.gh_token.as_deref(), } } } fn main() -> Result<()> { - env_logger::init(); let args = Args::parse(); + env_logger::Builder::new() + .filter_level(args.verbose.log_level_filter()) + .init(); + let config = AuditConfig::from(&args); let mut workflow_paths = vec![]; diff --git a/src/models.rs b/src/models.rs index 199aecce..f6bb8339 100644 --- a/src/models.rs +++ b/src/models.rs @@ -192,12 +192,6 @@ impl<'w> Iterator for Steps<'w> { } } -#[derive(Copy, Clone)] -pub(crate) struct AuditConfig<'a> { - pub(crate) pedantic: bool, - pub(crate) gh_token: &'a str, -} - /// Represents the components of an "action ref", i.e. the value /// of a `uses:` clause in a normal job step or a reusable workflow job. /// Does not support `docker://` refs, or "local" (i.e. `./`) refs.