Skip to content

Commit a1cffa7

Browse files
redact user-defined secrets in remote & workflow logs (#11446)
## Summary - redact user-defined secret values in CI runner logs for `bb remote` and workflows - pass secret env var names from execution server to CI runner via `BUILDBUDDY_SECRET_ENV_VAR_NAMES` - extend redaction logic to apply dynamic value redaction (`RedactTextWithValues`) - add failing-then-passing integration coverage in `TestAccessingSecrets` by echoing a user-defined secret and asserting `<REDACTED>` - add unit tests for `RedactTextWithValues`, including overlap handling (e.g. `abc` vs `abcdef`) to verify longest-first replacement behavior - centralize CI runner env-var contract constants in `enterprise/server/util/ci_runner_env` ## Validation - `bb remote test //enterprise/server/test/integration/remote_bazel:remote_bazel_test --config=remote --test_filter=TestAccessingSecrets --test_output=all --nocache_test_results` - `bb remote test //server/util/redact:redact_test --config=remote --test_filter=TestRedactTextWithValues --test_output=all --nocache_test_results` Test invocations: - https://app.buildbuddy.io/invocation/d8af9aca-d126-408d-bda4-a62f26cd2522 - https://app.buildbuddy.io/invocation/1b09c525-6b2e-4b4d-8cd7-20c477ce687d --------- Co-authored-by: Shelley <shelley@exe.dev>
1 parent 36f1665 commit a1cffa7

File tree

15 files changed

+170
-22
lines changed

15 files changed

+170
-22
lines changed

enterprise/server/cmd/ci_runner/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ go_library(
1515
importpath = "github.com/buildbuddy-io/buildbuddy/enterprise/server/cmd/ci_runner",
1616
deps = [
1717
"//enterprise/server/bes_artifacts",
18+
"//enterprise/server/util/ci_runner_env",
1819
"//enterprise/server/workflow/config",
1920
"//proto:build_event_stream_go_proto",
2021
"//proto:command_line_go_proto",

enterprise/server/cmd/ci_runner/main.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/buildbuddy-io/buildbuddy/enterprise/server/bes_artifacts"
26+
"github.com/buildbuddy-io/buildbuddy/enterprise/server/util/ci_runner_env"
2627
"github.com/buildbuddy-io/buildbuddy/enterprise/server/workflow/config"
2728
"github.com/buildbuddy-io/buildbuddy/server/build_event_publisher"
2829
"github.com/buildbuddy-io/buildbuddy/server/real_environment"
@@ -107,9 +108,8 @@ const (
107108
// Env vars set by workflow runner
108109
// NOTE: These env vars are not populated for non-private repos.
109110

110-
buildbuddyAPIKeyEnvVarName = "BUILDBUDDY_API_KEY"
111-
repoUserEnvVarName = "REPO_USER"
112-
repoTokenEnvVarName = "REPO_TOKEN"
111+
repoUserEnvVarName = "REPO_USER"
112+
repoTokenEnvVarName = "REPO_TOKEN"
113113

114114
// Exit code placeholder used when a command doesn't return an exit code on its own.
115115
noExitCode = -1
@@ -142,7 +142,7 @@ const (
142142
ansiGray = "\033[90m"
143143
ansiReset = "\033[0m"
144144

145-
clientIdentityEnvVar = "BB_GRPC_CLIENT_IDENTITY"
145+
clientIdentityEnvVar = ci_runner_env.BBGrpcClientIdentityEnvVarName
146146

147147
// We save the startup options used for the last executed bazel command so we can apply
148148
// them on future bazel commands without restarting the Bazel server.
@@ -313,7 +313,7 @@ type buildEventReporter struct {
313313
progressCount int32
314314
}
315315

316-
func newBuildEventReporter(ctx context.Context, besBackend string, apiKey string, forcedInvocationID string, isWorkflow bool) (*buildEventReporter, error) {
316+
func newBuildEventReporter(ctx context.Context, besBackend string, apiKey string, forcedInvocationID string, isWorkflow bool, redactionValues []string) (*buildEventReporter, error) {
317317
iid := forcedInvocationID
318318
if iid == "" {
319319
var err error
@@ -338,7 +338,7 @@ func newBuildEventReporter(ctx context.Context, besBackend string, apiKey string
338338
uploader = ul
339339
}
340340

341-
return &buildEventReporter{apiKey: apiKey, bep: bep, uploader: uploader, log: newInvocationLog(), invocationID: iid, isWorkflow: isWorkflow, childInvocations: []string{}}, nil
341+
return &buildEventReporter{apiKey: apiKey, bep: bep, uploader: uploader, log: newInvocationLog(redactionValues), invocationID: iid, isWorkflow: isWorkflow, childInvocations: []string{}}, nil
342342
}
343343

344344
func (r *buildEventReporter) InvocationID() string {
@@ -664,7 +664,7 @@ func run() error {
664664

665665
ws := &workspace{
666666
startTime: time.Now(),
667-
buildbuddyAPIKey: os.Getenv(buildbuddyAPIKeyEnvVarName),
667+
buildbuddyAPIKey: os.Getenv(ci_runner_env.BuildBuddyAPIKeyEnvVarName),
668668
forcedInvocationID: *invocationID,
669669
runID: runID,
670670
}
@@ -685,7 +685,8 @@ func run() error {
685685

686686
// Use a context without a timeout for the build event reporter, so that even
687687
// if the `timeout` is reached, any events will finish getting published
688-
buildEventReporter, err := newBuildEventReporter(contextWithoutTimeout, *besBackend, ws.buildbuddyAPIKey, *invocationID, *workflowID != "" /*=isWorkflow*/)
688+
redactionValues := parseSecretRedactionValues(os.Getenv(ci_runner_env.BuildBuddySecretEnvVarNamesForRedaction))
689+
buildEventReporter, err := newBuildEventReporter(contextWithoutTimeout, *besBackend, ws.buildbuddyAPIKey, *invocationID, *workflowID != "" /*=isWorkflow*/, redactionValues)
689690
if err != nil {
690691
return err
691692
}
@@ -950,20 +951,24 @@ func (r *buildEventReporter) Printf(format string, vals ...interface{}) {
950951

951952
type invocationLog struct {
952953
lockingbuffer.LockingBuffer
953-
writer io.Writer
954-
writeListener func(s string)
954+
writer io.Writer
955+
writeListener func(s string)
956+
redactionValues []string
955957
}
956958

957-
func newInvocationLog() *invocationLog {
958-
invLog := &invocationLog{writeListener: func(s string) {}}
959+
func newInvocationLog(redactionValues []string) *invocationLog {
960+
invLog := &invocationLog{writeListener: func(s string) {}, redactionValues: redactionValues}
959961
invLog.writer = io.MultiWriter(&invLog.LockingBuffer, os.Stderr)
960962
return invLog
961963
}
962964

963965
func (invLog *invocationLog) Write(b []byte) (int, error) {
964966
output := string(b)
965967

966-
redacted := redact.RedactText(output)
968+
// Use value-aware redaction so user-defined secret values injected into the
969+
// runner environment are masked in invocation logs (including overlapping
970+
// values handled safely by longest-first replacement in redact package).
971+
redacted := redact.RedactTextWithValues(output, invLog.redactionValues)
967972

968973
invLog.writeListener(redacted)
969974
_, err := invLog.writer.Write([]byte(redacted))
@@ -2285,11 +2290,11 @@ func writeBazelrc(path, invocationID, runID, rootDir string) error {
22852290
if isPushedRefInFork() {
22862291
lines = append(lines, "common --build_metadata=FORK_REPO_URL="+*pushedRepoURL)
22872292
}
2288-
if apiKey := os.Getenv(buildbuddyAPIKeyEnvVarName); apiKey != "" {
2293+
if apiKey := os.Getenv(ci_runner_env.BuildBuddyAPIKeyEnvVarName); apiKey != "" {
22892294
lines = append(lines, "common --remote_header=x-buildbuddy-api-key="+apiKey)
22902295
lines = append(lines, "build:buildbuddy_api_key --remote_header=x-buildbuddy-api-key="+apiKey)
22912296
}
2292-
if origin := os.Getenv("BB_GRPC_CLIENT_ORIGIN"); origin != "" {
2297+
if origin := os.Getenv(ci_runner_env.BBGrpcClientOriginEnvVarName); origin != "" {
22932298
lines = append(lines, fmt.Sprintf("common --remote_header=%s=%s", usageutil.OriginHeaderName, origin))
22942299
lines = append(lines, fmt.Sprintf("common --bes_header=%s=%s", usageutil.OriginHeaderName, origin))
22952300
}
@@ -2782,3 +2787,24 @@ func diskUsage() (*diskUsageStats, error) {
27822787
usedBytes: int64(usedBytes),
27832788
}, nil
27842789
}
2790+
2791+
func parseSecretRedactionValues(serializedSecretNames string) []string {
2792+
if serializedSecretNames == "" {
2793+
return nil
2794+
}
2795+
var names []string
2796+
if err := json.Unmarshal([]byte(serializedSecretNames), &names); err != nil {
2797+
backendLog.Warningf("Failed to parse %s env var for secret redaction: %s", ci_runner_env.BuildBuddySecretEnvVarNamesForRedaction, err)
2798+
return nil
2799+
}
2800+
values := make([]string, 0, len(names))
2801+
for _, name := range names {
2802+
if name == "" {
2803+
continue
2804+
}
2805+
if val, ok := os.LookupEnv(name); ok && val != "" {
2806+
values = append(values, val)
2807+
}
2808+
}
2809+
return values
2810+
}

enterprise/server/hostedrunner/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ go_library(
1010
"//enterprise/server/experiments",
1111
"//enterprise/server/remote_execution/operation",
1212
"//enterprise/server/remote_execution/snaputil",
13+
"//enterprise/server/util/ci_runner_env",
1314
"//enterprise/server/util/ci_runner_util",
1415
"//enterprise/server/workflow/config",
1516
"//proto:invocation_status_go_proto",

enterprise/server/hostedrunner/hostedrunner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/buildbuddy-io/buildbuddy/enterprise/server/experiments"
1414
"github.com/buildbuddy-io/buildbuddy/enterprise/server/remote_execution/operation"
1515
"github.com/buildbuddy-io/buildbuddy/enterprise/server/remote_execution/snaputil"
16+
"github.com/buildbuddy-io/buildbuddy/enterprise/server/util/ci_runner_env"
1617
"github.com/buildbuddy-io/buildbuddy/enterprise/server/util/ci_runner_util"
1718
"github.com/buildbuddy-io/buildbuddy/enterprise/server/workflow/config"
1819
"github.com/buildbuddy-io/buildbuddy/server/endpoint_urls/build_buddy_url"
@@ -368,7 +369,7 @@ func (r *runnerService) credentialEnvOverrides(ctx context.Context, req *rnpb.Ru
368369

369370
// Use env override headers for credentials.
370371
envOverrides := []string{
371-
"BUILDBUDDY_API_KEY=" + apiKey.Value,
372+
ci_runner_env.BuildBuddyAPIKeyEnvVarName + "=" + apiKey.Value,
372373
"REPO_USER=" + req.GetGitRepo().GetUsername(),
373374
"REPO_TOKEN=" + accessToken,
374375
}

enterprise/server/remote_execution/execution_server/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
"//enterprise/server/remote_execution/action_merger",
1414
"//enterprise/server/remote_execution/operation",
1515
"//enterprise/server/tasksize",
16+
"//enterprise/server/util/ci_runner_env",
1617
"//enterprise/server/util/execution",
1718
"//proto:execution_stats_go_proto",
1819
"//proto:invocation_status_go_proto",

enterprise/server/remote_execution/execution_server/execution_server.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package execution_server
33
import (
44
"context"
55
"encoding/base64"
6+
"encoding/json"
67
"fmt"
78
"io"
89
"path/filepath"
@@ -18,6 +19,7 @@ import (
1819
"github.com/buildbuddy-io/buildbuddy/enterprise/server/remote_execution/action_merger"
1920
"github.com/buildbuddy-io/buildbuddy/enterprise/server/remote_execution/operation"
2021
"github.com/buildbuddy-io/buildbuddy/enterprise/server/tasksize"
22+
"github.com/buildbuddy-io/buildbuddy/enterprise/server/util/ci_runner_env"
2123
"github.com/buildbuddy-io/buildbuddy/proto/invocation_status"
2224
"github.com/buildbuddy-io/buildbuddy/server/environment"
2325
"github.com/buildbuddy-io/buildbuddy/server/interfaces"
@@ -861,6 +863,20 @@ func (s *ExecutionServer) dispatch(ctx context.Context, req *repb.ExecuteRequest
861863
return nil, err
862864
}
863865
executionTask.Command.EnvironmentVariables = append(executionTask.Command.EnvironmentVariables, envVars...)
866+
secretEnvVarNames := make([]string, 0, len(envVars))
867+
for _, envVar := range envVars {
868+
secretEnvVarNames = append(secretEnvVarNames, envVar.GetName())
869+
}
870+
if len(secretEnvVarNames) > 0 {
871+
serializedNames, err := json.Marshal(secretEnvVarNames)
872+
if err != nil {
873+
return nil, status.WrapError(err, "marshal secret env var names")
874+
}
875+
executionTask.Command.EnvironmentVariables = append(executionTask.Command.EnvironmentVariables, &repb.Command_EnvironmentVariable{
876+
Name: ci_runner_env.BuildBuddySecretEnvVarNamesForRedaction,
877+
Value: string(serializedNames),
878+
})
879+
}
864880
}
865881

866882
executionTask.QueuedTimestamp = timestamppb.Now()

enterprise/server/remote_execution/executorplatform/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ go_library(
77
srcs = ["executorplatform.go"],
88
importpath = "github.com/buildbuddy-io/buildbuddy/enterprise/server/remote_execution/executorplatform",
99
deps = [
10+
"//enterprise/server/util/ci_runner_env",
1011
"//proto:remote_execution_go_proto",
1112
"//server/environment",
1213
"//server/interfaces",

enterprise/server/remote_execution/executorplatform/executorplatform.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/buildbuddy-io/buildbuddy/enterprise/server/util/ci_runner_env"
1314
"github.com/buildbuddy-io/buildbuddy/server/environment"
1415
"github.com/buildbuddy-io/buildbuddy/server/interfaces"
1516
"github.com/buildbuddy-io/buildbuddy/server/util/flag"
@@ -257,7 +258,7 @@ func ApplyOverrides(env environment.Env, executorProps *ExecutorProperties, plat
257258
// forwarding an env var to the runner.
258259
if platformProps.WorkflowID != "" {
259260
command.EnvironmentVariables = append(command.EnvironmentVariables, &repb.Command_EnvironmentVariable{
260-
Name: "BB_GRPC_CLIENT_ORIGIN",
261+
Name: ci_runner_env.BBGrpcClientOriginEnvVarName,
261262
Value: usageutil.ClientOrigin(),
262263
})
263264

@@ -270,7 +271,7 @@ func ApplyOverrides(env environment.Env, executorProps *ExecutorProperties, plat
270271
return err
271272
}
272273
command.EnvironmentVariables = append(command.EnvironmentVariables, &repb.Command_EnvironmentVariable{
273-
Name: "BB_GRPC_CLIENT_IDENTITY",
274+
Name: ci_runner_env.BBGrpcClientIdentityEnvVarName,
274275
Value: h,
275276
})
276277
}

enterprise/server/test/integration/remote_bazel/remote_bazel_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,16 +531,17 @@ sh_binary(
531531
GroupId: env.GroupID1,
532532
}
533533

534-
// Save a secret
534+
// Save secrets
535535
saveSecret(t, bbClient, ctx, reqCtx, *pubKey, "SECRET_TARGET", ":hello_world")
536+
saveSecret(t, bbClient, ctx, reqCtx, *pubKey, "SECRET_MESSAGE", "super_secret_message_for_redaction_test")
536537

537538
// Run remote bazel
538539
runRemoteBazelInSeparateProcess(t, repoDir, bbServer.GRPCAddress(),
539540
// Initialize secrets as env vars on the runner
540541
"--runner_exec_properties=include-secrets=true",
541542
// Use --script here, because otherwise $SECRET_TARGET will be parsed
542543
// as a string literal and will not be expanded as an env var
543-
"--script=bazel run $SECRET_TARGET --noenable_bzlmod --enable_workspace",
544+
"--script=echo secret=$SECRET_MESSAGE && bazel run $SECRET_TARGET --noenable_bzlmod --enable_workspace",
544545
fmt.Sprintf("--remote_header=x-buildbuddy-api-key=%s", env.APIKey1))
545546

546547
// Check the invocation logs to ensure the bazel command successfully ran
@@ -567,6 +568,8 @@ sh_binary(
567568
require.NoError(t, err)
568569
require.Contains(t, string(logResp.GetBuffer()), "Build completed successfully")
569570
require.Contains(t, string(logResp.GetBuffer()), "FUTURE OF BUILDS!")
571+
require.NotContains(t, string(logResp.GetBuffer()), "super_secret_message_for_redaction_test")
572+
require.Contains(t, string(logResp.GetBuffer()), "secret=<REDACTED>")
570573
}
571574

572575
func setupSecrets(t *testing.T) (func(*rbetest.Env, *testenv.TestEnv), *string) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
package(default_visibility = ["//enterprise:__subpackages__"])
4+
5+
go_library(
6+
name = "ci_runner_env",
7+
srcs = ["ci_runner_env.go"],
8+
importpath = "github.com/buildbuddy-io/buildbuddy/enterprise/server/util/ci_runner_env",
9+
)

0 commit comments

Comments
 (0)