Skip to content

Commit

Permalink
Implement secret redacter for printing snapshots (#1686)
Browse files Browse the repository at this point in the history
* Implement secret redacter for printing snapshots
  • Loading branch information
GrahamGoudeau authored and soloio-bulldozer[bot] committed Nov 13, 2019
1 parent 101fcc3 commit 1476698
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 5 deletions.
6 changes: 1 addition & 5 deletions Gopkg.lock

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

4 changes: 4 additions & 0 deletions changelog/v0.21.1/secret-redacter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
changelog:
- type: NEW_FEATURE
description: Implement a utility to print snapshot content with the Secret content redacted
issueLink: https://github.com/solo-io/gloo/issues/1679
55 changes: 55 additions & 0 deletions pkg/utils/syncutil/log_redacter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package syncutil

import (
"fmt"
"reflect"
"strings"

v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
)

const Redacted = "[REDACTED]"

// stringify the contents of the snapshot
//
// NOTE that if any of the top-level fields of the snapshot is a SecretList, then the secrets will be
// stringified by printing just their name and namespace, and "REDACTED" for their data. Secrets may
// contain sensitive data like TLS private keys, so be sure to use this whenever you'd like to
// stringify a snapshot rather than Go's %v formatter
func StringifySnapshot(snapshot interface{}) string {
snapshotStruct := reflect.ValueOf(snapshot).Elem()
stringBuilder := strings.Builder{}

for i := 0; i < snapshotStruct.NumField(); i++ {
fieldName := snapshotStruct.Type().Field(i).Name
fieldValue := snapshotStruct.Field(i).Interface()

stringBuilder.Write([]byte(fieldName))
stringBuilder.Write([]byte(":"))

if secretList, ok := fieldValue.(v1.SecretList); ok {
stringBuilder.Write([]byte("["))

var redactedSecrets []string
secretList.Each(func(s *v1.Secret) {
redactedSecret := fmt.Sprintf(
"%v{name: %s namespace: %s data: %s}",
reflect.TypeOf(s),
s.Metadata.Name,
s.Metadata.Namespace,
Redacted,
)

redactedSecrets = append(redactedSecrets, redactedSecret)
})

stringBuilder.Write([]byte(strings.Join(redactedSecrets, " '")))
stringBuilder.Write([]byte("]"))
} else {
stringBuilder.Write([]byte(fmt.Sprintf("%v", fieldValue)))
}
stringBuilder.Write([]byte("\n"))
}

return stringBuilder.String()
}
54 changes: 54 additions & 0 deletions pkg/utils/syncutil/log_redacter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package syncutil_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/solo-io/gloo/pkg/utils/syncutil"
v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
"github.com/solo-io/solo-kit/pkg/api/v1/resources/core"
)

var _ = Describe("Log Redacter", func() {
var (
secretName = "my-test-secret"
secretNamespace = "my-secret-namespace"
privateKey = "RSA PRIVATE KEY CONTENT"

noSecretsSnapshot = &v1.SetupSnapshot{
Settings: []*v1.Settings{{
Metadata: core.Metadata{
Name: "settings",
Namespace: "ns",
},
}},
}
snapshotWithSecrets = &v1.ApiSnapshot{
Endpoints: []*v1.Endpoint{{
Metadata: core.Metadata{
Name: "endpoint",
Namespace: "ns",
},
}},
Secrets: []*v1.Secret{{
Kind: &v1.Secret_Tls{Tls: &v1.TlsSecret{
PrivateKey: privateKey,
}},
Metadata: core.Metadata{
Name: secretName,
Namespace: secretNamespace,
},
}},
}
)

It("does not redact anything when no secrets", func() {
Expect(syncutil.StringifySnapshot(noSecretsSnapshot)).NotTo(ContainSubstring(syncutil.Redacted))
})

It("contains redacted content when secrets are present", func() {
s := syncutil.StringifySnapshot(snapshotWithSecrets)

Expect(s).To(ContainSubstring(syncutil.Redacted))
Expect(s).NotTo(ContainSubstring(privateKey))
})
})
13 changes: 13 additions & 0 deletions pkg/utils/syncutil/syncutil_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package syncutil_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestSyncUtils(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Sync Utils Suite")
}
9 changes: 9 additions & 0 deletions projects/clusteringress/pkg/translator/translator_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"time"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

v1alpha1 "github.com/solo-io/gloo/projects/clusteringress/pkg/api/external/knative"
v1 "github.com/solo-io/gloo/projects/clusteringress/pkg/api/v1"
"github.com/solo-io/gloo/projects/gateway/pkg/utils"
Expand Down Expand Up @@ -47,6 +50,12 @@ func (s *translatorSyncer) Sync(ctx context.Context, snap *v1.TranslatorSnapshot
)
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

