From 7ef65dedf2fd33649a71d47a5bf82f448a3b5664 Mon Sep 17 00:00:00 2001 From: Denys Zhuravel Date: Fri, 9 Sep 2022 09:32:08 +0000 Subject: [PATCH] Add a --actions-admin-user flag Same as in here https://github.com/github/codeql-action-sync-tool/blob/33463970b845ac4bb9e4cae6adc2a8b7cd6f6142/internal/push/push.go#L106-L113 Add a warning about the missing site_admin scope in the token Add a way to ignore the actions admin impersonation. --- src/push.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/src/push.go b/src/push.go index 798f161..5f32537 100644 --- a/src/push.go +++ b/src/push.go @@ -17,9 +17,14 @@ import ( "golang.org/x/oauth2" ) +const enterpriseAegisVersionHeaderValue = "GitHub AE" +const enterpriseAPIPath = "/api/v3" +const enterpriseVersionHeaderKey = "X-GitHub-Enterprise-Version" +const xOAuthScopesHeader = "X-OAuth-Scopes" + type PushOnlyFlags struct { - BaseURL, Token string - DisableGitAuth bool + BaseURL, Token, ActionsAdminUser string + DisableGitAuth bool } type PushFlags struct { @@ -34,6 +39,7 @@ func (f *PushFlags) Init(cmd *cobra.Command) { func (f *PushOnlyFlags) Init(cmd *cobra.Command) { cmd.Flags().StringVar(&f.BaseURL, "destination-url", "", "URL of GHES instance") + cmd.Flags().StringVar(&f.ActionsAdminUser, "actions-admin-user", "actions-admin", "The name of the Actions admin user. Pass '-' to disable the impersonation") cmd.Flags().StringVar(&f.Token, "destination-token", "", "Token to access API on GHES instance") cmd.Flags().BoolVar(&f.DisableGitAuth, "disable-push-git-auth", false, "Disables git authentication whilst pushing") } @@ -53,7 +59,62 @@ func (f *PushOnlyFlags) Validate() Validations { return validations } +func GetImpersonationToken(ctx context.Context, flags *PushFlags) (string, error) { + fmt.Printf("Getting an impersonation token for `%s`..\n", flags.ActionsAdminUser) + + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: flags.Token}) + tc := oauth2.NewClient(ctx, ts) + ghClient, err := github.NewEnterpriseClient(flags.BaseURL, flags.BaseURL, tc) + if err != nil { + return "", errors.Wrap(err, "error creating enterprise client") + } + + rootRequest, err := ghClient.NewRequest("GET", enterpriseAPIPath, nil) + if err != nil { + return "", errors.Wrap(err, "Error constructing request for GitHub Enterprise client.") + } + rootResponse, err := ghClient.Do(ctx, rootRequest, nil) + if err != nil { + return "", errors.Wrap(err, "Error checking connectivity for GitHub Enterprise client.") + } + + scopesHeader := rootResponse.Header.Get(xOAuthScopesHeader) + fmt.Printf("These are the scopes we have for the current token `%s`..\n", scopesHeader) + + isAE := rootResponse.Header.Get(enterpriseVersionHeaderKey) == enterpriseAegisVersionHeaderValue + minimumRepositoryScope := "public_repo" + if isAE { + // the default repository scope for non-ae instances is 'public_repo' + // while it is `repo` for ae. + minimumRepositoryScope = "repo" + fmt.Printf("Running against GitHub AE, changing the repository scope to '%s'..\n", minimumRepositoryScope) + } else { + if !strings.Contains(scopesHeader, "site_admin") { + fmt.Printf("The current token doesn't have the `site_admin` scope. The impersonation request for GHES requres the `site_admin` permission to be able to impersonate. For GitHub AE it's not required.") + } + } + + impersonationToken, _, err := ghClient.Admin.CreateUserImpersonation(ctx, flags.ActionsAdminUser, &github.ImpersonateUserOptions{Scopes: []string{minimumRepositoryScope, "workflow"}}) + if err != nil { + return "", errors.Wrap(err, "Failed to impersonate Actions admin user.") + } + + fmt.Printf("Got the impersonation token for `%s`..\n", flags.ActionsAdminUser) + + return impersonationToken.GetToken(), nil +} + func Push(ctx context.Context, flags *PushFlags) error { + if flags.ActionsAdminUser != "-" { + var token, err = GetImpersonationToken(ctx, flags) + if err != nil { + return errors.Wrap(err, "error obtaining the impersonation token") + } + + // Override the initial token with the one that we got in the exchange + flags.Token = token + } + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: flags.Token}) tc := oauth2.NewClient(ctx, ts) ghClient, err := github.NewEnterpriseClient(flags.BaseURL, flags.BaseURL, tc)