Skip to content

Commit

Permalink
feat: make it possible to read secrets from files
Browse files Browse the repository at this point in the history
  • Loading branch information
tboerger committed Oct 8, 2023
1 parent ce9d0be commit 142e201
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 33 deletions.
12 changes: 12 additions & 0 deletions changelog/unreleased/file-secrets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Change: Read secrets form files

We have added proper support to load secrets like the token or the private key
for app authentication from files or from base64-encoded strings. Just provide
the flags or environment variables for token or private key with a DSN formatted
string like `file://path/to/file` or `base64://Zm9vYmFy`.

Since the private key for GitHub App authentication had been provided in
base64-encoded format this is a breaking change as this won't work anymore until
you prefix the value with `base64://`.

https://github.com/promhippie/github_exporter/pull/245
21 changes: 21 additions & 0 deletions docs/content/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ directly at [http://localhost:9504/metrics](http://localhost:9504/metrics):
- GITHUB_EXPORTER_REPO=promhippie/example
{{< / highlight >}}

It's also possible to provide the token to access the GitHub API gets provided
by a file, in case you are using some kind of secret provider. For this use case
you can write the token to a file on any path and reference it with the
following format:

{{< highlight diff >}}
github-exporter:
image: promhippie/github-exporter:latest
restart: always
environment:
- - GITHUB_EXPORTER_TOKEN=bldyecdtysdahs76ygtbw51w3oeo6a4cvjwoitmb
+ - GITHUB_EXPORTER_TOKEN=file://path/to/secret/file/with/token
- GITHUB_EXPORTER_LOG_PRETTY=true
- GITHUB_EXPORTER_ORG=promhippie
- GITHUB_EXPORTER_REPO=promhippie/example
{{< / highlight >}}

Besides the `file://` format we currently also support `base64://` which expects
the token in a base64 encoded format. This functionality can be used for the
token and the private key for GitHub App authentication so far.

If you want to collect the metrics of all repositories within an organization
you are able to use globbing, but be aware that all repositories matched by
globbing won't provide metrics for the number of subscribers, the number of
Expand Down
16 changes: 16 additions & 0 deletions pkg/action/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package action

// boolP returns a boolean pointer.
func boolP(i bool) *bool {
return &i
}

// stringP returns a string pointer.
func stringP(i string) *string {
return &i
}

// slceP returns a slice pointer.
func sliceP(i []string) *[]string {
return &i
}
59 changes: 28 additions & 31 deletions pkg/action/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package action
import (
"context"
"crypto/tls"
"encoding/base64"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -233,30 +232,6 @@ func handler(cfg *config.Config, logger log.Logger, client *github.Client) *chi.
return mux
}

func boolP(i bool) *bool {
return &i
}

func stringP(i string) *string {
return &i
}

func sliceP(i []string) *[]string {
return &i
}

func contentOrDecode(file string) ([]byte, error) {
decoded, err := base64.StdEncoding.DecodeString(
file,
)

if err != nil {
return os.ReadFile(file)
}

return decoded, nil
}

func useEnterprise(cfg *config.Config, _ log.Logger) bool {
return cfg.Target.BaseURL != ""
}
Expand All @@ -271,7 +246,7 @@ func getClient(cfg *config.Config, logger log.Logger) (*github.Client, error) {
}

if useApplication(cfg, logger) {
privateKey, err := contentOrDecode(cfg.Target.PrivateKey)
privateKey, err := config.Value(cfg.Target.PrivateKey)

if err != nil {
level.Error(logger).Log(
Expand All @@ -286,7 +261,7 @@ func getClient(cfg *config.Config, logger log.Logger) (*github.Client, error) {
http.DefaultTransport,
cfg.Target.AppID,
cfg.Target.InstallID,
privateKey,
[]byte(privateKey),
)

if err != nil {
Expand All @@ -305,12 +280,23 @@ func getClient(cfg *config.Config, logger log.Logger) (*github.Client, error) {
), nil
}

accessToken, err := config.Value(cfg.Target.Token)

if err != nil {
level.Error(logger).Log(
"msg", "Failed to read token",
"err", err,
)

return nil, err
}

return github.NewClient(
oauth2.NewClient(
context.Background(),
oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: cfg.Target.Token,
AccessToken: accessToken,
},
),
),
Expand All @@ -319,7 +305,7 @@ func getClient(cfg *config.Config, logger log.Logger) (*github.Client, error) {

func getEnterprise(cfg *config.Config, logger log.Logger) (*github.Client, error) {
if useApplication(cfg, logger) {
privateKey, err := contentOrDecode(cfg.Target.PrivateKey)
privateKey, err := config.Value(cfg.Target.PrivateKey)

if err != nil {
level.Error(logger).Log(
Expand All @@ -334,7 +320,7 @@ func getEnterprise(cfg *config.Config, logger log.Logger) (*github.Client, error
http.DefaultTransport,
cfg.Target.AppID,
cfg.Target.InstallID,
privateKey,
[]byte(privateKey),
)

if err != nil {
Expand Down Expand Up @@ -374,6 +360,17 @@ func getEnterprise(cfg *config.Config, logger log.Logger) (*github.Client, error
return client, err
}

accessToken, err := config.Value(cfg.Target.Token)

if err != nil {
level.Error(logger).Log(
"msg", "Failed to read token",
"err", err,
)

return nil, err
}

client, err := github.NewClient(
oauth2.NewClient(
context.WithValue(
Expand All @@ -389,7 +386,7 @@ func getEnterprise(cfg *config.Config, logger log.Logger) (*github.Client, error
),
oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: cfg.Target.Token,
AccessToken: accessToken,
},
),
),
Expand Down
4 changes: 2 additions & 2 deletions pkg/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func RootFlags(cfg *config.Config) []cli.Flag {
&cli.StringFlag{
Name: "github.token",
Value: "",
Usage: "Access token for the GitHub API",
Usage: "Access token for the GitHub API, also supports file:// and base64://",
EnvVars: []string{"GITHUB_EXPORTER_TOKEN"},
Destination: &cfg.Target.Token,
},
Expand All @@ -131,7 +131,7 @@ func RootFlags(cfg *config.Config) []cli.Flag {
&cli.StringFlag{
Name: "github.private_key",
Value: "",
Usage: "Private key for the GitHub app, path or base64-encoded",
Usage: "Private key for the GitHub app, also supports file:// and base64://",
EnvVars: []string{"GITHUB_EXPORTER_PRIVATE_KEY"},
Destination: &cfg.Target.PrivateKey,
},
Expand Down
33 changes: 33 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package config

import (
"encoding/base64"
"fmt"
"os"
"strings"
"time"

"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -65,3 +69,32 @@ type Config struct {
func Load() *Config {
return &Config{}
}

// Value returns the config value based on a DSN.
func Value(val string) (string, error) {
if strings.HasPrefix(val, "file://") {
content, err := os.ReadFile(
strings.TrimPrefix(val, "file://"),
)

if err != nil {
return "", fmt.Errorf("failed to parse secret file: %w", err)
}

return string(content), nil
}

if strings.HasPrefix(val, "base64://") {
content, err := base64.StdEncoding.DecodeString(
strings.TrimPrefix(val, "base64://"),
)

if err != nil {
return "", fmt.Errorf("failed to parse base64 value: %w", err)
}

return string(content), nil
}

return val, nil
}

0 comments on commit 142e201

Please sign in to comment.