Skip to content

Commit

Permalink
[analyze] Deduplicate finegrained GitHub permissions (#3196)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcastorina authored Aug 7, 2024
1 parent fc4829a commit bf2afc9
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 191 deletions.
80 changes: 41 additions & 39 deletions pkg/analyzer/analyzers/github/finegrained/finegrained.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (

var ErrInvalid = errors.New("invalid")

var repoPermFuncMap = []func(client *gh.Client, repo *gh.Repository, access string) (Permission, error){
var repoPermFuncMap = []func(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error){
getActionsPermission,
getAdministrationPermission,
getCodeScanningAlertsPermission,
Expand Down Expand Up @@ -105,7 +105,7 @@ func permissionFormatter(key, val any) (string, string) {
return fmt.Sprintf("%v", key), fmt.Sprintf("%v", val)
}

func notImplementedRepoPerm(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func notImplementedRepoPerm(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
return NoAccess, nil
}

Expand All @@ -114,7 +114,7 @@ func notImplementedAcctPerm(client *gh.Client, user *gh.User) (Permission, error
return NoAccess, nil
}

func getMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// -> GET request to /repos/{owner}/{repo}/collaborators
_, resp, err := client.Repositories.ListCollaborators(context.Background(), *repo.Owner.Login, *repo.Name, nil)
Expand All @@ -129,7 +129,7 @@ func getMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess
return MetadataRead, nil
}

func getActionsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getActionsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
if *repo.Private {
// Risk: Extremely Low
// -> GET request to /repos/{owner}/{repo}/actions/artifacts
Expand Down Expand Up @@ -160,7 +160,7 @@ func getActionsPermission(client *gh.Client, repo *gh.Repository, currentAccess
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Very, very low.
Expand All @@ -183,7 +183,7 @@ func getActionsPermission(client *gh.Client, repo *gh.Repository, currentAccess

// Continue with the other functions using the same pattern...

func getAdministrationPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getAdministrationPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// -> GET request to /repos/{owner}/{repo}/actions/permissions
_, resp, err := client.Repositories.GetActionsPermissions(context.Background(), *repo.Owner.Login, *repo.Name)
Expand Down Expand Up @@ -213,7 +213,7 @@ func getAdministrationPermission(client *gh.Client, repo *gh.Repository, current
}
}

func getCodeScanningAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getCodeScanningAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// -> GET request to /repos/{owner}/{repo}/code-scanning/alerts
_, resp, err := client.CodeScanning.ListAlertsForRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)
Expand Down Expand Up @@ -247,7 +247,7 @@ func getCodeScanningAlertsPermission(client *gh.Client, repo *gh.Repository, cur
}
}

func getCodespacesPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getCodespacesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET request to /repos/{owner}/{repo}/codespaces
_, resp, err := client.Codespaces.ListInRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)
Expand Down Expand Up @@ -279,7 +279,7 @@ func getCodespacesPermission(client *gh.Client, repo *gh.Repository, currentAcce
}
}

func getCodespacesMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getCodespacesMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET request to /repos/{owner}/{repo}/codespaces/machines
req, err := client.NewRequest("GET", "https://api.github.com/repos/"+*repo.Owner.Login+"/"+*repo.Name+"/codespaces/machines", nil)
Expand All @@ -297,7 +297,7 @@ func getCodespacesMetadataPermission(client *gh.Client, repo *gh.Repository, cur
}
}

func getCodespacesSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getCodespacesSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET request to /repos/{owner}/{repo}/codespaces/secrets for non-existent secret
_, resp, err := client.Codespaces.GetRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING)
Expand All @@ -317,7 +317,7 @@ func getCodespacesSecretsPermission(client *gh.Client, repo *gh.Repository, curr
// By default, we have read-only access to commit statuses for all public repos. If only public repos exist under
// this key's permissions, then they best we can hope for us a READ_WRITE status or an UNKNOWN status.
// If a private repo exists, then we can check for READ_ONLY, READ_WRITE and NO_ACCESS.
func getCommitStatusesPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getCommitStatusesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
if *repo.Private {
// Risk: Extremely Low
// GET request to /repos/{owner}/{repo}/commits/{commit_sha}/statuses
Expand Down Expand Up @@ -346,7 +346,7 @@ func getCommitStatusesPermission(client *gh.Client, repo *gh.Repository, current
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Extremely Low
Expand All @@ -369,7 +369,7 @@ func getCommitStatusesPermission(client *gh.Client, repo *gh.Repository, current
// By default, we have read-only access to the contents of all public repos. If only public repos exist under
// this key's permissions, then they best we can hope for us a READ_WRITE status or an UNKNOWN status.
// If a private repo exists, then we can check for READ_ONLY, READ_WRITE and NO_ACCESS.
func getContentsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getContentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
if *repo.Private {
// Risk: Extremely Low
// GET request to /repos/{owner}/{repo}/commits
Expand Down Expand Up @@ -403,7 +403,7 @@ func getContentsPermission(client *gh.Client, repo *gh.Repository, currentAccess
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Low-Medium
Expand All @@ -423,7 +423,7 @@ func getContentsPermission(client *gh.Client, repo *gh.Repository, currentAccess
}
}

func getDependabotAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getDependabotAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/dependabot/alerts
_, resp, err := client.Dependabot.ListRepoAlerts(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListAlertsOptions{})
Expand Down Expand Up @@ -453,7 +453,7 @@ func getDependabotAlertsPermission(client *gh.Client, repo *gh.Repository, curre
}
}

func getDependabotSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getDependabotSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/dependabot/secrets
_, resp, err := client.Dependabot.ListRepoSecrets(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
Expand Down Expand Up @@ -483,7 +483,7 @@ func getDependabotSecretsPermission(client *gh.Client, repo *gh.Repository, curr
}
}

func getDeploymentsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getDeploymentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/deployments
_, resp, err := client.Repositories.ListDeployments(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DeploymentsListOptions{})
Expand Down Expand Up @@ -513,7 +513,7 @@ func getDeploymentsPermission(client *gh.Client, repo *gh.Repository, currentAcc
}
}

func getEnvironmentsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getEnvironmentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/environments
envResp, resp, _ := client.Repositories.ListEnvironments(context.Background(), *repo.Owner.Login, *repo.Name, &gh.EnvironmentListOptions{})
Expand Down Expand Up @@ -554,7 +554,7 @@ func getEnvironmentsPermission(client *gh.Client, repo *gh.Repository, currentAc
}
}

func getIssuesPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getIssuesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {

if *repo.Private {

Expand Down Expand Up @@ -587,7 +587,7 @@ func getIssuesPermission(client *gh.Client, repo *gh.Repository, currentAccess s
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Very Low
Expand All @@ -608,7 +608,7 @@ func getIssuesPermission(client *gh.Client, repo *gh.Repository, currentAccess s
}
}

func getPagesPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getPagesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
if *repo.Private {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/pages
Expand Down Expand Up @@ -643,7 +643,7 @@ func getPagesPermission(client *gh.Client, repo *gh.Repository, currentAccess st
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Very Low
Expand All @@ -668,7 +668,7 @@ func getPagesPermission(client *gh.Client, repo *gh.Repository, currentAccess st
}
}

func getPullRequestsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getPullRequestsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
if *repo.Private {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/pulls
Expand Down Expand Up @@ -699,7 +699,7 @@ func getPullRequestsPermission(client *gh.Client, repo *gh.Repository, currentAc
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Very Low
Expand All @@ -720,7 +720,7 @@ func getPullRequestsPermission(client *gh.Client, repo *gh.Repository, currentAc
}
}

func getRepoSecurityPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getRepoSecurityPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {

if *repo.Private {
// Risk: Extremely Low
Expand Down Expand Up @@ -756,7 +756,7 @@ func getRepoSecurityPermission(client *gh.Client, repo *gh.Repository, currentAc
}
} else {
// Will only land here if already tested one public repo and got a 403.
if currentAccess == "UNKNOWN" {
if currentAccess == NoAccess {
return NoAccess, nil
}
// Risk: Very Low
Expand All @@ -781,7 +781,7 @@ func getRepoSecurityPermission(client *gh.Client, repo *gh.Repository, currentAc
}
}

func getSecretScanningPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getSecretScanningPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/secret-scanning/alerts
_, resp, err := client.SecretScanning.ListAlertsForRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)
Expand Down Expand Up @@ -811,7 +811,7 @@ func getSecretScanningPermission(client *gh.Client, repo *gh.Repository, current
}
}

func getSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/actions/secrets
_, resp, err := client.Actions.ListRepoSecrets(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
Expand Down Expand Up @@ -841,7 +841,7 @@ func getSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess
}
}

func getVariablesPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getVariablesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/actions/variables
_, resp, err := client.Actions.ListRepoVariables(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
Expand Down Expand Up @@ -871,7 +871,7 @@ func getVariablesPermission(client *gh.Client, repo *gh.Repository, currentAcces
}
}

func getWebhooksPermission(client *gh.Client, repo *gh.Repository, currentAccess string) (Permission, error) {
func getWebhooksPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {
// Risk: Extremely Low
// GET /repos/{owner}/{repo}/hooks
_, resp, err := client.Repositories.ListHooks(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})
Expand Down Expand Up @@ -906,15 +906,16 @@ func getWebhooksPermission(client *gh.Client, repo *gh.Repository, currentAccess
// If we only checked one repo, we wouldn't be able to tell if the token has access to a specific permission type.
// Ex: "Code scanning alerts" must be enabled to tell if we have that permission.
func analyzeRepositoryPermissions(client *gh.Client, repos []*gh.Repository) ([]Permission, error) {
perms := []Permission{}
perms := make([]Permission, len(repoPermFuncMap))
for _, repo := range repos {
for _, permFunc := range repoPermFuncMap {
access, err := permFunc(client, repo, "")
if err != nil {
return nil, err
for i, permFunc := range repoPermFuncMap {
access, err := permFunc(client, repo, perms[i])
if err != nil || access == Invalid {
// TODO: Log error.
continue
}
if access != Invalid {
perms = append(perms, access)
if perms[i] == Invalid || perms[i] == NoAccess {
perms[i] = access
}
}
}
Expand Down Expand Up @@ -1259,9 +1260,10 @@ func AnalyzeFineGrainedToken(client *gh.Client, meta *common.TokenMetadata, shal
}
accessibleRepos := make([]*gh.Repository, 0)
for _, repo := range allRepos {
perm, err := getMetadataPermission(client, repo, "")
perm, err := getMetadataPermission(client, repo, Invalid)
if err != nil {
return nil, err
// TODO: Log error.
continue
}
if perm != Invalid {
accessibleRepos = append(accessibleRepos, repo)
Expand Down
Loading

0 comments on commit bf2afc9

Please sign in to comment.