Skip to content

Commit

Permalink
Merge pull request #37 from wbollock/ci/golanglint
Browse files Browse the repository at this point in the history
ci: add golanglintci precommit/gh action
  • Loading branch information
wbollock authored Nov 26, 2024
2 parents 92da4de + de05ea5 commit 6a98a64
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 35 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/golangci-lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- main
pull_request:

permissions:
contents: read

jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.21
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
43 changes: 43 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
repos:
- repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-rc.1
hooks:
#
# Go Build
#
- id: go-build-mod
- id: go-build-repo-mod
#
# Go Mod Tidy
#
- id: go-mod-tidy
- id: go-mod-tidy-repo
#
# Go Test
#
- id: go-test-mod
- id: go-test-repo-mod
#
# Formatters
#
- id: go-fmt
#
#
#
# Style Checkers
#
- id: go-lint
#
# GolangCI-Lint
# local hook from https://github.com/golangci/golangci-lin>
# other versions are outdated
- repo: local
hooks:
- id: golangci-lint
name: golangci-lint
description: Fast linters runner for Go. Note that only >
entry: golangci-lint run --new-from-rev HEAD --fix
types: [go]
language: golang
require_serial: true
pass_filenames: false
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ Right now the exporter will find any Vault PKI secrets engines and attempt to ge

## Contributing

Make sure run `pre-commit install` to install the various pre-commit linter and formatting hooks.

### Testing

Venom is used for tests, run `sudo venom run tests.yml` to perform integration tests. Make sure you have at least venom version 1.2.0.
Expand Down
7 changes: 5 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ func init() {
}

