Skip to content

Commit 4738485

Browse files
committed
fix: show unsalted hash in error messages
1 parent 9080422 commit 4738485

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

server/remote_cache/chunking/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ go_library(
1010
"//proto:resource_go_proto",
1111
"//server/interfaces",
1212
"//server/remote_cache/digest",
13+
"//server/util/log",
1314
"//server/util/proto",
1415
"//server/util/status",
1516
"@com_github_buildbuddy_io_fastcdc2020//fastcdc",
17+
"@org_golang_google_grpc//status",
1618
"@org_golang_x_sync//errgroup",
1719
],
1820
)

server/remote_cache/chunking/chunking.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import (
1414

1515
"github.com/buildbuddy-io/buildbuddy/server/interfaces"
1616
"github.com/buildbuddy-io/buildbuddy/server/remote_cache/digest"
17+
"github.com/buildbuddy-io/buildbuddy/server/util/log"
1718
"github.com/buildbuddy-io/buildbuddy/server/util/proto"
1819
"github.com/buildbuddy-io/buildbuddy/server/util/status"
1920
"github.com/buildbuddy-io/fastcdc2020/fastcdc"
2021
"golang.org/x/sync/errgroup"
22+
gstatus "google.golang.org/grpc/status"
2123

2224
repb "github.com/buildbuddy-io/buildbuddy/proto/remote_execution"
2325
rspb "github.com/buildbuddy-io/buildbuddy/proto/resource"
@@ -270,7 +272,14 @@ func (cm *Manifest) Store(ctx context.Context, cache interfaces.Cache) error {
270272
return err
271273
}
272274

273-
return cache.Set(ctx, acRNProto, arBytes)
275+
if err := cache.Set(ctx, acRNProto, arBytes); err != nil {
276+
err = sanitizeManifestError(err, acRNProto.GetDigest().GetHash(), cm.BlobDigest.GetHash())
277+
if status.IsInternalError(err) {
278+
log.CtxInfof(ctx, "Failed to set chunking manifest (blob %s, manifest key %s): %v", cm.BlobDigest.GetHash(), acRNProto.GetDigest().GetHash(), err)
279+
}
280+
return err
281+
}
282+
return nil
274283
}
275284

276285
// LoadManifest retrieves a chunked manifest from the cache. It does NOT validate existence of the chunks.
@@ -282,6 +291,10 @@ func LoadManifest(ctx context.Context, cache interfaces.Cache, blobDigest *repb.
282291

283292
arBytes, err := cache.Get(ctx, acRNProto)
284293
if err != nil {
294+
err = sanitizeManifestError(err, acRNProto.GetDigest().GetHash(), blobDigest.GetHash())
295+
if status.IsInternalError(err) {
296+
log.CtxInfof(ctx, "Failed to get chunking manifest (blob %s, manifest key %s): %v", blobDigest.GetHash(), acRNProto.GetDigest().GetHash(), err)
297+
}
285298
if status.IsNotFoundError(err) {
286299
blobRN := digest.NewCASResourceName(blobDigest, instanceName, digestFunction).ToProto()
287300
if exists, existsErr := cache.Contains(ctx, blobRN); existsErr != nil {
@@ -385,6 +398,28 @@ func acResourceName(blobDigest *repb.Digest, instanceName string, digestFunction
385398
return acRN.ToProto(), nil
386399
}
387400

401+
// sanitizeManifestError replaces the salted AC key hash with the original blob hash in
402+
// the error message, so callers see the blob digest rather than the internal salted key.
403+
// The gRPC status code is preserved.
404+
func sanitizeManifestError(err error, saltedHash, blobHash string) error {
405+
if err == nil || saltedHash == "" || saltedHash == blobHash {
406+
return err
407+
}
408+
grpcStatus, ok := gstatus.FromError(err)
409+
if !ok {
410+
return fmt.Errorf("%s", sanitizeMsg(err.Error(), saltedHash, blobHash))
411+
}
412+
return gstatus.Error(grpcStatus.Code(), sanitizeMsg(grpcStatus.Message(), saltedHash, blobHash))
413+
}
414+
415+
func sanitizeMsg(msg, saltedHash, blobHash string) string {
416+
replacement := fmt.Sprintf("manifestKey(%s)", blobHash)
417+
if replaced := strings.ReplaceAll(msg, saltedHash, replacement); replaced != msg {
418+
return replaced
419+
}
420+
return fmt.Sprintf("%s (blob %s)", msg, blobHash)
421+
}
422+
388423
func DigestsSummary(digests []*repb.Digest) string {
389424
const maxShown = 3
390425
strs := digestsStrings(digests...)

server/remote_cache/chunking/chunking_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,29 @@ func TestLoadWithoutManifest_BlobMissing(t *testing.T) {
213213
require.True(t, status.IsNotFoundError(err))
214214
}
215215

216+
func TestLoadWithoutManifest_SaltedHashNotLeaked(t *testing.T) {
217+
const salt = "test-salt"
218+
flags.Set(t, "cache.chunking.ac_key_salt", salt)
219+
220+
ctx := context.Background()
221+
te := testenv.GetTestEnv(t)
222+
ctx, err := prefix.AttachUserPrefixToContext(ctx, te.GetAuthenticator())
223+
require.NoError(t, err)
224+
225+
blobRN, _ := testdigest.RandomCASResourceBuf(t, 500)
226+
blobDigest := blobRN.GetDigest()
227+
228+
saltedDigest, err := digest.Compute(bytes.NewReader([]byte(salt+":"+blobDigest.GetHash())), repb.DigestFunction_SHA256)
229+
require.NoError(t, err)
230+
231+
_, err = chunking.LoadManifest(ctx, te.GetCache(), blobDigest, "", repb.DigestFunction_SHA256)
232+
233+
require.Error(t, err)
234+
require.True(t, status.IsNotFoundError(err))
235+
assert.Contains(t, err.Error(), blobDigest.GetHash())
236+
assert.NotContains(t, err.Error(), saltedDigest.GetHash())
237+
}
238+
216239
func TestStore_ErrGroupContextCancellation(t *testing.T) {
217240
ctx := context.Background()
218241
te := testenv.GetTestEnv(t)

0 commit comments

Comments
 (0)