Skip to content

Commit

Permalink
feat: add FailOnError
Browse files Browse the repository at this point in the history
  • Loading branch information
gi8lino committed Aug 8, 2023
1 parent 771691a commit 6255b27
Show file tree
Hide file tree
Showing 17 changed files with 127 additions and 54 deletions.
4 changes: 2 additions & 2 deletions cmd/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ to quickly create a Cobra application.`,

if printAll {
// Handle --all flag
output, err := print.EvaluateOutputFormat(outputFormat, config.App.Certs)
output, err := print.EvaluateOutputFormat(outputFormat, config.App.Certs, config.FailOnError)
if err != nil {
log.Error(err)
os.Exit(1)
Expand Down Expand Up @@ -81,7 +81,7 @@ to quickly create a Cobra application.`,
}

// Print the certificates
output, err := print.EvaluateOutputFormat(outputFormat, certs)
output, err := print.EvaluateOutputFormat(outputFormat, certs, config.FailOnError)
if err != nil {
log.Error(err)
os.Exit(1)
Expand Down
6 changes: 4 additions & 2 deletions cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ Examples:
config.App.Pushgateway.Job,
config.App.Pushgateway.Auth,
config.App.Certs,
config.App.Pushgateway.InsecureSkipVerify); err != nil {
config.App.Pushgateway.InsecureSkipVerify,
config.FailOnError); err != nil {
log.Panic(err)
}
return
Expand All @@ -80,7 +81,8 @@ Examples:
config.App.Pushgateway.Job,
config.App.Pushgateway.Auth,
[]certificates.Certificate{*certificate},
config.App.Pushgateway.InsecureSkipVerify); err != nil {
config.App.Pushgateway.InsecureSkipVerify,
config.FailOnError); err != nil {
log.Panic(err)
}
}
Expand Down
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&silent, "silent", "s", false, "silent output")
rootCmd.MarkFlagsMutuallyExclusive("verbose", "silent")

rootCmd.PersistentFlags().BoolVarP(&config.FailOnError, "fail-on-error", "f", false, "fail on error")
rootCmd.PersistentFlags().BoolVarP(&printVersion, "version", "V", false, "print version and exit")

}
Expand Down
51 changes: 35 additions & 16 deletions internal/certificates/jks.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,75 @@ import (
"github.com/pavlo-v-chernykh/keystore-go/v4"
)

// ExtractJksCertificatesInfo reads the JKS file, extracts certificate information, and returns a list of CertificateInfo
func ExtractJKSCertificatesInfo(name string, certData []byte, password string) ([]CertificateInfo, error) {
func ExtractJKSCertificatesInfo(name string, certData []byte, password string, failOnError bool) ([]CertificateInfo, error) {
var certInfoList []CertificateInfo

handleError := func(errMsg string) error {
if failOnError {
return fmt.Errorf(errMsg)
}
certInfoList = append(certInfoList, CertificateInfo{
Name: name,
Type: "jks",
Error: errMsg,
})
return nil
}

ks := keystore.New()
if err := ks.Load(bytes.NewReader(certData), []byte(password)); err != nil {
return nil, fmt.Errorf("Failed to load JKS file '%s': %w", name, err)
return certInfoList, handleError(fmt.Sprintf("Failed to load JKS file '%s': %v", name, err))
}

for _, alias := range ks.Aliases() {
var certificates []keystore.Certificate
// Check the entry type and get certificates accordingly

if ks.IsPrivateKeyEntry(alias) {
entry, err := ks.GetPrivateKeyEntry(alias, []byte(password))
if err != nil {
return nil, fmt.Errorf("Failed to get entries in JKS file '%s': %w", name, err)
if handleError(fmt.Sprintf("Failed to get private key in JKS file '%s': %v", name, err)) != nil {
return certInfoList, nil
}
continue
}
certificates = entry.CertificateChain
} else if ks.IsTrustedCertificateEntry(alias) {
entry, err := ks.GetTrustedCertificateEntry(alias)
if err != nil {
return nil, fmt.Errorf("Failed to get certificates in JKS file '%s': %w", name, err)
if handleError(fmt.Sprintf("Failed to get certificates in JKS file '%s': %v", name, err)) != nil {
return certInfoList, nil
}
continue
}
certificates = []keystore.Certificate{entry.Certificate}
} else {
return nil, fmt.Errorf("Unknown entry type for alias '%s' in JKS file '%s'", alias, name)
if handleError(fmt.Sprintf("Unknown entry type for alias '%s' in JKS file '%s'", alias, name)) != nil {
return certInfoList, nil
}
continue
}

// Iterate over all certificates in the entry
for idx, cert := range certificates {
for _, cert := range certificates {
certificate, err := x509.ParseCertificate(cert.Content)
if err != nil {
return nil, fmt.Errorf("Failed to parse certificate '%s': %w", name, err)
if handleError(fmt.Sprintf("Failed to parse certificate '%s': %v", name, err)) != nil {
return certInfoList, nil
}
continue
}

subject := certificate.Subject.CommonName
if subject == "" {
subject = fmt.Sprint(idx)
subject = fmt.Sprintf("%d", len(certInfoList)+1)
}

certInfo := CertificateInfo{
certInfoList = append(certInfoList, CertificateInfo{
Name: name,
Subject: subject,
Epoch: certificate.NotAfter.Unix(),
Type: "jks",
}

certInfoList = append(certInfoList, certInfo)
})
}
}

return certInfoList, nil
}
2 changes: 1 addition & 1 deletion internal/certificates/jks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestExtractJKSCertificatesInfo(t *testing.T) {
if err != nil {
t.Errorf("Failed to read certificate file '%s': %v", tc.Name, err)
}
certs, err := ExtractJKSCertificatesInfo("TestCert", certData, tc.Password)
certs, err := ExtractJKSCertificatesInfo("TestCert", certData, tc.Password, true)

if tc.ExpectedError == "" && err != nil {
t.Errorf("Test case '%s': unexpected error: %v", tc.Name, err)
Expand Down
19 changes: 16 additions & 3 deletions internal/certificates/p12.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,29 @@ import (
)

// ExtractP12CertificatesInfo reads the P12 file, extracts certificate information, and returns a list of CertificateInfo
func ExtractP12CertificatesInfo(name string, certData []byte, password string) ([]CertificateInfo, error) {
func ExtractP12CertificatesInfo(name string, certData []byte, password string, failOnError bool) ([]CertificateInfo, error) {
var certInfoList []CertificateInfo

handleError := func(errMsg string) error {
if failOnError {
return fmt.Errorf(errMsg)
}
certInfoList = append(certInfoList, CertificateInfo{
Name: name,
Type: "p12",
Error: errMsg,
})
return nil
}

// Decode the P12 data
_, certificate, caCerts, err := pkcs12.DecodeChain(certData, password)
if err != nil {
return nil, fmt.Errorf("Failed to decode P12 file '%s': %w", name, err)
return certInfoList, handleError(fmt.Sprintf("Failed to decode P12 file '%s': %v", name, err))
}

// Prepare for extraction
certs := append(caCerts, certificate)
var certInfoList []CertificateInfo
var counter int

// Extract certificates
Expand Down
2 changes: 1 addition & 1 deletion internal/certificates/p12_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestExtractP12CertificatesInfo(t *testing.T) {
if err != nil {
t.Errorf("Failed to read certificate file '%s': %v", tc.Name, err)
}
certs, err := ExtractP12CertificatesInfo("TestCert", certData, tc.Password)
certs, err := ExtractP12CertificatesInfo("TestCert", certData, tc.Password, true)

if tc.ExpectedError == "" && err != nil {
t.Errorf("Test case '%s': unexpected error: %v", tc.Name, err)
Expand Down
48 changes: 33 additions & 15 deletions internal/certificates/pem.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"

log "github.com/sirupsen/logrus"
)

// ExtractPEMCertificatesInfo reads the PEM file, extracts certificate information, and returns a list of CertificateInfo
func ExtractPEMCertificatesInfo(name string, certData []byte, password string) ([]CertificateInfo, error) {
// ExtractPEMCertificatesInfo extracts certificate information from the given PEM data
func ExtractPEMCertificatesInfo(name string, certData []byte, password string, failOnError bool) ([]CertificateInfo, error) {
var certInfoList []CertificateInfo
var counter int

handleError := func(errMsg string) error {
if failOnError {
return fmt.Errorf(errMsg)
}
certInfoList = append(certInfoList, CertificateInfo{
Name: name,
Type: "pem",
Error: errMsg,
})
return nil
}

// Decode PEM and extract certificates
for {
block, rest := pem.Decode(certData)
Expand All @@ -22,30 +32,36 @@ func ExtractPEMCertificatesInfo(name string, certData []byte, password string) (

// if block is a private key, try to parse it
if block.Type == "PRIVATE KEY" {
var err error
if password != "" {
_, err := x509.DecryptPEMBlock(block, []byte(password))
if err != nil {
log.Warningf("Failed to decrypt private key '%s': %v", name, err)
}
_, err = x509.DecryptPEMBlock(block, []byte(password))
} else {
_, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
log.Warningf("Failed to parse private key '%s': %v", name, err)
_, err = x509.ParsePKCS8PrivateKey(block.Bytes)
}
if err != nil {
if handleError(fmt.Sprintf("Failed to parse/decrypt private key '%s': %v", name, err)) != nil {
return certInfoList, err
}
certData = rest
continue
}
}

// skip if is not a certificate
if block.Type != "CERTIFICATE" {
certData = rest // Move to the next PEM block
certData = rest
continue
}

counter++

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("Failed to parse certificate '%s': %w", name, err)
if handleError(fmt.Sprintf("Failed to parse certificate '%s': %v", name, err)) != nil {
return certInfoList, err
}
certData = rest
continue
}

subject := cert.Subject.CommonName
Expand All @@ -64,8 +80,10 @@ func ExtractPEMCertificatesInfo(name string, certData []byte, password string) (

certData = rest // Move to the next PEM block
}
if certInfoList == nil {
return nil, fmt.Errorf("Failed to decode certificate '%s'", name)
if len(certInfoList) == 0 {
if err := handleError(fmt.Sprintf("Failed to decode any certificate in '%s'", name)); err != nil {
return certInfoList, err
}
}

return certInfoList, nil
Expand Down
6 changes: 3 additions & 3 deletions internal/certificates/pem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestExtractPEMCertificatesInfo(t *testing.T) {
FilePath: "../../tests/certs/pem/broken.pem",
Password: "",
ExpectedResults: []CertificateInfo{},
ExpectedError: "Failed to decode certificate 'TestCert'",
ExpectedError: "Failed to decode any certificate in 'TestCert'",
},
{
Name: "Test PEM certificate with wrong password",
Expand All @@ -69,7 +69,7 @@ func TestExtractPEMCertificatesInfo(t *testing.T) {
{
Name: "Test PEM certificate with chain",
FilePath: "../../tests/certs/pem/chain.pem",
Password: "password",
Password: "",
ExpectedResults: []CertificateInfo{
{
Name: "TestCert",
Expand Down Expand Up @@ -102,7 +102,7 @@ func TestExtractPEMCertificatesInfo(t *testing.T) {
if err != nil {
t.Errorf("Failed to read certificate file '%s': %v", tc.Name, err)
}
certs, err := ExtractPEMCertificatesInfo("TestCert", certData, tc.Password)
certs, err := ExtractPEMCertificatesInfo("TestCert", certData, tc.Password, true)

if tc.ExpectedError == "" && err != nil {
t.Errorf("Test case '%s': unexpected error: %v", tc.Name, err)
Expand Down
1 change: 1 addition & 0 deletions internal/certificates/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type CertificateInfo struct {
Subject string `json:"subject"`
Epoch int64 `json:"epoch"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
}

// ExpiryAsTime returns the expiry date as a time.Time
Expand Down
12 changes: 7 additions & 5 deletions internal/certificates/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func GetCertificateByName(name string, certificates []Certificate) (*Certificate
}

// Process extracts certificate information from the certificates and updates the Prometheus metrics
func Process(certificates []Certificate) (certificatesInfo []CertificateInfo, err error) {
func Process(certificates []Certificate, failOnError bool) (certificatesInfo []CertificateInfo, err error) {
for _, cert := range certificates {
if cert.Enabled != nil && !*cert.Enabled {
log.Debugf("Skip certificate '%s' as it is disabled", cert.Name)
Expand All @@ -27,23 +27,25 @@ func Process(certificates []Certificate) (certificatesInfo []CertificateInfo, er
log.Debugf("Processing certificate '%s'", cert.Name)

var certificateInfo []CertificateInfo

certData, err := os.ReadFile(cert.Path)
if err != nil {
return nil, fmt.Errorf("Failed to read certificate file '%s': %w", cert.Path, err)
}

switch cert.Type {
case "p12", "pkcs12", "pfx":
certificateInfo, err = ExtractP12CertificatesInfo(cert.Name, certData, cert.Password)
certificateInfo, err = ExtractP12CertificatesInfo(cert.Name, certData, cert.Password, failOnError)
case "pem", "crt":
certificateInfo, err = ExtractPEMCertificatesInfo(cert.Name, certData, cert.Password)
certificateInfo, err = ExtractPEMCertificatesInfo(cert.Name, certData, cert.Password, failOnError)
case "jks":
certificateInfo, err = ExtractJKSCertificatesInfo(cert.Name, certData, cert.Password)
certificateInfo, err = ExtractJKSCertificatesInfo(cert.Name, certData, cert.Password, failOnError)
default:
// Cannot happen, as the config is validated before
// Only here to make the linter happy :)
return nil, fmt.Errorf("Unknown certificate type '%s'", cert.Type)
}
if err != nil {
// err is only returned if failOnError is true
return nil, fmt.Errorf("Error extracting certificate information: %v", err)
}
certificatesInfo = append(certificatesInfo, certificateInfo...)
Expand Down
2 changes: 2 additions & 0 deletions internal/config/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ var App Config
// ConfigCopy represents the config file with sensitive data redacted
var AppCopy Config

var FailOnError bool

// Config represents the config file
type Config struct {
Server Server `json:"server"`
Expand Down
2 changes: 1 addition & 1 deletion internal/handlers/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func HomeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")

certificatesInfo, err := certificates.Process(config.App.Certs)
certificatesInfo, err := certificates.Process(config.App.Certs, config.FailOnError)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand Down
2 changes: 1 addition & 1 deletion internal/handlers/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

func MetricsHandler(w http.ResponseWriter, r *http.Request) {
certificateInfos, err := certificates.Process(config.App.Certs)
certificateInfos, err := certificates.Process(config.App.Certs, config.FailOnError)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand Down
Loading

0 comments on commit 6255b27

Please sign in to comment.