Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ Available Commands:

Flags:
--app-id string The GitHub App to connect to. ($BATON_APP_ID)
--app-privatekey-path string Path to private key that is used to connect to the GitHub App ($BATON_APP_PRIVATEKEY_PATH)
--app-privatekey string Raw PEM contents of the private key used to connect to the GitHub App. Takes precedence over app-privatekey-path when both are set. ($BATON_APP_PRIVATEKEY)
--app-privatekey-path string Path to private key that is used to connect to the GitHub App. Ignored when app-privatekey is set. ($BATON_APP_PRIVATEKEY_PATH)
--client-id string The client ID used to authenticate with ConductorOne ($BATON_CLIENT_ID)
--client-secret string The client secret used to authenticate with ConductorOne ($BATON_CLIENT_SECRET)
--enterprises strings Sync enterprise roles, must be an admin of the enterprise. ($BATON_ENTERPRISES)
Expand Down
14 changes: 9 additions & 5 deletions config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,22 @@
{
"name": "app-privatekey-path",
"displayName": "GitHub App private key (.pem)",
"description": "Path to private key that is used to connect to the GitHub App",
"isRequired": true,
"description": "Path to private key that is used to connect to the GitHub App. Ignored when app-privatekey is set.",
"isSecret": true,
"stringField": {
"rules": {
"isRequired": true
},
"type": "STRING_FIELD_TYPE_FILE_UPLOAD",
"allowedExtensions": [
".pem"
]
}
},
{
"name": "app-privatekey",
"displayName": "GitHub App private key (PEM)",
"description": "Raw PEM contents of the private key used to connect to the GitHub App. Takes precedence over app-privatekey-path when both are set.",
"isSecret": true,
"stringField": {}
},
{
"name": "org",
"displayName": "Github App Organization",
Expand Down Expand Up @@ -202,6 +205,7 @@
"fields": [
"app-id",
"app-privatekey-path",
"app-privatekey",
"org",
"sync-secrets",
"omit-archived-repositories",
Expand Down
3 changes: 3 additions & 0 deletions docs/connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,10 @@ stringData:

# GitHub credentials if configuring with a GitHub app
BATON_APP_ID: <GitHub app ID>
# Supply the private key one of two ways:
BATON_APP_PRIVATEKEY_PATH: <Path to the private key file for the GitHub app>
# ...or pass the raw PEM contents directly (takes precedence when both are set):
# BATON_APP_PRIVATEKEY: <Raw PEM contents of the private key for the GitHub app>
BATON_ORGS: <Name of the single GitHib org the app was created for>

# Optional: include if you want C1 to provision access using this connector
Expand Down
1 change: 1 addition & 0 deletions pkg/config/conf.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,25 @@ var (
field.WithRequired(true),
)

// appPrivateKeyPath and appPrivateKey are two ways to supply the same GitHub
// App private key. Neither is marked required individually: providing either
// one satisfies the "app private key required" check, which is enforced in the
// connector's GitHub App constructor (see pkg/connector.appPrivateKeyPEM).
// A framework-level constraint can't express "required only for the GitHub App
// auth group", since constraints are evaluated globally across auth methods.
appPrivateKeyPath = field.FileUploadField(
"app-privatekey-path",
[]string{".pem"},
field.WithDisplayName("GitHub App private key (.pem)"),
field.WithDescription("Path to private key that is used to connect to the GitHub App"),
field.WithDescription("Path to private key that is used to connect to the GitHub App. Ignored when app-privatekey is set."),
field.WithIsSecret(true),
)

appPrivateKey = field.StringField(
"app-privatekey",
field.WithDisplayName("GitHub App private key (PEM)"),
field.WithDescription("Raw PEM contents of the private key used to connect to the GitHub App. Takes precedence over app-privatekey-path when both are set."),
field.WithIsSecret(true),
field.WithRequired(true),
)

syncSecrets = field.BoolField(
Expand Down Expand Up @@ -90,6 +102,7 @@ var Config = field.NewConfiguration(
instanceUrlField,
appIDField,
appPrivateKeyPath,
appPrivateKey,
orgField,
syncSecrets,
omitArchivedRepositories,
Expand All @@ -110,7 +123,7 @@ var Config = field.NewConfiguration(
Name: GithubAppGroup,
DisplayName: "GitHub app",
HelpText: "Use a github app for authentication",
Fields: []field.SchemaField{appIDField, appPrivateKeyPath, orgField, syncSecrets, omitArchivedRepositories, directCollaboratorsOnly},
Fields: []field.SchemaField{appIDField, appPrivateKeyPath, appPrivateKey, orgField, syncSecrets, omitArchivedRepositories, directCollaboratorsOnly},
Default: false,
},
}),
Expand Down
41 changes: 41 additions & 0 deletions pkg/connector/app_privatekey_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package connector

import (
"testing"

cfg "github.com/conductorone/baton-github/pkg/config"
"github.com/stretchr/testify/require"
)

func TestAppPrivateKeyPEM(t *testing.T) {
const (
inlineKey = "-----BEGIN PRIVATE KEY-----\ninline\n-----END PRIVATE KEY-----"
pathKey = "-----BEGIN PRIVATE KEY-----\nfrom-path\n-----END PRIVATE KEY-----"
)

t.Run("prefers app-privatekey when both are set", func(t *testing.T) {
got, err := appPrivateKeyPEM(&cfg.Github{
AppPrivatekey: inlineKey,
AppPrivatekeyPath: []byte(pathKey),
})
require.NoError(t, err)
require.Equal(t, inlineKey, got)
})

t.Run("uses app-privatekey when only it is set", func(t *testing.T) {
got, err := appPrivateKeyPEM(&cfg.Github{AppPrivatekey: inlineKey})
require.NoError(t, err)
require.Equal(t, inlineKey, got)
})

t.Run("falls back to app-privatekey-path when app-privatekey is empty", func(t *testing.T) {
got, err := appPrivateKeyPEM(&cfg.Github{AppPrivatekeyPath: []byte(pathKey)})
require.NoError(t, err)
require.Equal(t, pathKey, got)
})

t.Run("errors when neither is set", func(t *testing.T) {
_, err := appPrivateKeyPEM(&cfg.Github{})
require.Error(t, err)
})
}
23 changes: 21 additions & 2 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,27 @@ func newWithGithubPAT(ctx context.Context, ghc *cfg.Github) (*GitHub, error) {
}, nil
}

// appPrivateKeyPEM returns the GitHub App private key PEM contents to use,
// preferring the in-memory app-privatekey flag over the on-disk
// app-privatekey-path. Providing either one satisfies the requirement; if
// neither is set an error is returned.
func appPrivateKeyPEM(ghc *cfg.Github) (string, error) {
if ghc.AppPrivatekey != "" {
return ghc.AppPrivatekey, nil
}
if len(ghc.AppPrivatekeyPath) > 0 {
return string(ghc.AppPrivatekeyPath), nil
}
return "", errors.New("github app authentication requires either --app-privatekey or --app-privatekey-path")
}

func newWithGithubApp(ctx context.Context, ghc *cfg.Github) (*GitHub, error) {
jwttoken, err := getJWTToken(ghc.AppId, string(ghc.AppPrivatekeyPath))
privateKey, err := appPrivateKeyPEM(ghc)
if err != nil {
return nil, err
}

jwttoken, err := getJWTToken(ghc.AppId, privateKey)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -382,7 +401,7 @@ func newWithGithubApp(ctx context.Context, ghc *cfg.Github) (*GitHub, error) {
},
&appJWTTokenRefresher{
appID: ghc.AppId,
privateKey: string(ghc.AppPrivatekeyPath),
privateKey: privateKey,
},
)
// Wrap the installation-token refresher in a refreshableTokenSource so the
Expand Down
Loading