Skip to content

Commit

Permalink
feat: allow flexible options for github app secret and metrics (#412)
Browse files Browse the repository at this point in the history
feat: allow flexible options for github app secret and metrics

allows for the GitHub app secret to be loaded from GCPKMS as the
default, but also now allows envvars and files to be utilized. Vaulting
mechanisms are used in our case, however they're loaded a bit
differently.

allows for the metrics specific to chainguard to be optionally disabled.
the default is to allow the chainguard default metrics.

---------

Signed-off-by: Karl Haworth <[email protected]>
Co-authored-by: Billy Lynch <[email protected]>
  • Loading branch information
karlhaworth and wlynch authored Jul 29, 2024
1 parent 11cf87b commit 4a23eac
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 45 deletions.
45 changes: 18 additions & 27 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,49 @@ import (
"context"
"log"
"log/slog"
"net/http"
"os"
"os/signal"

"chainguard.dev/go-grpc-kit/pkg/duplex"
pboidc "chainguard.dev/sdk/proto/platform/oidc/v1"
kms "cloud.google.com/go/kms/apiv1"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/chainguard-dev/clog"
metrics "github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics"
mce "github.com/chainguard-dev/terraform-infra-common/pkg/httpmetrics/cloudevents"
"github.com/kelseyhightower/envconfig"
"github.com/octo-sts/app/pkg/gcpkms"
envConfig "github.com/octo-sts/app/pkg/envconfig"
"github.com/octo-sts/app/pkg/ghtransport"
"github.com/octo-sts/app/pkg/octosts"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

type envConfig struct {
Port int `envconfig:"PORT" required:"true"`
Domain string `envconfig:"STS_DOMAIN" required:"true"`
KMSKey string `envconfig:"KMS_KEY" required:"true"`
AppID int64 `envconfig:"GITHUB_APP_ID" required:"true"`
EventingIngress string `envconfig:"EVENT_INGRESS_URI" required:"true"`
}

func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
ctx = clog.WithLogger(ctx, clog.New(slog.Default().Handler()))

go metrics.ServeMetrics()

// Setup tracing.
defer metrics.SetupTracer(ctx)()

var env envConfig
if err := envconfig.Process("", &env); err != nil {
env, err := envConfig.Process()
if err != nil {
log.Panicf("failed to process env var: %s", err)
}

client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
log.Panicf("could not create kms client: %v", err)
if env.Metrics {
go metrics.ServeMetrics()

// Setup tracing.
defer metrics.SetupTracer(ctx)()
}

signer, err := gcpkms.New(ctx, client, env.KMSKey)
if err != nil {
log.Panicf("error creating signer: %v", err)
var client *kms.KeyManagementClient

if env.KMSKey != "" {
client, err = kms.NewKeyManagementClient(ctx)
if err != nil {
log.Panicf("could not create kms client: %v", err)
}
}

atr, err := ghinstallation.NewAppsTransportWithOptions(http.DefaultTransport, env.AppID, ghinstallation.WithSigner(signer))
atr, err := ghtransport.New(ctx, env, client)
if err != nil {
log.Panicf("error creating GitHub App transport: %v", err)
}
Expand All @@ -76,7 +67,7 @@ func main() {
log.Panicf("failed to create cloudevents client: %v", err)
}

pboidc.RegisterSecurityTokenServiceServer(d.Server, octosts.NewSecurityTokenServiceServer(atr, ceclient, env.Domain))
pboidc.RegisterSecurityTokenServiceServer(d.Server, octosts.NewSecurityTokenServiceServer(atr, ceclient, env.Domain, env.Metrics))
if err := d.RegisterHandler(ctx, pboidc.RegisterSecurityTokenServiceHandlerFromEndpoint); err != nil {
log.Panicf("failed to register gateway endpoint: %v", err)
}
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ require (
sigs.k8s.io/yaml v1.4.0
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
cloud.google.com/go/auth v0.2.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.0 // indirect
Expand Down Expand Up @@ -56,6 +62,7 @@ require (
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/stretchr/testify v1.9.0
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 // indirect
Expand Down
41 changes: 41 additions & 0 deletions pkg/envconfig/envconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package envconfig

import (
"errors"

"github.com/kelseyhightower/envconfig"
)

type EnvConfig struct {
Port int `envconfig:"PORT" required:"true"`
Domain string `envconfig:"STS_DOMAIN" required:"true"`
KMSKey string `envconfig:"KMS_KEY" required:"false"`
AppID int64 `envconfig:"GITHUB_APP_ID" required:"true"`
EventingIngress string `envconfig:"EVENT_INGRESS_URI" required:"true"`
AppSecretCertificateFile string `envconfig:"APP_SECRET_CERTIFICATE_FILE" required:"false"`
AppSecretCertificateEnvVar string `envconfig:"APP_SECRET_CERTIFICATE_ENV_VAR" required:"false"`
Metrics bool `envconfig:"METRICS" required:"false" default:"true"`
}

func Process() (*EnvConfig, error) {
cfg := new(EnvConfig)
var err error
if err = envconfig.Process("", cfg); err != nil {
return nil, err
}

kmsSet := false
for _, v := range []string{cfg.KMSKey, cfg.AppSecretCertificateFile, cfg.AppSecretCertificateEnvVar} {
if v != "" {
if kmsSet {
return nil, errors.New("only one of KMS_KEY, APP_SECRET_CERTIFICATE_FILE, APP_SECRET_CERTIFICATE_ENV_VAR may be set")
}
kmsSet = true
}
}

return cfg, err
}
102 changes: 102 additions & 0 deletions pkg/envconfig/envconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package envconfig

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestProcess(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
wantErr bool
}{
{
name: "No environment variables set",
envVars: map[string]string{
"PORT": "8080",
"STS_DOMAIN": "",
"GITHUB_APP_ID": "1234",
"EVENT_INGRESS_URI": "",
"KMS_KEY": "",
"APP_SECRET_CERTIFICATE_FILE": "",
"APP_SECRET_CERTIFICATE_ENVVAR": "",
},
wantErr: false,
},
{
name: "Only KMS_KEY set",
envVars: map[string]string{
"PORT": "8080",
"STS_DOMAIN": "",
"GITHUB_APP_ID": "1234",
"EVENT_INGRESS_URI": "",
"KMS_KEY": "some-kms-key",
"APP_SECRET_CERTIFICATE_FILE": "",
"APP_SECRET_CERTIFICATE_ENVVAR": "",
},
wantErr: false,
},
{
name: "Only APP_SECRET_CERTIFICATE_FILE set",
envVars: map[string]string{
"PORT": "8080",
"STS_DOMAIN": "",
"GITHUB_APP_ID": "1234",
"EVENT_INGRESS_URI": "",
"KMS_KEY": "",
"APP_SECRET_CERTIFICATE_FILE": "some-file-path",
"APP_SECRET_CERTIFICATE_ENVVAR": "",
},
wantErr: false,
},
{
name: "Only APP_SECRET_CERTIFICATE_ENVVAR set",
envVars: map[string]string{
"PORT": "8080",
"STS_DOMAIN": "",
"GITHUB_APP_ID": "1234",
"EVENT_INGRESS_URI": "",
"KMS_KEY": "",
"APP_SECRET_CERTIFICATE_FILE": "",
"APP_SECRET_CERTIFICATE_ENVVAR": "some-env-var",
},
wantErr: false,
},
{
name: "Multiple variables set",
envVars: map[string]string{
"PORT": "8080",
"STS_DOMAIN": "",
"GITHUB_APP_ID": "1234",
"EVENT_INGRESS_URI": "",
"KMS_KEY": "some-kms-key",
"APP_SECRET_CERTIFICATE_FILE": "some-file-path",
"APP_SECRET_CERTIFICATE_ENVVAR": "",
},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for key, value := range tt.envVars {
t.Setenv(key, value)
}

cfg, err := Process()

if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, cfg)
} else {
assert.NoError(t, err)
assert.NotNil(t, cfg)
}
})
}
}
51 changes: 51 additions & 0 deletions pkg/ghtransport/ghtransport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 Chainguard, Inc.
// SPDX-License-Identifier: Apache-2.0

package ghtransport

import (
"context"
"fmt"
"net/http"

kms "cloud.google.com/go/kms/apiv1"
"github.com/bradleyfalzon/ghinstallation/v2"
envConfig "github.com/octo-sts/app/pkg/envconfig"
"github.com/octo-sts/app/pkg/gcpkms"
)

func New(ctx context.Context, env *envConfig.EnvConfig, kmsClient *kms.KeyManagementClient) (*ghinstallation.AppsTransport, error) {
switch {
case env.AppSecretCertificateEnvVar != "":
atr, err := ghinstallation.NewAppsTransport(http.DefaultTransport, env.AppID, []byte(env.AppSecretCertificateEnvVar))

if err != nil {
return nil, err
}
return atr, nil

case env.AppSecretCertificateFile != "":
atr, err := ghinstallation.NewAppsTransportKeyFromFile(http.DefaultTransport, env.AppID, env.AppSecretCertificateFile)

if err != nil {
return nil, err
}
return atr, nil
default:
if env.KMSKey == "" {
return nil, fmt.Errorf("failed to process env var: %q", env.KMSKey)
}

signer, err := gcpkms.New(ctx, kmsClient, env.KMSKey)
if err != nil {
return nil, fmt.Errorf("error creating signer: %w", err)
}

atr, err := ghinstallation.NewAppsTransportWithOptions(http.DefaultTransport, env.AppID, ghinstallation.WithSigner(signer))
if err != nil {
return nil, err
}

return atr, nil
}
}
Loading

0 comments on commit 4a23eac

Please sign in to comment.