Skip to content

Conversation

@n4mlz
Copy link

@n4mlz n4mlz commented Aug 20, 2025

Description

Added implementation to verify path before saving cert file.

Background

The current SIA agent fetches a new certificate from ZTS before attempting to write it to the filesystem. If the file write operation fails for any reason (e.g., incorrect permissions, non-existent directory), the agent enters a state of inconsistency: the new certificate exists in memory, but the old one remains on disk.

This inconsistency prevents future certificate renewals and requires complex manual intervention, such as deleting files and hard-rebooting the instance, to recover the agent.

What's done?

This PR introduces a robust pre-verification check that runs before the agent requests a certificate from ZTS. This check ensures that the followings are writable:

  • X.509 Cert File Path (CERT_FILE)
  • X.509 CA Cert File Path (CA_CERT_FILE)
  • X.509 Key Path (KEY_FILE)

This PR does not include the following:

  • RoleCert
  • AccessTokens
  • RoleTokens

If file path is not ready, it will output the following as a log:

file path not exist: stat /non/existent/path: no such file or directory, stat /non/existent: no such file or directory.

Assignees

  • Assignees is set

Type of changes

  • Apply one or more labels of the following that fits:
    • bug: Bug fix
    • dependencies: Dependency upgrades
    • documentation: Documentation changes
    • enhancement: New Feature
    • good first issue: First contribution
    • logging: Log changes
    • refactor: Refactoring (no functional changes, no api changes)

Flags

- [ ] Breaks backward compatibility
- [ ] Requires a documentation update
- [ ] Has untestable code

Checklist

- [ ] Followed the guidelines in the CONTRIBUTING document
- [ ] Added prefix `[skip ci]`/`[ci skip]`/`[no ci]`/`[skip actions]`/`[actions skip]` in the PR title if necessary
- [ ] Tested and linted the code
- [ ] Commented the code
- [ ] Made corresponding changes to the documentation

Checklist for maintainer

- [ ] Use `Squash and merge`
- [ ] Double-confirm the merge message has prefix `[skip ci]`/`[ci skip]`/`[no ci]`/`[skip actions]`/`[actions skip]`
- [ ] Delete the branch after merge

@codecov-commenter
Copy link

codecov-commenter commented Aug 20, 2025

Codecov Report

❌ Patch coverage is 0% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 9.39%. Comparing base (cdb74dd) to head (57e0768).

Files with missing lines Patch % Lines
pkg/config/validate-file-path.go 0.00% 36 Missing ⚠️
pkg/certificate/service.go 0.00% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##            main    #195      +/-   ##
========================================
- Coverage   9.50%   9.39%   -0.12%     
========================================
  Files         34      35       +1     
  Lines       3326    3365      +39     
========================================
  Hits         316     316              
- Misses      2988    3027      +39     
  Partials      22      22              
Flag Coverage Δ
unittests 9.39% <0.00%> (-0.12%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@n4mlz n4mlz force-pushed the fix/validate-file-path branch from f40d0b6 to da47ac4 Compare August 20, 2025 05:27
Copy link
Contributor

@mlajkim mlajkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you are still in the middle of implementation but do not write config related logic inside the pkg/certificate/service.go, but instead, write a new file under the directory config with the prefix dervied- & return error if certain path is not valid.

@mlajkim mlajkim added the enhancement New feature or request label Aug 20, 2025
Copy link
Contributor

@mlajkim mlajkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to include changes only for the validation logic.

If you think some part of code requires some fix, leave TODOs as a comment instead, rather than fixing them within the same PR.

Also was wondering if you would apply the same logic for the RoleCert as well? Thanks

@n4mlz n4mlz force-pushed the fix/validate-file-path branch from 8b53fed to c0dc6db Compare August 22, 2025 01:20
@n4mlz
Copy link
Author

n4mlz commented Aug 22, 2025

Regarding the location for certificate path validation processing

The config/* files perform validation immediately upon reading the configuration file (e.g., see

func (idCfg *IdentityConfig) validateAndInit() (err error) {
). However, our current verification target is file write permissions, specifically checking values like idCfg.ServiceCert.CopperArgos.Cert.Paths. Looking at pkg/certificate/service.go, these files exist in use cases where only reading occurs, with no write operations performed. If we were to perform write permission validation simultaneously with reading the configuration file, we would end up verifying write capabilities even when write operations aren't actually required. Furthermore, performing actual write permission validation as part of the configuration file processing seems beyond its intended responsibility. This could potentially create implicit dependencies.
Additionally, the requirement specified was "to verify actual write permissions before performing any file write operations. For these reasons, performing write permission validation within pkg/certificate/service.go appears more appropriate.

Copy link
Contributor

@mlajkim mlajkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write some more detailed description please.

  • What's happening? (Background)
  • What's done? (Your own words, not code only)

I made a sample PR that you may use as a reference here:
#196

Comment on lines 103 to 123
isValidFiles := func() error {
if !idCfg.ServiceCert.LocalCert.Use {
for _, certFile := range idCfg.ServiceCert.CopperArgos.Cert.Paths {
err := isValidFile(certFile)
if err != nil {
return err
}
}
for _, keyFile := range idCfg.ServiceCert.CopperArgos.Key.Paths {
err := isValidFile(keyFile)
if err != nil {
return err
}
}
err := isValidFile(idCfg.CaCertFile)
if err != nil {
return err
}
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the following:

  • Do early return as much as possible => gives less indent = more readable code = less buggy as easier to fix
  • In go, you can return error without defining them in each line.
Suggested change
isValidFiles := func() error {
if !idCfg.ServiceCert.LocalCert.Use {
for _, certFile := range idCfg.ServiceCert.CopperArgos.Cert.Paths {
err := isValidFile(certFile)
if err != nil {
return err
}
}
for _, keyFile := range idCfg.ServiceCert.CopperArgos.Key.Paths {
err := isValidFile(keyFile)
if err != nil {
return err
}
}
err := isValidFile(idCfg.CaCertFile)
if err != nil {
return err
}
}
return nil
}
isValidFiles := func() error {
// TODO: Write a reason why LocalCert Mode does not require the cert path validation!!
if idCfg.ServiceCert.LocalCert.Use {
return nil
}
for _, certFile := range idCfg.ServiceCert.CopperArgos.Cert.Paths {
if err := isValidFile(certFile); err != nil {
return err
}
}
for _, keyFile := range idCfg.ServiceCert.CopperArgos.Key.Paths {
if err := isValidFile(keyFile); err != nil {
return err
}
}
return isValidFile(idCfg.CaCertFile) // will return error if idCfg.CaCertFile is not a valid filepath
}

@n4mlz n4mlz requested a review from mlajkim August 25, 2025 05:00
Copy link
Contributor

@mlajkim mlajkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review

Comment on lines +63 to +66
err := validFilePath(keyFile)
if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits but:

Suggested change
err := validFilePath(keyFile)
if err != nil {
return err
}
if err := validFilePath(keyFile); err != nil {
return err
}

Comment on lines +57 to +60
err := validFilePath(certFile)
if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// Verify that the certificate file paths are in writable locations
func (idCfg *IdentityConfig) ValidateCertFilePath() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a background why we do this
(Can we have more comments for "why" this function exists)

Comment on lines +41 to +44
err := unix.Access(target_path, unix.W_OK)
if err != nil {
return fmt.Errorf("file permission error: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Verify that the certificate file paths are in writable locations
func (idCfg *IdentityConfig) ValidateCertFilePath() error {
// When idCfg.ServiceCert.LocalCert.Use is true, skip file writing and return early
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a why please thanks. Why do we skip for LocalCert Mode?

"golang.org/x/sys/unix"
)

func validFilePath(file_path string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write a comment please.

  • What it does
  • Why we have this

Comment on lines +26 to +39
dir_path := filepath.Dir(file_path)
var target_path string
_, file_err := os.Stat(file_path)
_, dir_err := os.Stat(dir_path)

if file_err == nil {
// validate file path
target_path = file_path
} else if os.IsNotExist(file_err) && dir_err == nil {
// validate dir path
target_path = dir_path
} else {
return fmt.Errorf("file path not exist: %w, %w", file_err, dir_err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was wondering if you can write a separate function for this, instead of including within the validFilePath()

func getPath(path string) error, string {
  _, err:= os.Stat(filePath)
  if err == nil {
     return nil, filePath
  }

  if !os.IsNotExist(err) {
     return  "", fmt.Errorf("error accessing path '%s': %w", filePath, err)
  }
 
  dirPath := filepath.Dir(filePath)
   if _, dirErr := osStat(dirPath); dirErr != nil { return "", fmt.Errorf("todo error"); }
  return dirPath, nil
}

Then just do

	if err := unix.Access(getPath(path), unix.W_OK); err != nil {
		return fmt.Errorf("file permission error: %w", err)
	}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not write ocmments or good function name so please do so.

@mlajkim mlajkim requested a review from Copilot August 25, 2025 23:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds validation of certificate file paths before the SIA agent requests certificates from ZTS, preventing inconsistent states when file write operations fail. The implementation checks that certificate, CA certificate, and key file paths are writable before attempting to fetch new certificates.

  • Pre-validates write permissions for X.509 certificate files, CA certificate files, and key files
  • Prevents agent from entering inconsistent state when certificate writes fail
  • Returns early if LocalCert.Use is true to skip file operations

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/config/validate-file-path.go New file implementing file path validation logic with write permission checks
pkg/certificate/service.go Added validation call in the certificate service run function before certificate requests

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

// validate dir path
target_path = dir_path
} else {
return fmt.Errorf("file path not exist: %w, %w", file_err, dir_err)
Copy link

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message has grammatical issues. Change 'file path not exist' to 'file path does not exist'.

Suggested change
return fmt.Errorf("file path not exist: %w, %w", file_err, dir_err)
return fmt.Errorf("file path does not exist: %w, %w", file_err, dir_err)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants