13 Commits

Author SHA1 Message Date
Ajay
3a24d8ed61 Merge pull request #46 from derwasp/derwasp/add-actions-admin-user-switch
Some checks failed
goreleaser / goreleaser (push) Has been cancelled
Add actions admin user impersonation via a --actions-admin-user flag
2022-09-21 21:59:10 +09:00
Denys Zhuravel
4b5fb2c8ad Add a note about the site_admin scope
* add a note to the readme
* add a note to the autogenerated cli help
2022-09-21 12:05:27 +00:00
Denys Zhuravel
37946dfc91 Require site_admin for the impersonation logic
Also, fixed casing in the text literals
2022-09-21 11:59:16 +00:00
Denys Zhuravel
a7d588cf7c Don't enable the impersonation by default.
* Require the impersonation to be explicit with the --actions-admin-user flag
* Reword the log messages to look the same way the existing messages are
2022-09-20 09:48:29 +00:00
Denys Zhuravel
c32265a4df Add actions-admin-user to readme 2022-09-09 09:35:07 +00:00
Denys Zhuravel
669526d239 Add the compiled binary to git ignore 2022-09-09 09:35:07 +00:00
Denys Zhuravel
e4525fb4aa Add dummy routes to the test stub to mimic the github api 2022-09-09 09:35:07 +00:00
Denys Zhuravel
7ef65dedf2 Add a --actions-admin-user flag
Same as in here
33463970b8/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.
2022-09-09 09:35:01 +00:00
Ajay
51405dbbd5 Merge pull request #44 from niketbp/patch-1
Fix small typo in README.md
2022-08-08 00:17:59 +09:00
Niket Parikh
e59ec6da57 Fix small typo in README.md 2022-07-27 14:41:47 -07:00
Ajay
f123f251b9 Merge pull request #41 from martincostello/patch-1
Fix typos
2022-06-02 23:29:42 +09:00
Martin Costello
1854c6ae2d Fix typo
Fix another typo.

Co-authored-by: Ajay <40024974+ajaykn@users.noreply.github.com>
2022-06-01 07:04:43 +01:00
Martin Costello
400f484e15 Fix typos
Fix a number of minor typos.
2022-05-31 14:38:07 +01:00
4 changed files with 90 additions and 7 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@
_tools/bin
bin
test/tmp
dist
dist
actions-sync

View File

@@ -24,6 +24,8 @@ When there are machines which have access to both the public internet and the GH
**Arguments:**
- `actions-admin-user` _(optional)_
The name of the Actions admin user, which will be used for updating the chosen action. To use the default user, pass `actions-admin`. If not set, the impersonation is disabled. Note that `site_admin` scope is required in the token for the impersonation to work.
- `cache-dir` _(required)_
The directory in which to cache repositories as they are synced. This speeds up re-syncing.
- `destination-url` _(required)_
@@ -35,7 +37,7 @@ When there are machines which have access to both the public internet and the GH
- `repo-name-list` _(optional)_
A comma-separated list of repositories to be synced. Each entry follows the format of `repo-name`.
- `repo-name-list-file` _(optional)_
A path to a file containing a newline separate listof repositories to be synced. Each entry follows te format of `repo-name`.
A path to a file containing a newline separated list of repositories to be synced. Each entry follows the format of `repo-name`.
**Example Usage:**
@@ -64,11 +66,11 @@ When no machine has access to both the public internet and the GHES instance:
- `cache-dir` _(required)_
The directory to cache the pulled repositories into.
- `repo-name` _(optional)_
A single repository to be synced. In the format of `owner/repo`. Optionally if you wish the repository to be named different on your GHES instance you can provide an aliase in the format: `upstream_owner/up_streamrepo:destination_owner/destination_repo`
A single repository to be synced. In the format of `owner/repo`. Optionally if you wish the repository to be named different on your GHES instance you can provide an alias in the format: `upstream_owner/upstream_repo:destination_owner/destination_repo`
- `repo-name-list` _(optional)_
A comma-separated list of repositories to be synced. Each entry follows the format of `repo-name`.
- `repo-name-list-file` _(optional)_
A path to a file containing a newline separate listof repositories to be synced. Each entry follows te format of `repo-name`.
A path to a file containing a newline separated list of repositories to be synced. Each entry follows the format of `repo-name`.
**Example Usage:**
@@ -84,6 +86,8 @@ When no machine has access to both the public internet and the GHES instance:
**Arguments:**
- `actions-admin-user` _(optional)_
The name of the Actions admin user, which will be used for updating the chosen action. To use the default user, pass `actions-admin`. If not set, the impersonation is disabled. Note that `site_admin` scope is required in the token for the impersonation to work.
- `cache-dir` _(required)_
The directory containing the repositories fetched using the `pull` command.
- `destination-url` _(required)_
@@ -104,7 +108,7 @@ When no machine has access to both the public internet and the GHES instance:
## Destination token scopes
When creating a personal access token include the `repo` and `workflow` scopes. Include the `site_admin` scope (optional) if you want organizations to be created as necessary.
When creating a personal access token include the `repo` and `workflow` scopes. Include the `site_admin` scope (optional) if you want organizations to be created as necessary or you want to use the impersonation logic for the `push` or `sync` commands.
## Contributing

View File

@@ -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", "", "A user to impersonate for the push requests. To use the default name, pass 'actions-admin'. Note that the site_admin scope in the token is required for the impersonation to work.")
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,64 @@ 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)
if !strings.Contains(scopesHeader, "site_admin") {
return "", errors.Wrap(err, "the current token doesn't have the `site_admin` scope, the impersonation function requires the `site_admin` permission to be able to impersonate.")
}
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)
}
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
} else {
fmt.Print("not using impersonation for the requests \n")
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: flags.Token})
tc := oauth2.NewClient(ctx, ts)
ghClient, err := github.NewEnterpriseClient(flags.BaseURL, flags.BaseURL, tc)

View File

@@ -26,6 +26,10 @@ func main() {
r := mux.NewRouter()
r.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {})
r.HandleFunc("/api/v3", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("x-github-enterprise-version", "GitHub AE")
})
r.HandleFunc("/api/v3/user", func(w http.ResponseWriter, r *http.Request) {
currentUser := github.User{Login: &authenticatedLogin}
b, _ := json.Marshal(currentUser)
@@ -35,6 +39,17 @@ func main() {
}
})
r.HandleFunc("/api/v3/admin/users/actions-admin/authorizations", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("x-github-enterprise-version", "GitHub AE")
token := "token"
auth := github.Authorization{Token: &token}
b, _ := json.Marshal(auth)
_, err := w.Write(b)
if err != nil {
panic(err)
}
}).Methods("POST")
r.HandleFunc("/api/v3/admin/organizations", func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {