diff --git a/godebugset/setup.go b/godebugset/setup.go new file mode 100644 index 0000000000000000000000000000000000000000..93574c47dd2737ff2db23551784d74baf760141d --- /dev/null +++ b/godebugset/setup.go @@ -0,0 +1,56 @@ +package godebugset + +import ( + "os" + "strings" +) + +// Initialize sets GODEBUG to disable post-quantum TLS if GODEBUG is not already set. +// This handles both Go 1.23 (tlskyber=0) and Go 1.24+ (tlsmlkem=0) flags. +func Initialize() { + godebug := os.Getenv("GODEBUG") + + if godebug == "" { + // Set both flags for compatibility across Go versions + os.Setenv("GODEBUG", "tlskyber=0,tlsmlkem=0") + return + } + + // If GODEBUG is already set, we don't modify it to avoid conflicts +} + +// InitializeWithAppend appends the post-quantum TLS disable flags if they're not present. +func InitializeWithAppend() { + godebug := os.Getenv("GODEBUG") + var additions []string + + if godebug == "" { + os.Setenv("GODEBUG", "tlskyber=0,tlsmlkem=0") + return + } + + // Parse existing GODEBUG settings + existingSettings := make(map[string]bool) + for _, setting := range strings.Split(godebug, ",") { + setting = strings.TrimSpace(setting) + if setting != "" { + // Extract the key part (before the '=') + if idx := strings.Index(setting, "="); idx != -1 { + key := setting[:idx] + existingSettings[key] = true + } + } + } + + // Check and add missing flags + if !existingSettings["tlskyber"] { + additions = append(additions, "tlskyber=0") + } + if !existingSettings["tlsmlkem"] { + additions = append(additions, "tlsmlkem=0") + } + + if len(additions) > 0 { + os.Setenv("GODEBUG", godebug+","+strings.Join(additions, ",")) + } +} diff --git a/godebugset/setup_test.go b/godebugset/setup_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cd3c6ba87e9c92c146ed8092c722861e10c4085d --- /dev/null +++ b/godebugset/setup_test.go @@ -0,0 +1,205 @@ +package godebugset + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitialize(t *testing.T) { + originalGodebug := os.Getenv("GODEBUG") + + defer func() { + if originalGodebug == "" { + os.Unsetenv("GODEBUG") + } else { + os.Setenv("GODEBUG", originalGodebug) + } + }() + + tests := []struct { + name string + initialGodebug string + expectedResult string + }{ + { + name: "empty GODEBUG sets both flags", + initialGodebug: "", + expectedResult: "tlskyber=0,tlsmlkem=0", + }, + { + name: "existing GODEBUG unchanged", + initialGodebug: "http2server=0", + expectedResult: "http2server=0", + }, + { + name: "existing GODEBUG with tlskyber unchanged", + initialGodebug: "tlskyber=1,http2server=0", + expectedResult: "tlskyber=1,http2server=0", + }, + { + name: "existing GODEBUG with tlsmlkem unchanged", + initialGodebug: "tlsmlkem=1,http2server=0", + expectedResult: "tlsmlkem=1,http2server=0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.initialGodebug == "" { + os.Unsetenv("GODEBUG") + } else { + os.Setenv("GODEBUG", tt.initialGodebug) + } + + Initialize() + + result := os.Getenv("GODEBUG") + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestInitializeWithAppend(t *testing.T) { + originalGodebug := os.Getenv("GODEBUG") + + defer func() { + if originalGodebug == "" { + os.Unsetenv("GODEBUG") + } else { + os.Setenv("GODEBUG", originalGodebug) + } + }() + + tests := []struct { + name string + initialGodebug string + expectedResult string + }{ + { + name: "empty GODEBUG sets both flags", + initialGodebug: "", + expectedResult: "tlskyber=0,tlsmlkem=0", + }, + { + name: "existing GODEBUG gets both flags appended", + initialGodebug: "http2server=0", + expectedResult: "http2server=0,tlskyber=0,tlsmlkem=0", + }, + { + name: "existing tlskyber only gets tlsmlkem appended", + initialGodebug: "tlskyber=1,http2server=0", + expectedResult: "tlskyber=1,http2server=0,tlsmlkem=0", + }, + { + name: "existing tlsmlkem only gets tlskyber appended", + initialGodebug: "tlsmlkem=1,http2server=0", + expectedResult: "tlsmlkem=1,http2server=0,tlskyber=0", + }, + { + name: "both flags already present, no change", + initialGodebug: "tlskyber=1,tlsmlkem=1,http2server=0", + expectedResult: "tlskyber=1,tlsmlkem=1,http2server=0", + }, + { + name: "flags with different values already present, no change", + initialGodebug: "tlskyber=0,tlsmlkem=0", + expectedResult: "tlskyber=0,tlsmlkem=0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.initialGodebug == "" { + os.Unsetenv("GODEBUG") + } else { + os.Setenv("GODEBUG", tt.initialGodebug) + } + + InitializeWithAppend() + + result := os.Getenv("GODEBUG") + assert.Equal(t, tt.expectedResult, result) + }) + } +} + +func TestInitializeWithAppend_EdgeCases(t *testing.T) { + originalGodebug := os.Getenv("GODEBUG") + defer func() { + if originalGodebug == "" { + os.Unsetenv("GODEBUG") + } else { + os.Setenv("GODEBUG", originalGodebug) + } + }() + + t.Run("substring matching doesn't trigger false positives", func(t *testing.T) { + // Test that "sometlskyber=1" doesn't match "tlskyber=" + os.Setenv("GODEBUG", "sometlskyber=1,anothertlsmlkem=0") + + InitializeWithAppend() + + result := os.Getenv("GODEBUG") + assert.Contains(t, result, "tlskyber=0") + assert.Contains(t, result, "tlsmlkem=0") + assert.Contains(t, result, "sometlskyber=1") + assert.Contains(t, result, "anothertlsmlkem=0") + }) + + t.Run("case sensitivity", func(t *testing.T) { + // GODEBUG settings are case sensitive, so TLSKYBER should not match tlskyber + os.Setenv("GODEBUG", "TLSKYBER=1,TLSMLKEM=1") + + InitializeWithAppend() + + result := os.Getenv("GODEBUG") + assert.Contains(t, result, "tlskyber=0") + assert.Contains(t, result, "tlsmlkem=0") + assert.Contains(t, result, "TLSKYBER=1") + assert.Contains(t, result, "TLSMLKEM=1") + }) +} + +func TestFunctionsSideEffects(t *testing.T) { + originalGodebug := os.Getenv("GODEBUG") + defer func() { + if originalGodebug == "" { + os.Unsetenv("GODEBUG") + } else { + os.Setenv("GODEBUG", originalGodebug) + } + }() + + t.Run("Initialize doesn't affect other environment variables", func(t *testing.T) { + testKey := "TEST_GODEBUG_PACKAGE" + testValue := "test_value" + os.Setenv(testKey, testValue) + defer os.Unsetenv(testKey) + + // Clear GODEBUG + os.Unsetenv("GODEBUG") + + Initialize() + + // Verify our test variable is unchanged + assert.Equal(t, testValue, os.Getenv(testKey)) + // Verify GODEBUG was set correctly + assert.Equal(t, "tlskyber=0,tlsmlkem=0", os.Getenv("GODEBUG")) + }) + + t.Run("multiple calls to Initialize are idempotent", func(t *testing.T) { + os.Unsetenv("GODEBUG") + + Initialize() + firstResult := os.Getenv("GODEBUG") + + Initialize() + secondResult := os.Getenv("GODEBUG") + + // Second call should not change anything since GODEBUG is now set + assert.Equal(t, firstResult, secondResult) + assert.Equal(t, "tlskyber=0,tlsmlkem=0", secondResult) + }) +}