feat: Allow for negations in repo allowlist (#3414)

* feat: Omit repos from allowlist

* Add quote in comment

* Better comment

* Remove test
This commit is contained in:
Luke Massa
2023-07-31 09:46:11 -04:00
committed by GitHub
parent 72a763b283
commit cbb86c248e
4 changed files with 37 additions and 5 deletions

View File

@@ -46,6 +46,7 @@ For example:
* Specific repositories: `--repo-allowlist=github.com/runatlantis/atlantis,github.com/runatlantis/atlantis-tests`
* Your whole organization: `--repo-allowlist=github.com/runatlantis/*`
* Every repository in your GitHub Enterprise install: `--repo-allowlist=github.yourcompany.com/*`
* You can also omit specific repos: `--repo-allowlist='github.com/runatlantis/*,!github.com/runatlantis/untrusted-repo'`
* All repositories: `--repo-allowlist=*`. Useful for when you're in a protected network but dangerous without also setting a webhook secret.
This flag ensures your Atlantis install isn't being used with repositories you don't control. See `atlantis server --help` for more details.

View File

@@ -810,6 +810,7 @@ This is useful when you have many projects and want to keep the pull request cle
* Accepts a comma separated list, ex. `definition1,definition2`
* Format is `{hostname}/{owner}/{repo}`, ex. `github.com/runatlantis/atlantis`
* `*` matches any characters, ex. `github.com/runatlantis/*` will match all repos in the runatlantis organization
* An entry beginning with `!` negates it, ex. `github.com/foo/*,!github.com/foo/bar` will match all github repos in the `foo` owner *except* `bar`.
* For Bitbucket Server: `{hostname}` is the domain without scheme and port, `{owner}` is the name of the project (not the key), and `{repo}` is the repo name
* User (not project) repositories take on the format: `{hostname}/{full name}/{repo}` (e.g., `bitbucket.example.com/Jane Doe/myatlantis` for username `jdoe` and full name `Jane Doe`, which is not very intuitive)
* For Azure DevOps the allowlist takes one of two forms: `{owner}.visualstudio.com/{project}/{repo}` or `dev.azure.com/{owner}/{project}/{repo}`
@@ -820,6 +821,8 @@ This is useful when you have many projects and want to keep the pull request cle
* `--repo-allowlist=github.com/myorg/repo1,github.com/myorg/repo2`
* Allowlist all repos under `myorg` on `github.com`
* `--repo-allowlist='github.com/myorg/*'`
* Allowlist all repos under `myorg` on `github.com`, excluding `myorg/untrusted-repo`
* `--repo-allowlist='github.com/myorg/*,!github.com/myorg/untrusted-repo'`
* Allowlist all repos in my GitHub Enterprise installation
* `--repo-allowlist='github.yourcompany.com/*'`
* Allowlist all repos under `myorg` project `myproject` on Azure DevOps

View File

@@ -24,20 +24,28 @@ const Wildcard = "*"
// RepoAllowlistChecker implements checking if repos are allowlisted to be used with
// this Atlantis.
type RepoAllowlistChecker struct {
rules []string
includeRules []string
omitRules []string
}
// NewRepoAllowlistChecker constructs a new checker and validates that the
// allowlist isn't malformed.
func NewRepoAllowlistChecker(allowlist string) (*RepoAllowlistChecker, error) {
rules := strings.Split(allowlist, ",")
for _, rule := range rules {
includeRules := make([]string, 0)
omitRules := make([]string, 0)
for _, rule := range strings.Split(allowlist, ",") {
if strings.Contains(rule, "://") {
return nil, fmt.Errorf("allowlist %q contained ://", rule)
}
if len(rule) > 1 && rule[0] == '!' {
omitRules = append(omitRules, rule[1:])
} else {
includeRules = append(includeRules, rule)
}
}
return &RepoAllowlistChecker{
rules: rules,
includeRules: includeRules,
omitRules: omitRules,
}, nil
}
@@ -45,7 +53,13 @@ func NewRepoAllowlistChecker(allowlist string) (*RepoAllowlistChecker, error) {
// otherwise.
func (r *RepoAllowlistChecker) IsAllowlisted(repoFullName string, vcsHostname string) bool {
candidate := fmt.Sprintf("%s/%s", vcsHostname, repoFullName)
for _, rule := range r.rules {
shouldInclude := r.matchesAtLeastOneRule(r.includeRules, candidate)
shouldOmit := r.matchesAtLeastOneRule(r.omitRules, candidate)
return shouldInclude && !shouldOmit
}
func (r *RepoAllowlistChecker) matchesAtLeastOneRule(rules []string, candidate string) bool {
for _, rule := range rules {
if r.matchesRule(rule, candidate) {
return true
}

View File

@@ -175,6 +175,20 @@ func TestRepoAllowlistChecker_IsAllowlisted(t *testing.T) {
"github.com",
true,
},
{
"should exclude with negative match",
"github.com/owner/*,!github.com/owner/badrepo",
"owner/badrepo",
"github.com",
false,
},
{
"should match if with negative rule doesn't match",
"github.com/owner/*,!github.com/owner/badrepo",
"owner/otherrepo",
"github.com",
true,
},
}
for _, c := range cases {