Skip to content

Commit 01b8718

Browse files
David HavenunRob
David Haven
andauthored
allow user to pass gh-app-key directly instead of filename (#1706)
* allow user to pass gh-app-key directly instead of filename * start fixing tests * fix make test * update website with info about new flag * Apply suggestions from code review Co-authored-by: Roberto Hidalgo <[email protected]> Co-authored-by: Roberto Hidalgo <[email protected]>
1 parent fe31bc6 commit 01b8718

9 files changed

+73
-38
lines changed

cmd/server.go

+18-6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const (
6565
GHTokenFlag = "gh-token"
6666
GHUserFlag = "gh-user"
6767
GHAppIDFlag = "gh-app-id"
68+
GHAppKeyFlag = "gh-app-key"
6869
GHAppKeyFileFlag = "gh-app-key-file"
6970
GHAppSlugFlag = "gh-app-slug"
7071
GHOrganizationFlag = "gh-org"
@@ -192,6 +193,10 @@ var stringFlags = map[string]stringFlag{
192193
GHTokenFlag: {
193194
description: "GitHub token of API user. Can also be specified via the ATLANTIS_GH_TOKEN environment variable.",
194195
},
196+
GHAppKeyFlag: {
197+
description: "The GitHub App's private key",
198+
defaultValue: "",
199+
},
195200
GHAppKeyFileFlag: {
196201
description: "A path to a file containing the GitHub App's private key",
197202
defaultValue: "",
@@ -634,12 +639,19 @@ func (s *ServerCmd) validate(userConfig server.UserConfig) error {
634639

635640
// The following combinations are valid.
636641
// 1. github user and token set
637-
// 2. gitlab user and token set
638-
// 3. bitbucket user and token set
639-
// 4. azuredevops user and token set
640-
// 5. any combination of the above
641-
vcsErr := fmt.Errorf("--%s/--%s or --%s/--%s or --%s/--%s or --%s/--%s or --%s/--%s must be set", GHUserFlag, GHTokenFlag, GHAppIDFlag, GHAppKeyFileFlag, GitlabUserFlag, GitlabTokenFlag, BitbucketUserFlag, BitbucketTokenFlag, ADUserFlag, ADTokenFlag)
642-
if ((userConfig.GithubUser == "") != (userConfig.GithubToken == "")) || ((userConfig.GithubAppID == 0) != (userConfig.GithubAppKey == "")) || ((userConfig.GitlabUser == "") != (userConfig.GitlabToken == "")) || ((userConfig.BitbucketUser == "") != (userConfig.BitbucketToken == "")) || ((userConfig.AzureDevopsUser == "") != (userConfig.AzureDevopsToken == "")) {
642+
// 2. github app ID and (key file set or key set)
643+
// 3. gitlab user and token set
644+
// 4. bitbucket user and token set
645+
// 5. azuredevops user and token set
646+
// 6. any combination of the above
647+
vcsErr := fmt.Errorf("--%s/--%s or --%s/--%s or --%s/--%s or --%s/--%s or --%s/--%s or --%s/--%s must be set", GHUserFlag, GHTokenFlag, GHAppIDFlag, GHAppKeyFileFlag, GHAppIDFlag, GHAppKeyFlag, GitlabUserFlag, GitlabTokenFlag, BitbucketUserFlag, BitbucketTokenFlag, ADUserFlag, ADTokenFlag)
648+
if ((userConfig.GithubUser == "") != (userConfig.GithubToken == "")) || ((userConfig.GitlabUser == "") != (userConfig.GitlabToken == "")) || ((userConfig.BitbucketUser == "") != (userConfig.BitbucketToken == "")) || ((userConfig.AzureDevopsUser == "") != (userConfig.AzureDevopsToken == "")) {
649+
return vcsErr
650+
}
651+
if (userConfig.GithubAppID != 0) && ((userConfig.GithubAppKey == "") && (userConfig.GithubAppKeyFile == "")) {
652+
return vcsErr
653+
}
654+
if (userConfig.GithubAppID == 0) && ((userConfig.GithubAppKey != "") || (userConfig.GithubAppKeyFile != "")) {
643655
return vcsErr
644656
}
645657
// At this point, we know that there can't be a single user/token without

cmd/server_test.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
homedir "github.com/mitchellh/go-homedir"
2626
"github.com/runatlantis/atlantis/server"
27+
"github.com/runatlantis/atlantis/server/events/vcs/fixtures"
2728
"github.com/runatlantis/atlantis/server/logging"
2829
. "github.com/runatlantis/atlantis/testing"
2930
"github.com/spf13/cobra"
@@ -75,6 +76,7 @@ var testFlags = map[string]interface{}{
7576
GHTokenFlag: "token",
7677
GHUserFlag: "user",
7778
GHAppIDFlag: int64(0),
79+
GHAppKeyFlag: "",
7880
GHAppKeyFileFlag: "",
7981
GHAppSlugFlag: "atlantis",
8082
GHOrganizationFlag: "",
@@ -350,7 +352,7 @@ func TestExecute_ValidateSSLConfig(t *testing.T) {
350352
}
351353

352354
func TestExecute_ValidateVCSConfig(t *testing.T) {
353-
expErr := "--gh-user/--gh-token or --gh-app-id/--gh-app-key-file or --gitlab-user/--gitlab-token or --bitbucket-user/--bitbucket-token or --azuredevops-user/--azuredevops-token must be set"
355+
expErr := "--gh-user/--gh-token or --gh-app-id/--gh-app-key-file or --gh-app-id/--gh-app-key or --gitlab-user/--gitlab-token or --bitbucket-user/--bitbucket-token or --azuredevops-user/--azuredevops-token must be set"
354356
cases := []struct {
355357
description string
356358
flags map[string]interface{}
@@ -404,12 +406,19 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
404406
true,
405407
},
406408
{
407-
"just github app key set",
409+
"just github app key file set",
408410
map[string]interface{}{
409411
GHAppKeyFileFlag: "key.pem",
410412
},
411413
true,
412414
},
415+
{
416+
"just github app key set",
417+
map[string]interface{}{
418+
GHAppKeyFlag: fixtures.GithubPrivateKey,
419+
},
420+
true,
421+
},
413422
{
414423
"just gitlab user set",
415424
map[string]interface{}{
@@ -464,13 +473,21 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
464473
false,
465474
},
466475
{
467-
"github app and key set and should be successful",
476+
"github app and key file set and should be successful",
468477
map[string]interface{}{
469478
GHAppIDFlag: "1",
470479
GHAppKeyFileFlag: "key.pem",
471480
},
472481
false,
473482
},
483+
{
484+
"github app and key set and should be successful",
485+
map[string]interface{}{
486+
GHAppIDFlag: "1",
487+
GHAppKeyFlag: fixtures.GithubPrivateKey,
488+
},
489+
false,
490+
},
474491
{
475492
"gitlab user and gitlab token set and should be successful",
476493
map[string]interface{}{
@@ -572,7 +589,7 @@ func TestExecute_GithubUser(t *testing.T) {
572589
func TestExecute_GithubApp(t *testing.T) {
573590
t.Log("Should remove the @ from the github username if it's passed.")
574591
c := setup(map[string]interface{}{
575-
GHAppKeyFileFlag: "key.pem",
592+
GHAppKeyFlag: fixtures.GithubPrivateKey,
576593
GHAppIDFlag: "1",
577594
RepoAllowlistFlag: "*",
578595
}, t)

runatlantis.io/docs/access-credentials.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Available in Atlantis versions **newer** than 0.13.0.
4545
- Create a file with the contents of the GitHub App Key, e.g. `atlantis-app-key.pem`
4646
- Restart Atlantis with new flags: `atlantis server --gh-app-id <your id> --gh-app-key-file atlantis-app-key.pem --gh-webhook-secret <your secret> --write-git-creds --repo-allowlist 'github.com/your-org/*' --atlantis-url https://$ATLANTIS_HOST`.
4747

48-
NOTE: You can also create a config file instead of using flags. See [Server Configuration](/docs/server-configuration.html#config-file).
48+
NOTE: Instead of using a file for the GitHub App Key you can also pass the key value directly using `--gh-app-key`. You can also create a config file instead of using flags. See [Server Configuration](/docs/server-configuration.html#config-file).
4949

5050
::: warning
5151
Only a single installation per GitHub App is supported at the moment.

runatlantis.io/docs/server-configuration.md

+10
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ Values are chosen in this order:
342342
```
343343
Path to a GitHub App PEM encoded private key file. If set, GitHub authentication will be performed as [an installation](https://developer.github.com/v3/apps/installations/).
344344

345+
- ### `--gh-app-key`
346+
```bash
347+
atlantis server --gh-app-key="-----BEGIN RSA PRIVATE KEY-----(...)"
348+
```
349+
The PEM encoded private key for the GitHub App.
350+
351+
::: warning SECURITY WARNING
352+
The contents of the private key will be visible by anyone that can run `ps` or look at the shell history of the machine where Atlantis is running. Use `--gh-app-key-file` to mitigate that risk.
353+
:::
354+
345355
* ### `--gitlab-hostname`
346356
```bash
347357
atlantis server --gitlab-hostname="my.gitlab.enterprise.com"

server/events/github_app_working_dir_test.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,14 @@ func TestClone_GithubAppNoneExisting(t *testing.T) {
3131
TestingOverrideHeadCloneURL: fmt.Sprintf("file://%s", repoDir),
3232
}
3333

34-
tmpDir, cleanup3 := DirStructure(t, map[string]interface{}{
35-
"key.pem": fixtures.GithubPrivateKey,
36-
})
37-
defer cleanup3()
38-
3934
defer disableSSLVerification()()
4035
testServer, err := fixtures.GithubAppTestServer(t)
4136
Ok(t, err)
4237

4338
gwd := &events.GithubAppWorkingDir{
4439
WorkingDir: wd,
4540
Credentials: &vcs.GithubAppCredentials{
46-
KeyPath: fmt.Sprintf("%v/key.pem", tmpDir),
41+
Key: []byte(fixtures.GithubPrivateKey),
4742
AppID: 1,
4843
Hostname: testServer,
4944
},

server/events/vcs/github_credentials.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (c *GithubUserCredentials) GetToken() (string, error) {
6868
// GithubAppCredentials implements GithubCredentials for github app installation token flow.
6969
type GithubAppCredentials struct {
7070
AppID int64
71-
KeyPath string
71+
Key []byte
7272
Hostname string
7373
apiURL *url.URL
7474
installationID int64
@@ -128,7 +128,7 @@ func (c *GithubAppCredentials) getInstallationID() (int64, error) {
128128

129129
tr := http.DefaultTransport
130130
// A non-installation transport
131-
t, err := ghinstallation.NewAppsTransportKeyFromFile(tr, c.AppID, c.KeyPath)
131+
t, err := ghinstallation.NewAppsTransport(tr, c.AppID, c.Key)
132132
if err != nil {
133133
return 0, err
134134
}
@@ -163,7 +163,7 @@ func (c *GithubAppCredentials) transport() (*ghinstallation.Transport, error) {
163163
}
164164

165165
tr := http.DefaultTransport
166-
itr, err := ghinstallation.NewKeyFromFile(tr, c.AppID, installationID, c.KeyPath)
166+
itr, err := ghinstallation.New(tr, c.AppID, installationID, c.Key)
167167
if err == nil {
168168
apiURL := c.getAPIURL()
169169
itr.BaseURL = strings.TrimSuffix(apiURL.String(), "/")

server/events/vcs/github_credentials_test.go

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package vcs_test
22

33
import (
4-
"fmt"
54
"testing"
65

76
"github.com/runatlantis/atlantis/server/events/vcs"
@@ -21,15 +20,9 @@ func TestGithubClient_GetUser_AppSlug(t *testing.T) {
2120
tempSecrets, err := anonClient.ExchangeCode("good-code")
2221
Ok(t, err)
2322

24-
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
25-
"key.pem": tempSecrets.Key,
26-
})
27-
defer cleanup()
28-
keyPath := fmt.Sprintf("%v/key.pem", tmpDir)
29-
3023
appCreds := &vcs.GithubAppCredentials{
3124
AppID: tempSecrets.ID,
32-
KeyPath: keyPath,
25+
Key: []byte(fixtures.GithubPrivateKey),
3326
Hostname: testServer,
3427
AppSlug: "some-app",
3528
}
@@ -51,15 +44,9 @@ func TestGithubClient_AppAuthentication(t *testing.T) {
5144
tempSecrets, err := anonClient.ExchangeCode("good-code")
5245
Ok(t, err)
5346

54-
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
55-
"key.pem": tempSecrets.Key,
56-
})
57-
defer cleanup()
58-
keyPath := fmt.Sprintf("%v/key.pem", tmpDir)
59-
6047
appCreds := &vcs.GithubAppCredentials{
6148
AppID: tempSecrets.ID,
62-
KeyPath: keyPath,
49+
Key: []byte(fixtures.GithubPrivateKey),
6350
Hostname: testServer,
6451
}
6552
_, err = vcs.NewGithubClient(testServer, appCreds, logging.NewNoopLogger(t))

server/server.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"flag"
2222
"fmt"
23+
"io/ioutil"
2324
"log"
2425
"net/http"
2526
"net/url"
@@ -156,10 +157,22 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
156157
User: userConfig.GithubUser,
157158
Token: userConfig.GithubToken,
158159
}
159-
} else if userConfig.GithubAppID != 0 {
160+
} else if userConfig.GithubAppID != 0 && userConfig.GithubAppKeyFile != "" {
161+
privateKey, err := ioutil.ReadFile(userConfig.GithubAppKeyFile)
162+
if err != nil {
163+
return nil, err
164+
}
165+
githubCredentials = &vcs.GithubAppCredentials{
166+
AppID: userConfig.GithubAppID,
167+
Key: privateKey,
168+
Hostname: userConfig.GithubHostname,
169+
AppSlug: userConfig.GithubAppSlug,
170+
}
171+
githubAppEnabled = true
172+
} else if userConfig.GithubAppID != 0 && userConfig.GithubAppKey != "" {
160173
githubCredentials = &vcs.GithubAppCredentials{
161174
AppID: userConfig.GithubAppID,
162-
KeyPath: userConfig.GithubAppKey,
175+
Key: []byte(userConfig.GithubAppKey),
163176
Hostname: userConfig.GithubHostname,
164177
AppSlug: userConfig.GithubAppSlug,
165178
}

server/user_config.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ type UserConfig struct {
3636
GithubWebhookSecret string `mapstructure:"gh-webhook-secret"`
3737
GithubOrg string `mapstructure:"gh-org"`
3838
GithubAppID int64 `mapstructure:"gh-app-id"`
39-
GithubAppKey string `mapstructure:"gh-app-key-file"`
39+
GithubAppKey string `mapstructure:"gh-app-key"`
40+
GithubAppKeyFile string `mapstructure:"gh-app-key-file"`
4041
GithubAppSlug string `mapstructure:"gh-app-slug"`
4142
GitlabHostname string `mapstructure:"gitlab-hostname"`
4243
GitlabToken string `mapstructure:"gitlab-token"`

0 commit comments

Comments
 (0)