Redact no_log task results

This commit is contained in:
Virgil 2026-04-01 21:22:32 +00:00
parent f233605542
commit 89eee7b964
2 changed files with 109 additions and 2 deletions

View file

@ -602,13 +602,18 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin
e.results[host][task.Register] = result
}
displayResult := result
if task.NoLog {
displayResult = redactTaskResult(result)
}
// Handle notify
if result.Changed && task.Notify != nil {
e.handleNotify(task.Notify)
}
if e.OnTaskEnd != nil {
e.OnTaskEnd(host, task, result)
e.OnTaskEnd(host, task, displayResult)
}
if NormalizeModule(task.Module) == "ansible.builtin.meta" {
@ -619,7 +624,7 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin
if result.Failed && !task.IgnoreErrors {
e.markBatchHostFailed(host)
return coreerr.E("Executor.runTaskOnHost", "task failed: "+result.Msg, nil)
return taskFailureError(task, result)
}
if result.Failed {
e.markBatchHostFailed(host)
@ -663,6 +668,39 @@ func (e *Executor) checkMaxFailPercentage(play *Play, hosts []string) error {
return nil
}
func redactTaskResult(result *TaskResult) *TaskResult {
if result == nil {
return nil
}
redacted := *result
redacted.Msg = "censored due to no_log"
redacted.Stdout = ""
redacted.Stderr = ""
redacted.Data = nil
if len(result.Results) > 0 {
redacted.Results = make([]TaskResult, len(result.Results))
for i := range result.Results {
redacted.Results[i] = *redactTaskResult(&result.Results[i])
}
}
return &redacted
}
func taskFailureError(task *Task, result *TaskResult) error {
if task != nil && task.NoLog {
return coreerr.E("Executor.runTaskOnHost", "task failed", nil)
}
msg := "task failed"
if result != nil && result.Msg != "" {
msg += ": " + result.Msg
}
return coreerr.E("Executor.runTaskOnHost", msg, nil)
}
// runTaskWithRetries executes a task once or multiple times when retries,
// delay, or until are configured.
func (e *Executor) runTaskWithRetries(ctx context.Context, host string, task *Task, play *Play, execute func() (*TaskResult, error)) (*TaskResult, error) {

View file

@ -635,6 +635,75 @@ func TestExecutor_RunTaskOnHost_Good_CheckModeSkipsMutatingTask(t *testing.T) {
assert.True(t, e.results["host1"]["shell_result"].Skipped)
}
// --- no_log ---
func TestExecutor_RunTaskOnHost_Good_NoLogRedactsCallbackResult(t *testing.T) {
e := NewExecutor("/tmp")
e.SetInventoryDirect(&Inventory{
All: &InventoryGroup{
Hosts: map[string]*Host{
"host1": {},
},
},
})
e.clients["host1"] = &SSHClient{}
var ended *TaskResult
task := &Task{
Name: "Sensitive debug",
Module: "debug",
Args: map[string]any{"msg": "top secret"},
Register: "debug_result",
NoLog: true,
}
e.OnTaskEnd = func(_ string, _ *Task, result *TaskResult) {
ended = result
}
err := e.runTaskOnHost(context.Background(), "host1", []string{"host1"}, task, &Play{})
require.NoError(t, err)
require.NotNil(t, ended)
assert.Equal(t, "censored due to no_log", ended.Msg)
assert.Empty(t, ended.Stdout)
assert.Empty(t, ended.Stderr)
assert.Nil(t, ended.Data)
require.NotNil(t, e.results["host1"]["debug_result"])
assert.Equal(t, "top secret", e.results["host1"]["debug_result"].Msg)
}
func TestExecutor_RunTaskOnHost_Bad_NoLogHidesFailureMessage(t *testing.T) {
e := NewExecutor("/tmp")
e.SetInventoryDirect(&Inventory{
All: &InventoryGroup{
Hosts: map[string]*Host{
"host1": {},
},
},
})
e.clients["host1"] = &SSHClient{}
var ended *TaskResult
task := &Task{
Name: "Sensitive failure",
Module: "fail",
Args: map[string]any{"msg": "super secret"},
NoLog: true,
}
e.OnTaskEnd = func(_ string, _ *Task, result *TaskResult) {
ended = result
}
err := e.runTaskOnHost(context.Background(), "host1", []string{"host1"}, task, &Play{})
require.Error(t, err)
assert.Contains(t, err.Error(), "task failed")
assert.NotContains(t, err.Error(), "super secret")
require.NotNil(t, ended)
assert.Equal(t, "censored due to no_log", ended.Msg)
}
// --- normalizeConditions ---
func TestExecutor_NormalizeConditions_Good_String(t *testing.T) {