func main() {
cli.ParseFlags(os.Args[1:])
err := cli.ParseFlags(os.Args[1:])
if err != nil {
logger.SlogFatal("CLI parsing failed", "error", err)
}

// preserve deprecated verbose flag
if viper.GetBool("verbose") {
Expand All @@ -98,7 +101,7 @@ func main() {
// note mix of underscores and dashes
slog.Info("CLI flag values", "fetch-interval", viper.GetDuration("fetch_interval"), "refresh-interval", viper.GetDuration("refresh_interval"), "batch-size-percent", viper.GetFloat64("batch_size_percent"), "request-limit", viper.GetFloat64("request_limit"), "request-limit-burst", viper.GetInt("request_limit_burst"))

err := cli.Execute()
err = cli.Execute()
if err != nil {
logger.SlogFatal("CLI execution failed", "error", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"
)

// Initialize default logger
// Init initializes default logger
func Init(level string) {
var slogLevel slog.Level
switch level {
Expand Down
3 changes: 2 additions & 1 deletion pkg/vault-mon/influx.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package vault_mon
package vaultmon

import (
"crypto/x509"
Expand All @@ -13,6 +13,7 @@ import (

var hostname string

// InfluxWatchCerts looks for new certificate data
func InfluxWatchCerts(pkimon *PKIMon, interval time.Duration, loop bool) {
hostname = os.Getenv("HOSTNAME")
if hostname == "" {
Expand Down
9 changes: 8 additions & 1 deletion pkg/vault-mon/pki.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package vault_mon
package vaultmon

import (
"context"
Expand All @@ -18,6 +18,7 @@ import (
"golang.org/x/time/rate"
)

// PKI has details about the Vault PKI Secrets Engine
type PKI struct {
path string
certs map[string]map[string]*x509.Certificate
Expand All @@ -29,6 +30,7 @@ type PKI struct {
crlmux sync.Mutex
}

// PKIMon helps watch all the possible PKI secrets engines
type PKIMon struct {
pkis map[string]*PKI
vault *vaultapi.Client
Expand All @@ -48,6 +50,7 @@ var loadCertsLimitDuration = promauto.NewHistogram(prometheus.HistogramOpts{
Buckets: prometheus.ExponentialBuckets(1, 3, 10),
})

// Init makes a new Vault client
func (mon *PKIMon) Init(vault *vaultapi.Client) error {
mon.vault = vault
mon.pkis = make(map[string]*PKI)
Expand Down Expand Up @@ -79,6 +82,7 @@ func (mon *PKIMon) loadPKI() error {
return nil
}

// Watch is a continuous loop to load cert and CRL data for later analysis
func (mon *PKIMon) Watch(interval time.Duration) {
slog.Info("Start watching PKI certs")

Expand Down Expand Up @@ -111,6 +115,7 @@ func (mon *PKIMon) Watch(interval time.Duration) {
}()
}

// GetPKIs returns PKI engines
func (mon *PKIMon) GetPKIs() map[string]*PKI {
mon.mux.Lock()
defer mon.mux.Unlock()
Expand Down Expand Up @@ -371,12 +376,14 @@ func (pki *PKI) clearCerts() {
pki.certsmux.Unlock()
}

// GetCRLs is a thread safe way to return all CRLs used by various PKI engines
func (pki *PKI) GetCRLs() map[string]*x509.RevocationList {
pki.crlmux.Lock()
defer pki.crlmux.Unlock()
return pki.crls
}

// GetCerts is a thread safe way to return all certs used by various PKI engines
func (pki *PKI) GetCerts() map[string]map[string]*x509.Certificate {
pki.certsmux.Lock()
defer pki.certsmux.Unlock()
Expand Down
24 changes: 13 additions & 11 deletions pkg/vault-mon/prometheus.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package vault_mon
package vaultmon

import (
"crypto/x509"
Expand All @@ -25,6 +25,7 @@ var labelNames = []string{
"locality",
}

// PromWatchCerts goes through all available certificates and updates metrics about them. Also deletes any certificate time series found in various CRLs
func PromWatchCerts(pkimon *PKIMon, interval time.Duration) {
expiry := promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "x509_cert_expiry",
Expand All @@ -42,21 +43,21 @@ func PromWatchCerts(pkimon *PKIMon, interval time.Duration) {
Name: "x509_cert_count",
Help: "Total count of non-expired certificates including revoked certificates",
}, []string{"source"})
expired_cert_count := promauto.NewGaugeVec(prometheus.GaugeOpts{
expiredCertCount := promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "x509_expired_cert_count",
Help: "Total count of expired certificates including revoked certificates",
}, []string{"source"})
crl_expiry := promauto.NewGaugeVec(prometheus.GaugeOpts{
crlExpiry := promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "x509_crl_expiry",
}, []string{"source", "issuer"})
crl_nextupdate := promauto.NewGaugeVec(prometheus.GaugeOpts{
crlNextupdate := promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "x509_crl_nextupdate",
}, []string{"source", "issuer"})
crl_byte_size := promauto.NewGaugeVec(prometheus.GaugeOpts{
crlByteSize := promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "x509_crl_byte_size",
Help: "Size of raw certificate revocation list pem stored in vault",
}, []string{"source", "issuer"})
crl_length := promauto.NewGaugeVec(prometheus.GaugeOpts{
crlLength := promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "x509_crl_length",
Help: "Length of certificate revocation list",
}, []string{"source", "issuer"})
Expand All @@ -81,10 +82,10 @@ func PromWatchCerts(pkimon *PKIMon, interval time.Duration) {
if crl != nil {
issuer := crl.Issuer.CommonName

crl_expiry.WithLabelValues(pkiname, issuer).Set(float64(crl.NextUpdate.Sub(now).Seconds()))
crl_nextupdate.WithLabelValues(pkiname, issuer).Set(float64(crl.NextUpdate.Unix()))
crl_length.WithLabelValues(pkiname, issuer).Set(float64(len(crl.RevokedCertificateEntries)))
crl_byte_size.WithLabelValues(pkiname, issuer).Set(float64(pki.crlRawSize))
crlExpiry.WithLabelValues(pkiname, issuer).Set(float64(crl.NextUpdate.Sub(now).Seconds()))
crlNextupdate.WithLabelValues(pkiname, issuer).Set(float64(crl.NextUpdate.Unix()))
crlLength.WithLabelValues(pkiname, issuer).Set(float64(len(crl.RevokedCertificateEntries)))
crlByteSize.WithLabelValues(pkiname, issuer).Set(float64(pki.crlRawSize))

slog.Debug("Updated CRL metrics", "pki", pkiname, "issuer", issuer, "next_update", crl.NextUpdate)

Expand Down Expand Up @@ -124,7 +125,7 @@ func PromWatchCerts(pkimon *PKIMon, interval time.Duration) {
slog.Debug("Updated certificate metrics", "pki", pkiname, "serial", cert.SerialNumber, "common_name", cert.Subject.CommonName, "organizational_unit", cert.Subject.OrganizationalUnit)
}
certcount.WithLabelValues(pkiname).Set(float64(len(pki.certs)))
expired_cert_count.WithLabelValues(pkiname).Set(float64(pki.expiredCertsCounter))
expiredCertCount.WithLabelValues(pkiname).Set(float64(pki.expiredCertsCounter))

}
duration := time.Since(startTime).Seconds()
Expand All @@ -149,6 +150,7 @@ func getLabelValues(pkiname string, cert *x509.Certificate) []string {
}
}

// PromStartExporter boots up the HTTP server to provide metrics
func PromStartExporter(port int) {
slog.Info("Starting Prometheus exporter", "port", port)
http.Handle("/metrics", promhttp.Handler())
Expand Down
2 changes: 1 addition & 1 deletion pkg/vault-mon/tools.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package vault_mon
package vaultmon

func getEmptyStringIfEmpty(data []string) string {
if len(data) > 0 {
Expand Down
44 changes: 26 additions & 18 deletions pkg/vault/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,31 @@ import (
)

type secretCallback func(secret *vaultapi.Secret)
type secretKV2Callback func(secret *KV_version2)
type secretKV2Callback func(secret *KVVersion2)

// ClientWrapper wraps around vaultapi client
type ClientWrapper struct {
Client *vaultapi.Client
}

type KV_version2 struct {
// KVVersion2 is Vault's newer API version structure
type KVVersion2 struct {
Data map[string]interface{} `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}

// SecretList is a list of keys
type SecretList struct {
Keys []string
}

// SecretCertificate is an object of the actual certificate we obtain from Vault
type SecretCertificate struct {
Certificate string
Revocation_time int64
Certificate string
RevocationTime int64
}

// Init makes a new Vault Client Wrapper
func (vault *ClientWrapper) Init() {
var err error

Expand All @@ -57,36 +63,37 @@ func (vault *ClientWrapper) Init() {
}
}

token_secret, err := vault.Client.Auth().Token().LookupSelf()
tokenSecret, err := vault.Client.Auth().Token().LookupSelf()
if err != nil {
logger.SlogFatal("[vault] Error getting a new token", err)
}
ttl, _ := token_secret.TokenTTL()
ttl, _ := tokenSecret.TokenTTL()

slog.Info("Token TTL and LeaseDuration", "ttl", int32(ttl/time.Second), "lease_duration", token_secret.LeaseDuration)
slog.Info("Token TTL and LeaseDuration", "ttl", int32(ttl/time.Second), "lease_duration", tokenSecret.LeaseDuration)

isRenewable, _ := token_secret.TokenIsRenewable()
isRenewable, _ := tokenSecret.TokenIsRenewable()
if isRenewable {
// Get a renewed token
secret, err := vault.Client.Auth().Token().RenewTokenAsSelf(vault.Client.Token(), 0)
if err != nil {
logger.SlogFatal("[vault] Error renewing token", err)
}

token_renewer, err := vault.Client.NewRenewer(&vaultapi.RenewerInput{
tokenRenewer, err := vault.Client.NewLifetimeWatcher(&vaultapi.RenewerInput{
Secret: secret,
})
if err != nil {
logger.SlogFatal("[vault] Error renewing token", err)
}

watch_renewer_vault(token_renewer)
watchRenewerVault(tokenRenewer)
} else {
ttl, _ := token_secret.TokenTTL()
ttl, _ := tokenSecret.TokenTTL()
slog.Info("[vault] token is not renewable", "ttl", int32(ttl/time.Second))
}
}

// GetSecret finds the secrets to authenticate with Vault
func (vault *ClientWrapper) GetSecret(path string, fn secretCallback) error {
var secret *vaultapi.Secret
var err error
Expand All @@ -101,14 +108,14 @@ func (vault *ClientWrapper) GetSecret(path string, fn secretCallback) error {
fn(secret)

if secret.Renewable {
renewer, err := vault.Client.NewRenewer(&vaultapi.RenewerInput{
renewer, err := vault.Client.NewLifetimeWatcher(&vaultapi.RenewerInput{
Secret: secret,
})
if err != nil {
logger.SlogFatal("[vault] Error renewing token", err)
}

watch_renewer_vault(renewer)
watchRenewerVault(renewer)
} else {
slog.Info("[vault] secret is not renewable, use TTL to refresh secret", "path", path)
// Refresh secret at the end of Lease
Expand All @@ -135,18 +142,19 @@ func (vault *ClientWrapper) GetSecret(path string, fn secretCallback) error {
return nil
}

// GetSecretKV2 gets the Vault auth secret for the KV2 API
func (vault *ClientWrapper) GetSecretKV2(path string, fn secretKV2Callback) error {
var secret_kv2 = &KV_version2{}
var secretKv2 = &KVVersion2{}
return vault.GetSecret(path, func(secret *vaultapi.Secret) {
err := mapstructure.WeakDecode(secret.Data, secret_kv2)
err := mapstructure.WeakDecode(secret.Data, secretKv2)
if err != nil {
slog.Error("Can decode secret as KV version 2", "error", err)
slog.Error("Cannot decode secret for KV version 2", "error", err)
}
fn(secret_kv2)
fn(secretKv2)
})
}

func watch_renewer_vault(renewer *vaultapi.Renewer) {
func watchRenewerVault(renewer *vaultapi.Renewer) {
go func() {
for {
select {
Expand Down

0 comments on commit 6a98a64

Please sign in to comment.