Skip to content

Commit 08e384f

Browse files
committed
Add command to generate a GitHub app installation access token
Disable the branch-protection command. We don't use it anymore and it's now dangerous to use because it would override our manualy brancht protection configuration.
1 parent 5d8da4b commit 08e384f

File tree

4 files changed

+108
-113
lines changed

4 files changed

+108
-113
lines changed

cmd/github.go

Lines changed: 48 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package cmd
22

33
import (
4-
cfg "github.com/Graylog2/graylog-project-cli/config"
4+
"fmt"
55
"github.com/Graylog2/graylog-project-cli/gh"
66
"github.com/Graylog2/graylog-project-cli/logger"
7-
"github.com/Graylog2/graylog-project-cli/manifest"
8-
p "github.com/Graylog2/graylog-project-cli/project"
9-
"github.com/Graylog2/graylog-project-cli/utils"
107
"github.com/spf13/cobra"
11-
"os"
8+
"github.com/spf13/viper"
129
)
1310

1411
var githubCmd = &cobra.Command{
@@ -18,105 +15,74 @@ var githubCmd = &cobra.Command{
1815
Long: `Management of GitHub projects.
1916
2017
Examples:
21-
# Enable branch protection for a GitHub repository branch
22-
graylog-project github branch-protection --enable --repo Graylog2/graylog2-server --branch 2.4
23-
24-
# Disable branch protection for a GitHub repository branch
25-
graylog-project github branch-protection --disable --repo Graylog2/graylog2-server --branch 2.4
26-
27-
# Enable branch protection for all GitHub repositories in the currently checked out manifest
28-
graylog-project github branch-protection --enable --manifest --branch 2.4
18+
# Create app installation access token for a GitHub org
19+
graylog-project github generate-app-token -o GitHub-org-name -a 1234 -k path/to/app/private.key
2920
`,
3021
}
3122

23+
var githubAppAccessTokenGenerateCmd = &cobra.Command{
24+
Use: "generate-app-access-token",
25+
Aliases: []string{"gaat"},
26+
Short: "Create an access token for an installed GitHub App",
27+
Run: githubAppAccessTokenGenerateCommand,
28+
}
29+
3230
var githubBranchProtectionCmd = &cobra.Command{
3331
Use: "branch-protection",
3432
Aliases: []string{"bp"},
3533
Short: "Manages the branch protection for a repository",
36-
Run: githubBranchProtectionCommand,
34+
Hidden: true,
35+
Run: func(cmd *cobra.Command, args []string) {
36+
logger.Fatal("This command is deprecated and doesn't work anymore.")
37+
},
3738
}
3839

39-
var githubBPEnable bool
40-
var githubBPDisable bool
41-
var githubBPManifest bool
42-
var githubBPDryRun bool
43-
var githubRepository string
44-
var githubRepositoryBranch string
45-
4640
func init() {
41+
githubAppAccessTokenGenerateCmd.Flags().StringP("app-id", "a", "", "the GitHub app ID (env: GPC_GITHUB_APP_ID)")
42+
githubAppAccessTokenGenerateCmd.Flags().StringP("key", "k", "", "path to the private key to use for token generation (env: GPC_GITHUB_APP_KEY)")
43+
githubAppAccessTokenGenerateCmd.Flags().StringP("org", "o", "", "GitHub org for the generated token (app needs to be installed in the org) (env: GPC_GITHUB_ORG)")
44+
45+
viper.BindPFlag("github.app-id", githubAppAccessTokenGenerateCmd.Flags().Lookup("app-id"))
46+
viper.BindPFlag("github.app-key", githubAppAccessTokenGenerateCmd.Flags().Lookup("key"))
47+
viper.BindPFlag("github.org", githubAppAccessTokenGenerateCmd.Flags().Lookup("org"))
48+
49+
viper.MustBindEnv("github.app-id", "GPC_GITHUB_APP_ID")
50+
viper.MustBindEnv("github.app-key", "GPC_GITHUB_APP_KEY")
51+
viper.MustBindEnv("github.org", "GPC_GITHUB_ORG")
52+
53+
githubCmd.AddCommand(githubAppAccessTokenGenerateCmd)
4754
githubCmd.AddCommand(githubBranchProtectionCmd)
4855
RootCmd.AddCommand(githubCmd)
56+
}
4957

50-
githubBranchProtectionCmd.Flags().BoolVar(&githubBPEnable, "enable", false, "Enable branch protection for the GitHub repository")
51-
githubBranchProtectionCmd.Flags().BoolVar(&githubBPDisable, "disable", false, "Disable branch protection for the GitHub repository")
52-
githubBranchProtectionCmd.Flags().BoolVar(&githubBPManifest, "manifest", false, "Toggle branch protection for all repos in the manifest")
53-
githubBranchProtectionCmd.Flags().BoolVar(&githubBPDryRun, "dry-run", false, "Don't execute branch protection commands, only show what would be done.")
54-
githubBranchProtectionCmd.Flags().StringVarP(&githubRepository, "repo", "r", "", "The GitHub repository name (e.g. Graylog2/graylog2-server")
55-
githubBranchProtectionCmd.Flags().StringVarP(&githubRepositoryBranch, "branch", "b", "", "The GitHub repository branch (e.g. master")
58+
type gitHubCmdConfig struct {
59+
GitHub struct {
60+
AppID string `mapstructure:"app-id"`
61+
AppKey string `mapstructure:"app-key"`
62+
Org string `mapstructure:"org"`
63+
} `mapstructure:"github"`
5664
}
5765

58-
func githubBranchProtectionCommand(cmd *cobra.Command, args []string) {
59-
if !githubBPEnable && !githubBPDisable {
60-
logger.Fatal("ERROR: You need to use the --enable or --disable flag")
66+
func githubAppAccessTokenGenerateCommand(cmd *cobra.Command, args []string) {
67+
var cfg gitHubCmdConfig
68+
if err := viper.Unmarshal(&cfg); err != nil {
69+
logger.Fatal("Couldn't deserialize config: %s", err.Error())
6170
}
62-
if githubRepositoryBranch == "" {
63-
logger.Fatal("--branch flag must be set")
64-
}
65-
66-
if githubBPManifest {
67-
config := cfg.Get()
68-
manifestFiles := manifest.ReadState().Files()
69-
project := p.New(config, manifestFiles)
7071

71-
p.ForEachSelectedModule(project, func(module p.Module) {
72-
url, err := utils.ParseGitHubURL(module.Repository)
73-
if err != nil {
74-
logger.Fatal("ERROR: %s", err)
75-
}
76-
toggleGitHubBranchProtection(url.Repository)
77-
})
78-
} else {
79-
if githubRepository == "" {
80-
logger.Fatal("--repo flag must be set")
81-
}
82-
toggleGitHubBranchProtection(githubRepository)
72+
if cfg.GitHub.AppID == "" {
73+
exitWithUsage(cmd, "Missing app ID flag")
8374
}
84-
}
85-
86-
func toggleGitHubBranchProtection(repo string) {
87-
accessToken := os.Getenv("GPC_GITHUB_TOKEN")
88-
89-
if accessToken == "" {
90-
logger.Fatal("ERROR: Missing GPC_GITHUB_TOKEN environment variable")
75+
if cfg.GitHub.AppKey == "" {
76+
exitWithUsage(cmd, "Missing app key flag")
77+
}
78+
if cfg.GitHub.Org == "" {
79+
exitWithUsage(cmd, "Missing GitHub org flag")
9180
}
9281

93-
owner, name, err := gh.SplitRepoString(repo)
82+
token, err := gh.GenerateAppToken(cfg.GitHub.Org, cfg.GitHub.AppID, cfg.GitHub.AppKey)
9483
if err != nil {
9584
logger.Fatal("ERROR: %s", err)
9685
}
9786

98-
branch := githubRepositoryBranch
99-
client := gh.NewGitHubClient(accessToken)
100-
101-
if githubBPEnable {
102-
if !githubBPDryRun {
103-
logger.Info("Adding branch protection from GitHub repository: %s/%s@%s", owner, name, branch)
104-
if err := client.EnableBranchProtection(owner, name, branch); err != nil {
105-
logger.Fatal("ERROR: Unable to enable branch protection for %s/%s@%s: %s",
106-
owner, name, branch, err)
107-
}
108-
} else {
109-
logger.Info("Would add branch protection from GitHub repository: %s/%s@%s", owner, name, branch)
110-
}
111-
} else if githubBPDisable {
112-
if !githubBPDryRun {
113-
logger.Info("Removing branch protection from GitHub repository: %s/%s@%s", owner, name, branch)
114-
if err := client.DisableBranchProtection(owner, name, branch); err != nil {
115-
logger.Fatal("ERROR: Unable to disable branch protection for %s/%s@%s: %s",
116-
owner, name, branch, err)
117-
}
118-
} else {
119-
logger.Info("Would remove branch protection from GitHub repository: %s/%s@%s", owner, name, branch)
120-
}
121-
}
87+
fmt.Println(token)
12288
}

cmd/root.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,11 @@ func initConfig() {
128128
logger.Debug("Error reading config file: %v", err)
129129
}
130130
}
131+
132+
func exitWithUsage(cmd *cobra.Command, format string, args ...any) {
133+
logger.Error(format, args...)
134+
if err := cmd.UsageFunc()(cmd); err != nil {
135+
logger.Error(err.Error())
136+
}
137+
os.Exit(1)
138+
}

gh/app_token.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package gh
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/golang-jwt/jwt/v4"
7+
"os"
8+
"time"
9+
)
10+
11+
// GenerateAppToken creates an access token for the given GitHub app in the given GitHub org. The app must be installed
12+
// in the org. The returned access token is valid for one hour.
13+
// (for details see: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app)
14+
func GenerateAppToken(org string, appId string, keyFile string) (string, error) {
15+
buf, err := os.ReadFile(keyFile)
16+
if err != nil {
17+
return "", fmt.Errorf("couldn't read key file: %w", err)
18+
}
19+
20+
key, err := jwt.ParseRSAPrivateKeyFromPEM(buf)
21+
if err != nil {
22+
return "", fmt.Errorf("couldn't parse key: %w", err)
23+
}
24+
25+
now := time.Now()
26+
27+
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
28+
Issuer: appId,
29+
IssuedAt: jwt.NewNumericDate(now),
30+
ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Minute)), // We only need it to generate an access token
31+
})
32+
33+
tokenString, err := token.SignedString(key)
34+
if err != nil {
35+
return "", fmt.Errorf("couldn't sign token: %w", err)
36+
}
37+
38+
ctx := context.Background()
39+
github := NewGitHubClient(tokenString)
40+
41+
installation, _, err := github.client.Apps.FindOrganizationInstallation(ctx, org)
42+
if err != nil {
43+
return "", fmt.Errorf("couldn't find app installation for org: %w", err)
44+
}
45+
46+
accessToken, _, err := github.client.Apps.CreateInstallationToken(ctx, *installation.ID)
47+
if err != nil {
48+
return "", fmt.Errorf("couldn't create access token: %w", err)
49+
}
50+
51+
return accessToken.GetToken(), nil
52+
}

gh/github.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,6 @@ func NewGitHubClient(accessToken string) Client {
2424
}
2525
}
2626

27-
func (c Client) EnableBranchProtection(owner string, repo string, branch string) error {
28-
request := &github.ProtectionRequest{
29-
RequiredStatusChecks: nil,
30-
RequiredPullRequestReviews: &github.PullRequestReviewsEnforcementRequest{
31-
DismissalRestrictionsRequest: &github.DismissalRestrictionsRequest{
32-
Users: &[]string{},
33-
Teams: &[]string{},
34-
},
35-
DismissStaleReviews: false,
36-
RequireCodeOwnerReviews: false,
37-
RequiredApprovingReviewCount: 1,
38-
},
39-
EnforceAdmins: true,
40-
Restrictions: nil,
41-
}
42-
43-
_, _, err := c.client.Repositories.UpdateBranchProtection(c.ctx, owner, repo, branch, request)
44-
45-
return err
46-
}
47-
48-
func (c Client) DisableBranchProtection(owner string, repo string, branch string) error {
49-
response, err := c.client.Repositories.RemoveBranchProtection(c.ctx, owner, repo, branch)
50-
51-
if response != nil && response.StatusCode == 404 {
52-
return nil
53-
}
54-
55-
return err
56-
}
57-
5827
func SplitRepoString(repository string) (string, string, error) {
5928
tokens := strings.Split(repository, "/")
6029

0 commit comments

Comments
 (0)