Skip to content

Commit 2ba7455

Browse files
committed
MVP HardcodedContainerCredentials check
1 parent 79deaa2 commit 2ba7455

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use std::ops::Deref;
2+
3+
use github_actions_models::{
4+
common::Expression,
5+
workflow::{
6+
job::{Container, DockerCredentials},
7+
Job,
8+
},
9+
};
10+
11+
use crate::{
12+
finding::{Confidence, Severity},
13+
models::AuditConfig,
14+
};
15+
16+
use super::WorkflowAudit;
17+
18+
pub(crate) struct HardcodedContainerCredentials<'a> {
19+
pub(crate) _config: AuditConfig<'a>,
20+
}
21+
22+
impl<'a> WorkflowAudit<'a> for HardcodedContainerCredentials<'a> {
23+
fn ident() -> &'static str
24+
where
25+
Self: Sized,
26+
{
27+
"hardcoded-container-credentials"
28+
}
29+
30+
fn new(config: crate::models::AuditConfig<'a>) -> anyhow::Result<Self>
31+
where
32+
Self: Sized,
33+
{
34+
Ok(Self { _config: config })
35+
}
36+
37+
fn audit<'w>(
38+
&self,
39+
workflow: &'w crate::models::Workflow,
40+
) -> anyhow::Result<Vec<crate::finding::Finding<'w>>> {
41+
let mut findings = vec![];
42+
43+
for job in workflow.jobs() {
44+
let Job::NormalJob(normal) = job.deref() else {
45+
continue;
46+
};
47+
48+
if let Some(Container::Container {
49+
image: _,
50+
credentials:
51+
Some(DockerCredentials {
52+
username: _,
53+
password: Some(password),
54+
}),
55+
..
56+
}) = &normal.container
57+
{
58+
// If the password doesn't parse as an expression, it's hardcoded.
59+
if Expression::from_curly(password.into()).is_none() {
60+
findings.push(
61+
Self::finding()
62+
.severity(Severity::High)
63+
.confidence(Confidence::High)
64+
.add_location(
65+
job.location()
66+
.annotated("container registry password is hard-coded"),
67+
)
68+
.build(workflow)?,
69+
)
70+
}
71+
}
72+
73+
for (service, config) in normal.services.iter() {
74+
if let Container::Container {
75+
image: _,
76+
credentials:
77+
Some(DockerCredentials {
78+
username: _,
79+
password: Some(password),
80+
}),
81+
..
82+
} = &config
83+
{
84+
if Expression::from_curly(password.into()).is_none() {
85+
findings.push(
86+
Self::finding()
87+
.severity(Severity::High)
88+
.confidence(Confidence::High)
89+
.add_location(job.location().annotated(format!(
90+
"service {service}: container registry password is hard-coded"
91+
)))
92+
.build(workflow)?,
93+
)
94+
}
95+
}
96+
}
97+
}
98+
99+
Ok(findings)
100+
}
101+
}

src/audit/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
use anyhow::Result;
66

77
pub(crate) mod artipacked;
8+
pub(crate) mod hardcoded_container_credentials;
89
pub(crate) mod impostor_commit;
910
pub(crate) mod pull_request_target;
1011
pub(crate) mod ref_confusion;

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ fn main() -> Result<()> {
8686
&audit::ref_confusion::RefConfusion::new(config)?,
8787
&audit::use_trusted_publishing::UseTrustedPublishing::new(config)?,
8888
&audit::template_injection::TemplateInjection::new(config)?,
89+
&audit::hardcoded_container_credentials::HardcodedContainerCredentials::new(config)?,
8990
];
9091
for workflow in workflows.iter() {
9192
// TODO: Proper abstraction for multiple audits here.

0 commit comments

Comments
 (0)