Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Commit

Permalink
Redacted logs (#27)
Browse files Browse the repository at this point in the history
* redact secrets from logs

Change-Id: Ibffbceb807e61506e5518b599016e006bd0429ac

* appease golint

Change-Id: I68e72c5df188182230b7111756ec3d59c0bc3f38
  • Loading branch information
theganyo authored and robbrit committed Mar 30, 2018
1 parent 291d2a3 commit 2bf1293
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 19 deletions.
38 changes: 25 additions & 13 deletions apigee/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/apigee/istio-mixer-adapter/apigee/config"
"github.com/apigee/istio-mixer-adapter/apigee/product"
"github.com/apigee/istio-mixer-adapter/apigee/quota"
"github.com/apigee/istio-mixer-adapter/apigee/util"
analyticsT "github.com/apigee/istio-mixer-adapter/template/analytics"
"istio.io/istio/mixer/pkg/adapter"
"istio.io/istio/mixer/pkg/status"
Expand All @@ -40,11 +41,10 @@ import (
)

const (
encodedClaimsKey = "encoded_claims"
apiClaimsAttribute = "api_claims"
apiKeyAttribute = "api_key"
apiNameAttribute = "api"
pathAttribute = "path"
encodedClaimsKey = "encoded_claims"
apiKeyAttribute = "api_key"
apiNameAttribute = "api"
pathAttribute = "path"
)

type (
Expand Down Expand Up @@ -142,6 +142,12 @@ func (b *builder) SetAdapterConfig(cfg adapter.Config) {

// Implements adapter.HandlerBuilder
func (b *builder) Build(context context.Context, env adapter.Env) (adapter.Handler, error) {
redacts := []interface{}{
b.adapterConfig.Key,
b.adapterConfig.Secret,
}
redactedConfig := util.SprintfRedacts(redacts, "%#v", *b.adapterConfig)
env.Logger().Infof("Handler config: %#v", redactedConfig)

apigeeBase, err := url.Parse(b.adapterConfig.ApigeeBase)
if err != nil {
Expand Down Expand Up @@ -228,13 +234,12 @@ func (h *handler) Close() error {

// Handle processing and delivery of Analytics to Apigee
func (h *handler) HandleAnalytics(ctx context.Context, instances []*analyticsT.Instance) error {
h.Log().Infof("HandleAnalytics: %d instances", len(instances))

var authContext *auth.Context
var records []analytics.Record

for _, inst := range instances {
h.Log().Infof("HandleAnalytics: %#v", inst)

record := analytics.Record{
ClientReceivedStartTimestamp: timeToUnix(inst.ClientReceivedStartTimestamp),
ClientReceivedEndTimestamp: timeToUnix(inst.ClientReceivedStartTimestamp),
Expand Down Expand Up @@ -268,14 +273,16 @@ func (h *handler) HandleAnalytics(ctx context.Context, instances []*analyticsT.I

// Handle Authentication and Authorization - NEVER RETURN ERROR!
func (h *handler) HandleAuthorization(ctx context.Context, inst *authT.Instance) (adapter.CheckResult, error) {
h.Log().Infof("HandleAuthorization: Subject: %#v, Action: %#v", inst.Subject, inst.Action)
redacts := []interface{}{
inst.Subject.Properties[apiKeyAttribute],
inst.Subject.Properties[encodedClaimsKey],
}
redactedSub := util.SprintfRedacts(redacts, "%#v", *inst.Subject)
h.Log().Infof("HandleAuthorization: Subject: %s, Action: %#v", redactedSub, *inst.Action)

claims := resolveClaimsInterface(h.Log(), inst.Subject.Properties)

var apiKey string
if k, ok := inst.Subject.Properties[apiKeyAttribute]; ok {
apiKey = k.(string)
}
apiKey, _ := inst.Subject.Properties[apiKeyAttribute].(string)

authContext, err := h.authMan.Authenticate(h, apiKey, claims)
if err != nil {
Expand Down Expand Up @@ -312,7 +319,12 @@ func (h *handler) HandleAuthorization(ctx context.Context, inst *authT.Instance)

// Handle Quota checks - NEVER RETURN ERROR!
func (h *handler) HandleQuota(ctx context.Context, inst *quotaT.Instance, args adapter.QuotaArgs) (adapter.QuotaResult, error) {
h.Log().Infof("HandleQuota: %#v args: %v", inst, args)
redacts := []interface{}{
inst.Dimensions[apiKeyAttribute],
inst.Dimensions[encodedClaimsKey],
}
redactedInst := util.SprintfRedacts(redacts, "%#v", *inst)
h.Log().Infof("HandleQuota: %#v args: %v", redactedInst, args)

// skip < 0 to eliminate Istio prefetch returns
if args.QuotaAmount <= 0 {
Expand Down
15 changes: 11 additions & 4 deletions apigee/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package auth

import (
"github.com/apigee/istio-mixer-adapter/apigee/context"
"github.com/apigee/istio-mixer-adapter/apigee/util"
"istio.io/istio/mixer/pkg/adapter"
)

Expand Down Expand Up @@ -40,8 +41,12 @@ func (a *Manager) Close() {
// Authenticate constructs an Apigee context from an existing context and either
// a set of JWT claims, or an Apigee API key.
func (a *Manager) Authenticate(ctx context.Context, apiKey string, claims map[string]interface{}) (Context, error) {

ctx.Log().Infof("Authenticate: key: %v, claims: %v", apiKey, claims)
redacts := []interface{}{
claims["access_token"],
claims["client_id"],
}
redactedClaims := util.SprintfRedacts(redacts, "%#v", claims)
ctx.Log().Infof("Authenticate: key: %v, claims: %v", util.Truncate(apiKey, 5), redactedClaims)

var ac = Context{Context: ctx}
if claims != nil {
Expand All @@ -62,10 +67,12 @@ func (a *Manager) Authenticate(ctx context.Context, apiKey string, claims map[st

err = ac.setClaims(claims)

redacts = []interface{}{ac.AccessToken, ac.ClientID}
redactedAC := util.SprintfRedacts(redacts, "%v", ac)
if err == nil {
ctx.Log().Infof("Authenticate success: %v", ac)
ctx.Log().Infof("Authenticate success: %s", redactedAC)
} else {
ctx.Log().Infof("Authenticate error: %v [%v]", ac, err)
ctx.Log().Infof("Authenticate error: %s [%v]", redactedAC, err)
}
return ac, err
}
Expand Down
1 change: 0 additions & 1 deletion apigee/auth/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ func (a *Context) setClaims(claims map[string]interface{}) error {
if claims[clientIDClaim] == nil {
return nil
}
a.Log().Infof("setClaims: %v", claims)

products, err := parseArrayOfStrings(claims[apiProductListClaim])
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion apigee/auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ func (a *jwtManager) jwtKey(ctx context.Context, token *jwt.Token) (interface{},
}

func (a *jwtManager) verifyJWT(ctx context.Context, raw string) (jwt.MapClaims, error) {
ctx.Log().Infof("verifyJWT: %v", raw)
keyFunc := func(token *jwt.Token) (interface{}, error) {
return a.getJWTKey(ctx, token)
}
Expand Down
1 change: 1 addition & 0 deletions apigee/integrationtest/cloud_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/lestrrat/go-jwx/jwk"
)

// CloudMockHandler provides a set of Mocked cloud APIs for integration tests
func CloudMockHandler(t *testing.T) http.HandlerFunc {

privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
Expand Down
41 changes: 41 additions & 0 deletions apigee/util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
"fmt"
"strings"
)

// SprintfRedacts truncates secret strings to len(5)
func SprintfRedacts(redacts []interface{}, format string, a ...interface{}) string {
s := fmt.Sprintf(format, a)
for _, r := range redacts {
if r, ok := r.(string); ok {
truncated := Truncate(r, 5)
s = strings.Replace(s, r, truncated, -1)
}
}
return s
}

// Truncate truncates secret strings to arbitrary length and adds "..." as indication
func Truncate(in string, end int) string {
out := in
if len(out) > end {
out = fmt.Sprintf("%s...", out[0:end])
}
return out
}
70 changes: 70 additions & 0 deletions apigee/util/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
"strings"
"testing"

"istio.io/istio/mixer/template/authorization"
)

func TestSprintfRedacted(t *testing.T) {

superman := "Clark Kent"
batman := "Bruce Wayne"
ironman := "Tony Stark"

inst := &authorization.Instance{
Subject: &authorization.Subject{
Properties: map[string]interface{}{
"superman": superman,
"ironman": ironman,
"batman": batman,
},
},
}

redacts := []interface{}{superman, batman}
result := SprintfRedacts(redacts, "%#v", *inst.Subject)

if strings.Contains(result, superman) {
t.Errorf("should not contain %s, got: %s", superman, result)
}
if strings.Contains(result, batman) {
t.Errorf("should not contain %s, got: %s", batman, result)
}
if !strings.Contains(result, ironman) {
t.Errorf("should contain %s, got: %s", ironman, result)
}
}

func TestTruncate(t *testing.T) {
for _, ea := range []struct {
in string
end int
want string
}{
{"hello world", 5, "hello..."},
{"hello", 5, "hello"},
{"he", 5, "he"},
} {
t.Logf("in: '%s' end: %d", ea.in, ea.end)
got := Truncate(ea.in, 5)
if got != ea.want {
t.Errorf("want: '%s', got: '%s'", ea.want, got)
}
}
}

0 comments on commit 2bf1293

Please sign in to comment.