Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to parse both pkcs12 truststore and keystore formats #315

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ __pycache__
*.debug
coverage.html
/kubeconfig*
passwords.yml
/x509-certificate-exporter
4 changes: 4 additions & 0 deletions cmd/x509-certificate-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ func main() {
kubeExcludeLabels := stringArrayFlag{}
getopt.FlagLong(&kubeExcludeLabels, "exclude-label", 0, "removes the kube secrets with the given label (or label value if specified) from the watch list (applied after --include-label)")

var PasswordsFile string
getopt.FlagLong(&PasswordsFile, "passwords-file", 0, "path to a yaml file containing a list of passwords to try when opening a p12 file")

getopt.Parse()

if *help {
Expand Down Expand Up @@ -145,6 +148,7 @@ func main() {
KubeExcludeNamespaces: kubeExcludeNamespaces,
KubeIncludeLabels: kubeIncludeLabels,
KubeExcludeLabels: kubeExcludeLabels,
PasswordsFile: PasswordsFile,
}

if getopt.Lookup("expose-labels").Seen() {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ require (
github.com/stretchr/testify v1.10.0
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
go.uber.org/automaxprocs v1.6.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.32.0
k8s.io/apimachinery v0.32.0
k8s.io/client-go v0.32.0
software.sslmate.com/src/go-pkcs12 v0.5.0
)

require (
Expand Down Expand Up @@ -72,7 +74,6 @@ require (
google.golang.org/protobuf v1.35.2 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aN
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M=
software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
32 changes: 32 additions & 0 deletions internal/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/yalp/jsonpath"
"gopkg.in/yaml.v3"
v1 "k8s.io/api/core/v1"
"software.sslmate.com/src/go-pkcs12"
)

// YAMLCertRef : Contains information to access certificates in yaml files
Expand Down Expand Up @@ -69,6 +70,8 @@ type certificateRef struct {
kubeSecret v1.Secret
kubeConfigMap v1.ConfigMap
kubeSecretKey string
password string

}

type parsedCertificate struct {
Expand All @@ -89,6 +92,7 @@ const (
certificateFormatYAML = iota
certificateFormatKubeSecret = iota
certificateFormatKubeConfigMap = iota
certificateFormatP12 = iota
)

func (cert *certificateRef) parse() error {
Expand All @@ -103,6 +107,8 @@ func (cert *certificateRef) parse() error {
cert.certificates, err = readAndParseKubeSecret(&cert.kubeSecret, cert.kubeSecretKey)
case certificateFormatKubeConfigMap:
cert.certificates, err = readAndParseKubeConfigMap(&cert.kubeConfigMap, cert.kubeSecretKey)
case certificateFormatP12:
cert.certificates, err = readAndParsePasswordPkcsFile(cert.path, cert.password)
}
return err
}
Expand All @@ -126,6 +132,32 @@ func readAndParsePEMFile(path string) ([]*parsedCertificate, error) {
return output, nil
}

func readAndParsePasswordPkcsFile(path string, password string) ([]*parsedCertificate, error) {
contents, err := readFile(path)
if err != nil {
return nil, err
}

output := []*parsedCertificate{}
// keystore p12
_, cert, err := pkcs12.Decode(contents, password)
if err == nil {
output = append(output, &parsedCertificate{cert: cert})
return output, nil
}

// truststore p12
certs, err := pkcs12.DecodeTrustStore(contents, password)
if err != nil {
return nil, err
}

for _, cert := range certs {
output = append(output, &parsedCertificate{cert: cert})
}
return output, nil
}

func readAndParseYAMLFile(filePath string, yamlPaths []YAMLCertRef) ([]*parsedCertificate, error) {
output := []*parsedCertificate{}

Expand Down
57 changes: 55 additions & 2 deletions internal/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/bmatcuk/doublestar/v4"
"github.com/prometheus/exporter-toolkit/web"
"gopkg.in/yaml.v2"
"k8s.io/client-go/kubernetes"
)

Expand Down Expand Up @@ -52,6 +53,7 @@ type Exporter struct {
isDiscovery bool
secretsCache *cache.Cache
configMapsCache *cache.Cache
PasswordsFile string
}

type KubeSecretType struct {
Expand Down Expand Up @@ -285,9 +287,28 @@ func (exporter *Exporter) collectMatchingPaths(pattern string, format certificat
continue
}

var password string
if strings.HasSuffix(file.Name(), ".p12") || strings.HasSuffix(file.Name(), ".jks") {
format = certificateFormatP12
password, err = exporter.obtainP12Passwords(path.Clean(path.Join(dir, file.Name())))
if err != nil {
outputErrors = append(outputErrors, err)
continue
}
} else {
if strings.HasSuffix(file.Name(), ".crt") ||
strings.HasSuffix(file.Name(), ".pem") ||
strings.HasSuffix(file.Name(), ".cert") {
format = certificateFormatPEM
} else {
continue
}
}

output = append(output, &certificateRef{
path: path.Clean(path.Join(dir, file.Name())),
format: certificateFormatPEM,
path: path.Clean(path.Join(dir, file.Name())),
format: format,
password: password,
})
}
} else {
Expand Down Expand Up @@ -340,6 +361,38 @@ func (exporter *Exporter) collectMatchingPaths(pattern string, format certificat
return output, outputErrors
}

func (exporter *Exporter) obtainP12Passwords(filename string) (string, error) {
filename = filepath.Base(filename)
if len(exporter.PasswordsFile) == 0 {
return "", errors.New("password file not specified")
}

passwordsFile, err := os.ReadFile(exporter.PasswordsFile)
if err != nil {
return "", err
}
type P12Config struct {
Name string `yaml:"name"`
Password string `yaml:"password"`
}
type Config struct {
P12 []P12Config `yaml:"pkcs12"`
}
var config Config
if err = yaml.Unmarshal(passwordsFile, &config); err != nil {
return "", err
}

for _, p12 := range config.P12 {
if p12.Name == filename {
return p12.Password, nil
}
}

return "", errors.New("p12 password not found")
}


// compareCertificates compares labels of these two certificates
// and returns true if they are the same
// It would normally run `.getLabels` on both cert/ref combinations,
Expand Down
6 changes: 6 additions & 0 deletions passwords.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
pkcs12:
- name: "keystore.p12"
password: "mysecretpassword1"
- name: "truststore.p12"
password: "mysecretpassword2"
Loading