diff --git a/cmd/core_plugin/agentcrypto/mtls_mds_linux.go b/cmd/core_plugin/agentcrypto/mtls_mds_linux.go index a1a3683d..7a978810 100644 --- a/cmd/core_plugin/agentcrypto/mtls_mds_linux.go +++ b/cmd/core_plugin/agentcrypto/mtls_mds_linux.go @@ -73,7 +73,7 @@ func (j *CredsJob) writeRootCACert(ctx context.Context, content []byte, outputFi galog.Debugf("Writing root CA cert to %q", outputFile) // Write the root CA cert to the output file. - if err := j.writeCredentials(ctx, content, outputFile); err != nil { + if err := j.writeCredentials(ctx, content, outputFile, 0644); err != nil { return err } galog.Debugf("Successfully wrote root CA cert to %q", outputFile) @@ -95,16 +95,16 @@ func (j *CredsJob) writeRootCACert(ctx context.Context, content []byte, outputFi // key). func (j *CredsJob) writeClientCredentials(ctx context.Context, plaintext []byte, outputFile string) error { galog.Debugf("Writing client credentials to %q", outputFile) - return j.writeCredentials(ctx, plaintext, outputFile) + return j.writeCredentials(ctx, plaintext, outputFile, 0600) } // writeCredentials stores the provided credentials to the output file. -func (j *CredsJob) writeCredentials(ctx context.Context, certContent []byte, outputFile string) error { +func (j *CredsJob) writeCredentials(ctx context.Context, certContent []byte, outputFile string, perm os.FileMode) error { // The directory should be executable, but the file does not need to be. - if err := os.MkdirAll(filepath.Dir(outputFile), 0655); err != nil { + if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil { return err } - return file.SaferWriteFile(ctx, certContent, outputFile, file.Options{Perm: 0644}) + return file.SaferWriteFile(ctx, certContent, outputFile, file.Options{Perm: perm}) } // getCAStoreUpdater iterates over known system trust store updaters and returns diff --git a/cmd/core_plugin/agentcrypto/mtls_mds_linux_test.go b/cmd/core_plugin/agentcrypto/mtls_mds_linux_test.go index 32404945..ac4d99c7 100644 --- a/cmd/core_plugin/agentcrypto/mtls_mds_linux_test.go +++ b/cmd/core_plugin/agentcrypto/mtls_mds_linux_test.go @@ -20,6 +20,7 @@ import ( "context" "os" "path/filepath" + "syscall" "testing" "github.com/GoogleCloudPlatform/google-guest-agent/internal/cfg" @@ -194,3 +195,45 @@ func TestCertificateDirFromUpdaterError(t *testing.T) { t.Errorf("certificateDirFromUpdater(unknown) succeeded for missing cert dir, want error") } } + +func TestWriteClientCredentials(t *testing.T) { + ctx := context.Background() + j := &CredsJob{} + tempDir := t.TempDir() + outputFile := filepath.Join(tempDir, "test_dir", "client.key") + + oldUmask := syscall.Umask(0) + defer syscall.Umask(oldUmask) + + content := []byte("test_credentials_content") + if err := j.writeClientCredentials(ctx, content, outputFile); err != nil { + t.Fatalf("writeClientCredentials failed unexpectedly: %v", err) + } + + // Verify file content + got, err := os.ReadFile(outputFile) + if err != nil { + t.Fatalf("Failed to read written file: %v", err) + } + if string(got) != string(content) { + t.Errorf("Written file content = %q, want %q", string(got), string(content)) + } + + // Verify file permissions + fInfo, err := os.Stat(outputFile) + if err != nil { + t.Fatalf("Failed to stat file: %v", err) + } + if fInfo.Mode().Perm() != 0600 { + t.Errorf("File permissions = %o, want 0600", fInfo.Mode().Perm()) + } + + // Verify directory permissions + dInfo, err := os.Stat(filepath.Dir(outputFile)) + if err != nil { + t.Fatalf("Failed to stat directory: %v", err) + } + if dInfo.Mode().Perm() != 0755 { + t.Errorf("Directory permissions = %o, want 0755", dInfo.Mode().Perm()) + } +} diff --git a/cmd/core_plugin/agentcrypto/mtls_mds_windows.go b/cmd/core_plugin/agentcrypto/mtls_mds_windows.go index 5dc704d1..4f7a7658 100644 --- a/cmd/core_plugin/agentcrypto/mtls_mds_windows.go +++ b/cmd/core_plugin/agentcrypto/mtls_mds_windows.go @@ -242,7 +242,7 @@ func (j *CredsJob) writeClientCredentials(ctx context.Context, creds []byte, out galog.Warnf("Could not get previous serial number, will skip cleanup: %v", err) } - if err := file.SaferWriteFile(ctx, creds, outputFile, file.Options{Perm: 0644}); err != nil { + if err := file.SaferWriteFile(ctx, creds, outputFile, file.Options{Perm: 0600}); err != nil { return fmt.Errorf("failed to write client key: %w", err) } galog.Debugf("Successfully wrote client credentials to %q", outputFile) @@ -256,7 +256,7 @@ func (j *CredsJob) writeClientCredentials(ctx context.Context, creds []byte, out galog.V(1).Debugf("Writing PFX file to %q", pfxFile) p := filepath.Join(filepath.Dir(outputFile), pfxFile) - if err := file.SaferWriteFile(ctx, pfx, p, file.Options{Perm: 0644}); err != nil { + if err := file.SaferWriteFile(ctx, pfx, p, file.Options{Perm: 0600}); err != nil { return fmt.Errorf("failed to write PFX file: %w", err) } galog.V(1).Debugf("Successfully wrote PFX file to %q", p) diff --git a/cmd/core_plugin/workloadcertrefresh/refresher.go b/cmd/core_plugin/workloadcertrefresh/refresher.go index 9cb6c099..968348cd 100644 --- a/cmd/core_plugin/workloadcertrefresh/refresher.go +++ b/cmd/core_plugin/workloadcertrefresh/refresher.go @@ -192,7 +192,7 @@ func writeWorkloadIdentities(destDir string, wisMd []byte) (string, error) { return "", fmt.Errorf("error writing certificates.pem: %w", err) } - if err := os.WriteFile(filepath.Join(destDir, "private_key.pem"), []byte(wis.WorkloadCredentials[spiffeID].PrivateKeyPem), 0644); err != nil { + if err := os.WriteFile(filepath.Join(destDir, "private_key.pem"), []byte(wis.WorkloadCredentials[spiffeID].PrivateKeyPem), 0600); err != nil { return "", fmt.Errorf("error writing private_key.pem: %w", err) } return spiffeID, nil @@ -449,7 +449,7 @@ func (j *RefresherJob) refreshCredsWithGRPC(ctx context.Context, contentDir stri } galog.Debugf("Writing workload certificates private key to %s", contentDir) - if err := os.WriteFile(filepath.Join(contentDir, "private_key.pem"), certs.GetPrivateKeyPem(), 0644); err != nil { + if err := os.WriteFile(filepath.Join(contentDir, "private_key.pem"), certs.GetPrivateKeyPem(), 0600); err != nil { return fmt.Errorf("error writing private_key.pem: %w", err) } galog.Debugf("Writing workload certificates certificate chain to %s", contentDir) diff --git a/cmd/core_plugin/workloadcertrefresh/refresher_test.go b/cmd/core_plugin/workloadcertrefresh/refresher_test.go index 5c4201e8..f5da0b13 100644 --- a/cmd/core_plugin/workloadcertrefresh/refresher_test.go +++ b/cmd/core_plugin/workloadcertrefresh/refresher_test.go @@ -21,6 +21,7 @@ import ( "net" "os" "path/filepath" + "runtime" "strconv" "testing" "time" @@ -175,6 +176,16 @@ func TestWriteWorkloadIdentities(t *testing.T) { if string(gotPvtPem) != pvtPem { t.Errorf("writeWorkloadIdentities(%s,%s) wrote %q, expected to write %q", dir, resp, string(gotPvtPem), pvtPem) } + + if runtime.GOOS != "windows" { + info, err := os.Stat(filepath.Join(dir, "private_key.pem")) + if err != nil { + t.Errorf("failed to stat private_key.pem: %v", err) + } + if info.Mode().Perm() != 0600 { + t.Errorf("private_key.pem has permissions %o, want 0600", info.Mode().Perm()) + } + } } func TestFindDomainError(t *testing.T) { @@ -354,6 +365,20 @@ func TestRefreshCreds(t *testing.T) { if string(got) != test.content { t.Errorf("refreshCreds(ctx, %+v) wrote %q, want content %q", out, string(got), test.content) } + + if runtime.GOOS != "windows" { + info, err := os.Stat(test.path) + if err != nil { + t.Errorf("failed to stat file %q: %v", test.path, err) + } + expectedPerm := os.FileMode(0644) + if filepath.Base(test.path) == "private_key.pem" { + expectedPerm = 0600 + } + if info.Mode().Perm() != expectedPerm { + t.Errorf("file %q has permissions %o, want %o", test.path, info.Mode().Perm(), expectedPerm) + } + } }) }