proxy, err := translateProxy(ctx, s.writeNamespace, snap)
if err != nil {
logger.Warnf("snapshot %v was rejected due to invalid config: %v\n"+
Expand Down
9 changes: 9 additions & 0 deletions projects/discovery/pkg/fds/syncer/discovery_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package syncer
import (
"context"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

"github.com/solo-io/gloo/projects/discovery/pkg/fds"
v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
"github.com/solo-io/go-utils/contextutils"
Expand All @@ -28,6 +31,12 @@ func (s *syncer) Sync(ctx context.Context, snap *v1.DiscoverySnapshot) error {
logger.Infof("begin sync %v (%v upstreams)", snap.Hash(), len(snap.Upstreams))
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

upstreamsToDetect := filterUpstreamsForDiscovery(s.fdsMode, snap.Upstreams, snap.Kubenamespaces)

return s.fd.Update(upstreamsToDetect, snap.Secrets)
Expand Down
9 changes: 9 additions & 0 deletions projects/discovery/pkg/uds/syncer/discovery_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package syncer
import (
"context"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

"time"

v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
Expand All @@ -29,6 +32,12 @@ func (s *syncer) Sync(ctx context.Context, snap *v1.DiscoverySnapshot) error {
logger.Infof("begin sync %v (%v upstreams)", snap.Hash(), len(snap.Upstreams))
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

// kick the uds, ensure that desired upstreams are in sync
return s.uds.Resync(ctx)
}
9 changes: 9 additions & 0 deletions projects/gateway/pkg/syncer/translator_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"fmt"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

"go.uber.org/zap"

v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1"
Expand Down Expand Up @@ -58,6 +61,12 @@ func (s *translatorSyncer) Sync(ctx context.Context, snap *v2.ApiSnapshot) error
len(snap.VirtualServices), len(snap.Gateways), len(snap.RouteTables))
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

labels := map[string]string{
"created_by": "gateway-v2",
}
Expand Down
9 changes: 9 additions & 0 deletions projects/gloo/pkg/discovery/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"time"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
"github.com/solo-io/go-utils/contextutils"
"github.com/solo-io/solo-kit/pkg/api/v1/clients"
Expand All @@ -30,6 +33,12 @@ func (s *syncer) Sync(ctx context.Context, snap *v1.EdsSnapshot) error {
logger.Infof("begin sync %v (%v upstreams)", snap.Hash(), len(snap.Upstreams))
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

opts := clients.WatchOpts{
Ctx: ctx,
RefreshRate: s.refreshRate,
Expand Down
8 changes: 8 additions & 0 deletions projects/gloo/pkg/syncer/envoy_translator_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"github.com/gorilla/mux"
"github.com/solo-io/gloo/pkg/utils/syncutil"
v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
"github.com/solo-io/gloo/projects/gloo/pkg/plugins"
"github.com/solo-io/gloo/projects/gloo/pkg/translator"
Expand All @@ -20,6 +21,7 @@ import (
"go.opencensus.io/tag"
"go.opencensus.io/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

var (
Expand Down Expand Up @@ -56,6 +58,12 @@ func (s *translatorSyncer) syncEnvoy(ctx context.Context, snap *v1.ApiSnapshot)
len(snap.Proxies), len(snap.Upstreams), len(snap.Endpoints), len(snap.Secrets), len(snap.Artifacts), len(snap.AuthConfigs))
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

allReports := make(reporter.ResourceReports)
allReports.Accept(snap.Upstreams.AsInputResources()...)
allReports.Accept(snap.UpstreamGroups.AsInputResources()...)
Expand Down
9 changes: 9 additions & 0 deletions projects/ingress/pkg/status/status_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"net"
"sort"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

"github.com/gogo/protobuf/proto"
"github.com/pkg/errors"
"github.com/solo-io/gloo/projects/ingress/pkg/api/ingress"
Expand Down Expand Up @@ -37,6 +40,12 @@ func (s *statusSyncer) Sync(ctx context.Context, snap *v1.StatusSnapshot) error
defer logger.Infof("end sync %v", snap.Hash())
services := snap.Services

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

lbStatus, err := getLbStatus(services)
if err != nil {
return err
Expand Down
9 changes: 9 additions & 0 deletions projects/ingress/pkg/translator/translator_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package translator
import (
"context"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

"github.com/solo-io/gloo/projects/gateway/pkg/utils"
gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
v1 "github.com/solo-io/gloo/projects/ingress/pkg/api/v1"
Expand Down Expand Up @@ -39,6 +42,12 @@ func (s *translatorSyncer) Sync(ctx context.Context, snap *v1.TranslatorSnapshot
len(snap.Ingresses))
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

proxy, err := translateProxy(s.writeNamespace, snap, s.requireIngressClass)
if err != nil {
logger.Warnf("snapshot %v was rejected due to invalid config: %v\n"+
Expand Down
9 changes: 9 additions & 0 deletions projects/knative/pkg/translator/translator_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"time"

"github.com/solo-io/gloo/pkg/utils/syncutil"
"go.uber.org/zap/zapcore"

"golang.org/x/sync/errgroup"

"github.com/solo-io/gloo/projects/gateway/pkg/utils"
Expand Down Expand Up @@ -56,6 +59,12 @@ func (s *translatorSyncer) Sync(ctx context.Context, snap *v1.TranslatorSnapshot
)
defer logger.Infof("end sync %v", snap.Hash())

// stringifying the snapshot may be an expensive operation, so we'd like to avoid building the large
// string if we're not even going to log it anyway
if contextutils.GetLogLevel() == zapcore.DebugLevel {
logger.Debug(syncutil.StringifySnapshot(snap))
}

// split ingresses by their visibility, create a proxy for each
var externalIngresses, internalIngresses v1alpha1.IngressList

Expand Down

0 comments on commit 1476698

Please sign in to comment.