Skip to content

Commit cd1cda1

Browse files
woodruffw최하늘
and
최하늘
authored
feat: add argument --ghe-hostname for GHE Servers (#371)
Co-authored-by: 최하늘 <[email protected]>
1 parent 238e30d commit cd1cda1

File tree

6 files changed

+92
-7
lines changed

6 files changed

+92
-7
lines changed

Diff for: docs/snippets/help.txt

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Options:
1414
Perform only offline operations [env: ZIZMOR_OFFLINE=]
1515
--gh-token <GH_TOKEN>
1616
The GitHub API token to use [env: GH_TOKEN=]
17+
--gh-hostname <GH_HOSTNAME>
18+
The GitHub Server Hostname. Defaults to github.com [env: GH_HOST=] [default: github.com]
1719
--no-online-audits
1820
Perform only offline audits [env: ZIZMOR_NO_ONLINE_AUDITS=]
1921
-v, --verbose...

Diff for: docs/usage.md

+16
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,22 @@ as GitHub's example of [running ESLint] as a security workflow.
434434

435435
[Advanced Security]: https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security
436436

437+
### Use with GitHub Enterprise
438+
439+
`zizmor` supports GitHub instances other than `github.com`.
440+
441+
To use it with your [GitHub Enterprise] instance (either cloud or self-hosted),
442+
pass your instance's domain with `--gh-hostname` or `GH_HOST`:
443+
444+
```bash
445+
zizmor --gh-hostname custom.example.com ...
446+
447+
# or, with GH_HOST
448+
GH_HOST=custom.ghe.com zizmor ...
449+
```
450+
451+
[GitHub Enterprise]: https://github.com/enterprise
452+
437453
### Use with `pre-commit`
438454

439455
`zizmor` can be used with the [`pre-commit`](https://pre-commit.com/) framework.

Diff for: src/audit/github_env.rs

+3
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ impl Audit for GitHubEnv {
378378
mod tests {
379379
use crate::audit::github_env::{GitHubEnv, GITHUB_ENV_WRITE_CMD};
380380
use crate::audit::Audit;
381+
use crate::github_api::GitHubHost;
381382
use crate::state::AuditState;
382383

383384
#[test]
@@ -435,6 +436,7 @@ mod tests {
435436
no_online_audits: false,
436437
cache_dir: "/tmp/zizmor".into(),
437438
gh_token: None,
439+
gh_hostname: GitHubHost::Standard("github.com".into()),
438440
};
439441

440442
let sut = GitHubEnv::new(audit_state).expect("failed to create audit");
@@ -519,6 +521,7 @@ mod tests {
519521
no_online_audits: false,
520522
cache_dir: "/tmp/zizmor".into(),
521523
gh_token: None,
524+
gh_hostname: GitHubHost::Standard("github.com".into()),
522525
};
523526

524527
let sut = GitHubEnv::new(audit_state).expect("failed to create audit");

Diff for: src/github_api.rs

+55-3
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,46 @@ use crate::{
2727
utils::PipeSelf,
2828
};
2929

30+
/// Represents different types of GitHub hosts.
31+
#[derive(Clone, Debug, PartialEq)]
32+
pub(crate) enum GitHubHost {
33+
Enterprise(String),
34+
Standard(String),
35+
}
36+
37+
impl GitHubHost {
38+
pub(crate) fn from_clap(hostname: &str) -> Result<Self, String> {
39+
let normalized = hostname.to_lowercase();
40+
41+
// NOTE: ideally we'd do a full domain validity check here.
42+
// For now, this just checks the most likely kind of user
43+
// confusion (supplying a URL instead of a bare domain name).
44+
if normalized.starts_with("https://") || normalized.starts_with("http://") {
45+
return Err("must be a domain name, not a URL".into());
46+
}
47+
48+
if normalized.eq_ignore_ascii_case("github.com") || normalized.ends_with(".ghe.com") {
49+
Ok(Self::Standard(hostname.into()))
50+
} else {
51+
Ok(Self::Enterprise(hostname.into()))
52+
}
53+
}
54+
55+
fn to_api_url(&self) -> String {
56+
match self {
57+
Self::Enterprise(ref host) => format!("https://{host}/api/v3"),
58+
Self::Standard(ref host) => format!("https://api.{host}"),
59+
}
60+
}
61+
}
62+
3063
pub(crate) struct Client {
31-
api_base: &'static str,
64+
api_base: String,
3265
http: ClientWithMiddleware,
3366
}
3467

3568
impl Client {
36-
pub(crate) fn new(token: &str, cache_dir: &Path) -> Self {
69+
pub(crate) fn new(hostname: &GitHubHost, token: &str, cache_dir: &Path) -> Self {
3770
let mut headers = HeaderMap::new();
3871
headers.insert(USER_AGENT, "zizmor".parse().unwrap());
3972
headers.insert(
@@ -71,7 +104,7 @@ impl Client {
71104
.build();
72105

73106
Self {
74-
api_base: "https://api.github.com",
107+
api_base: hostname.to_api_url(),
75108
http,
76109
}
77110
}
@@ -463,3 +496,22 @@ pub(crate) struct File {
463496
name: String,
464497
path: String,
465498
}
499+
500+
#[cfg(test)]
501+
mod tests {
502+
use crate::github_api::GitHubHost;
503+
504+
#[test]
505+
fn test_github_host() {
506+
for (host, expected) in [
507+
("github.com", "https://api.github.com"),
508+
("something.ghe.com", "https://api.something.ghe.com"),
509+
(
510+
"selfhosted.example.com",
511+
"https://selfhosted.example.com/api/v3",
512+
),
513+
] {
514+
assert_eq!(GitHubHost::from_clap(host).unwrap().to_api_url(), expected);
515+
}
516+
}
517+
}

Diff for: src/main.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use clap::{Parser, ValueEnum};
99
use clap_verbosity_flag::InfoLevel;
1010
use config::Config;
1111
use finding::{Confidence, Persona, Severity};
12+
use github_api::GitHubHost;
1213
use indicatif::ProgressStyle;
1314
use models::{Action, Uses};
1415
use owo_colors::OwoColorize;
@@ -48,13 +49,18 @@ struct App {
4849
///
4950
/// This disables all online audit rules, and prevents zizmor from
5051
/// auditing remote repositories.
51-
#[arg(short, long, env = "ZIZMOR_OFFLINE", group = "_offline")]
52+
#[arg(short, long, env = "ZIZMOR_OFFLINE",
53+
conflicts_with_all = ["gh_token", "gh_hostname"])]
5254
offline: bool,
5355

5456
/// The GitHub API token to use.
55-
#[arg(long, env, group = "_offline")]
57+
#[arg(long, env)]
5658
gh_token: Option<String>,
5759

60+
/// The GitHub Server Hostname. Defaults to github.com
61+
#[arg(long, env = "GH_HOST", default_value = "github.com", value_parser = GitHubHost::from_clap)]
62+
gh_hostname: GitHubHost,
63+
5864
/// Perform only offline audits.
5965
///
6066
/// This is a weaker version of `--offline`: instead of completely

Diff for: src/state.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ use std::path::PathBuf;
44

55
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
66

7-
use crate::{github_api::Client, App};
7+
use crate::{
8+
github_api::{Client, GitHubHost},
9+
App,
10+
};
811

912
#[derive(Clone)]
1013
pub(crate) struct AuditState {
1114
pub(crate) no_online_audits: bool,
1215
pub(crate) cache_dir: PathBuf,
1316
pub(crate) gh_token: Option<String>,
17+
pub(crate) gh_hostname: GitHubHost,
1418
}
1519

1620
impl AuditState {
@@ -33,14 +37,16 @@ impl AuditState {
3337
no_online_audits: app.no_online_audits,
3438
cache_dir,
3539
gh_token: app.gh_token.clone(),
40+
gh_hostname: app.gh_hostname.clone(),
3641
}
3742
}
3843

3944
/// Return a cache-configured GitHub API client, if
4045
/// a GitHub API token is present.
46+
/// If gh_hostname is also present, set it as api_base for client.
4147
pub(crate) fn github_client(&self) -> Option<Client> {
4248
self.gh_token
4349
.as_ref()
44-
.map(|token| Client::new(token, &self.cache_dir))
50+
.map(|token| Client::new(&self.gh_hostname, token, &self.cache_dir))
4551
}
4652
}

0 commit comments

Comments
 (0)