Skip to content

Commit

Permalink
feat: Allow for negations in repo allowlist (runatlantis#3414)
Browse files Browse the repository at this point in the history
* feat: Omit repos from allowlist

* Add quote in comment

* Better comment

* Remove test
  • Loading branch information
lukemassa authored Jul 31, 2023
1 parent 72a763b commit cbb86c2
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 5 deletions.
1 change: 1 addition & 0 deletions runatlantis.io/docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions runatlantis.io/docs/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand All @@ -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
Expand Down
24 changes: 19 additions & 5 deletions server/events/repo_allowlist_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,42 @@ 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
}

// IsAllowlisted returns true if this repo is in our allowlist and false
// 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
}
Expand Down
14 changes: 14 additions & 0 deletions server/events/repo_allowlist_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit cbb86c2

Please sign in to comment.