-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
VAULT-31409: trace postUnseal function (#28895)
* initial implementation of unseal trace * close file if we fail to start the trace didn't bother to check the error from traceFile.Close() * use reloadable config instead of env var * license * remove leftover * allow setting custom dir and remove new package * bring back StartDebugTrace after talking to Kuba it sounds like it's a good idea to try to move stuff out of core, so even if there's no immediate need for a generic debug trace function it's still fair to add it * track postUnseal instead of unsealInternal also some usability improvements from manual testing * address PR comments * address security review there were concerns about using the /tmp directory because of permissions, or having a default dir at all, so now it's required to set a dir in order to generate the traces. * add unit tests to StartDebugTrace * move back to default dir * document new parameters * add tiny integration test * avoid column in trace filename sounds like it might be forbidden in Windows and possibly cause problems in some MacOS applications. * address PR feedback * add go doc to test CI was complaining about missing comments on the new test function. It feels a bit silly to require this of tests but whatever XD * fix tests
- Loading branch information
Showing
10 changed files
with
272 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:improvement | ||
core: Added new `enable_post_unseal_trace` and `post_unseal_trace_directory` config options to generate Go traces during the post-unseal step for debug purposes. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package trace | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"runtime/trace" | ||
"time" | ||
) | ||
|
||
func StartDebugTrace(dir string, filePrefix string) (file string, stop func() error, err error) { | ||
dirMustExist := true | ||
if dir == "" { | ||
dirMustExist = false // if a dir is provided it must exist, otherwise we'll create a default one | ||
dir = filepath.Join(os.TempDir(), "vault-traces") | ||
} | ||
|
||
d, err := os.Stat(dir) | ||
if err != nil && !os.IsNotExist(err) { | ||
return "", nil, fmt.Errorf("failed to stat trace directory %q: %s", dir, err) | ||
} | ||
|
||
if os.IsNotExist(err) && dirMustExist { | ||
return "", nil, fmt.Errorf("trace directory %q does not exist", dir) | ||
} | ||
|
||
if !os.IsNotExist(err) && !d.IsDir() { | ||
return "", nil, fmt.Errorf("trace directory %q is not a directory", dir) | ||
} | ||
|
||
if os.IsNotExist(err) { | ||
if err := os.Mkdir(dir, 0o700); err != nil { | ||
return "", nil, fmt.Errorf("failed to create trace directory %q: %s", dir, err) | ||
} | ||
} | ||
|
||
// would prefer a more human readable time reference in the file name but the column | ||
// character can cause problems in filenames | ||
fileName := fmt.Sprintf("%s-%d.trace", filePrefix, time.Now().Unix()) | ||
traceFile, err := filepath.Abs(filepath.Join(dir, fileName)) | ||
if err != nil { | ||
return "", nil, fmt.Errorf("failed to get absolute path for trace file: %s", err) | ||
} | ||
f, err := os.Create(traceFile) | ||
if err != nil { | ||
return "", nil, fmt.Errorf("failed to create trace file %q: %s", traceFile, err) | ||
} | ||
|
||
if err := trace.Start(f); err != nil { | ||
f.Close() | ||
return "", nil, fmt.Errorf("failed to start trace: %s", err) | ||
} | ||
|
||
stop = func() error { | ||
trace.Stop() | ||
return f.Close() | ||
} | ||
|
||
return f.Name(), stop, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
|
||
package trace | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// TestStartDebugTrace tests the debug trace functionality creating real | ||
// files and traces. | ||
func TestStartDebugTrace(t *testing.T) { | ||
t.Run("error_on_non_existent_dir", func(t *testing.T) { | ||
_, _, err := StartDebugTrace("non-existent-dir", "filePrefix") | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), "does not exist") | ||
}) | ||
|
||
t.Run("error_on_non_dir", func(t *testing.T) { | ||
f, err := os.CreateTemp("", "") | ||
require.NoError(t, err) | ||
require.NoError(t, f.Close()) | ||
_, _, err = StartDebugTrace(f.Name(), "") | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), "is not a directory") | ||
}) | ||
|
||
t.Run("error_on_failed_to_create_trace_file", func(t *testing.T) { | ||
noWriteFolder := filepath.Join(os.TempDir(), "no-write-permissions") | ||
// create folder without write permission | ||
err := os.Mkdir(noWriteFolder, 0o000) | ||
t.Cleanup(func() { | ||
os.RemoveAll(noWriteFolder) | ||
}) | ||
require.NoError(t, err) | ||
_, _, err = StartDebugTrace(noWriteFolder, "") | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), "failed to create trace file") | ||
}) | ||
|
||
t.Run("error_trying_to_start_second_concurrent_trace", func(t *testing.T) { | ||
dir, err := os.MkdirTemp("", "") | ||
require.NoError(t, err) | ||
t.Cleanup(func() { | ||
os.RemoveAll(dir) | ||
}) | ||
_, stop, err := StartDebugTrace(dir, "filePrefix") | ||
require.NoError(t, err) | ||
_, stopNil, err := StartDebugTrace(dir, "filePrefix") | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), "failed to start trace") | ||
require.NoError(t, stop()) | ||
require.Nil(t, stopNil) | ||
}) | ||
|
||
t.Run("error_when_stating_tmp_dir_with_restricted_permissions", func(t *testing.T) { | ||
// this test relies on setting TMPDIR so skip it if we're not on a Unix system | ||
if runtime.GOOS == "windows" { | ||
t.Skip("skipping test on Windows") | ||
} | ||
|
||
tmpMissingPermissions := filepath.Join(t.TempDir(), "missing_permissions") | ||
err := os.Mkdir(tmpMissingPermissions, 0o000) | ||
require.NoError(t, err) | ||
t.Setenv("TMPDIR", tmpMissingPermissions) | ||
_, _, err = StartDebugTrace("", "filePrefix") | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), "failed to stat trace directory") | ||
}) | ||
|
||
t.Run("successful_trace_generates_non_empty_file", func(t *testing.T) { | ||
dir, err := os.MkdirTemp("", "") | ||
require.NoError(t, err) | ||
t.Cleanup(func() { | ||
os.RemoveAll(dir) | ||
}) | ||
file, stop, err := StartDebugTrace(dir, "filePrefix") | ||
require.NoError(t, err) | ||
require.NoError(t, stop()) | ||
f, err := os.Stat(file) | ||
require.NoError(t, err) | ||
require.Greater(t, f.Size(), int64(0)) | ||
}) | ||
|
||
t.Run("successful_creation_of_tmp_dir", func(t *testing.T) { | ||
os.RemoveAll(filepath.Join(os.TempDir(), "vault-traces")) | ||
file, stop, err := StartDebugTrace("", "filePrefix") | ||
require.NoError(t, err) | ||
require.NoError(t, stop()) | ||
require.Contains(t, file, filepath.Join(os.TempDir(), "vault-traces", "filePrefix")) | ||
f, err := os.Stat(file) | ||
require.NoError(t, err) | ||
require.Greater(t, f.Size(), int64(0)) | ||
}) | ||
|
||
t.Run("successful_trace_with_existing_tmp_dir", func(t *testing.T) { | ||
os.Mkdir(filepath.Join(os.TempDir(), "vault-traces"), 0o700) | ||
file, stop, err := StartDebugTrace("", "filePrefix") | ||
require.NoError(t, err) | ||
require.NoError(t, stop()) | ||
require.Contains(t, file, filepath.Join(os.TempDir(), "vault-traces", "filePrefix")) | ||
f, err := os.Stat(file) | ||
require.NoError(t, err) | ||
require.Greater(t, f.Size(), int64(0)) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters