Skip to content

Commit

Permalink
Allow adding an external package file
Browse files Browse the repository at this point in the history
  • Loading branch information
zadjadr committed Jul 19, 2024
1 parent a51fb5b commit 8e74fe6
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 163 deletions.
75 changes: 70 additions & 5 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@

Prometheus CVE Exporter is a Golang application that scans your system for all installed packages and compares them with the recent [NVD JSON feed](https://nvd.nist.gov/vuln/data-feeds#JSON_FEED). It exports metrics that provide insights into the security status of your packages.

## TOC

- [Prometheus CVE Exporter](#prometheus-cve-exporter)
* [Features](#features)
* [Exported Metrics](#exported-metrics)
+ [Example output](#example-output)
* [Building](#building)
+ [Prerequisites](#prerequisites)
+ [Steps](#steps)
* [Usage](#usage)
+ [Binary without config](#binary-without-config)
+ [Binary with config](#binary-with-config)
+ [Docker](#docker)

## Features

- **Vulnerability Detection**: Identifies installed packages that have known vulnerabilities.
Expand All @@ -18,6 +32,25 @@ Prometheus CVE Exporter is a Golang application that scans your system for all i
| `nvd_total_vulnerabilities` | Gauge | Total number of vulnerabilities detected | None |
| `nvd_last_update_time` | Gauge | Timestamp of the last successful update | None |

### Example output

```
# HELP nvd_last_update_time Timestamp of the last successful update
# TYPE nvd_last_update_time gauge
nvd_last_update_time 1.7213802588068807e+09
# HELP nvd_total_vulnerabilities Total number of vulnerabilities detected
# TYPE nvd_total_vulnerabilities gauge
nvd_total_vulnerabilities 6
# HELP nvd_vulnerable_packages Indicates if a package is vulnerable (1) or not (metric not present)
# TYPE nvd_vulnerable_packages gauge
nvd_vulnerable_packages{cve="CVE-2024-21513",impact="HIGH",package="langchain-experimental",version="0.0.17"} 1
nvd_vulnerable_packages{cve="CVE-2024-6072",impact="MEDIUM",package="wp_estore",version="8.5.3"} 1
nvd_vulnerable_packages{cve="CVE-2024-6073",impact="MEDIUM",package="wp_estore",version="8.5.3"} 1
nvd_vulnerable_packages{cve="CVE-2024-6074",impact="MEDIUM",package="wp_estore",version="8.5.3"} 1
nvd_vulnerable_packages{cve="CVE-2024-6075",impact="HIGH",package="wp_estore",version="8.5.3"} 1
nvd_vulnerable_packages{cve="CVE-2024-6076",impact="MEDIUM",package="wp_estore",version="8.5.3"} 1
```

## Building

### Prerequisites
Expand Down Expand Up @@ -58,6 +91,8 @@ To customize the settings, use the following flags:
path to config file
-nvd-feed-url string
URL for the NVD feed (default "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-recent.json.gz")
-package-file string
Path to file containing packages and versions
-port int
Port to run the server on (default 10250)
-severity string
Expand All @@ -66,26 +101,56 @@ To customize the settings, use the following flags:
Update interval duration (default 24h0m0s)
```
Example:
### Binary without config
```sh
./bin/prometheus-cve-exporter -port 9090 -severity "HIGH,CRITICAL" -update-interval 12h
./bin/prometheus-cve-exporter -port 9090 -severity "HIGH,CRITICAL" -update-interval 12h -package-file /tmp/packages.txt
```

Example with config file:
### Binary with config

```json
{
"package_file": "/tmp/packages.txt",
"nvd_feed_url": "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-recent.json.gz",
"update_interval": "12h",
"port": 9090,
"update_interval": "5m",
"port": 8080,
"severity": [
"LOW",
"MEDIUM",
"HIGH",
"CRITICAL"
]
}

```

```sh
./bin/prometheus-cve-exporter -config config.json
```

### Docker

Released version tags are:

- latest
- major version (e.g. `v1`)
- `major.minor` version (e.g. `v1.1`)
- tag name (e.g. `v1.0.0`)

For the docker version, you will need to provide a `package-file`, otherwise the scanner will only
scan the container.

```shell
docker run -it -v $(pwd):/app -p 10250:10250 --rm ghcr.io/zadjadr/prometheus-cve-exporter:latest -- -package-file /app/packages.txt
```

```
Current configuration:
NVD Feed URL: https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-recent.json.gz
Update Interval: 24h0m0s
Severity Levels: [CRITICAL]
Port: 10250
Package file: packages.txt
Starting server on :10250
```
4 changes: 2 additions & 2 deletions cmd/prometheus-cve-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"log"
"net/http"

"io.ki/prometheus-cve-exporter/config"
"io.ki/prometheus-cve-exporter/internal/exporter"
"zops.top/prometheus-cve-exporter/config"
"zops.top/prometheus-cve-exporter/internal/exporter"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down
7 changes: 5 additions & 2 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"package_file": "",
"nvd_feed_url": "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-recent.json.gz",
"update_interval": "24h",
"port": 10250,
"update_interval": "5m",
"port": 8080,
"severity": [
"LOW",
"MEDIUM",
"HIGH",
"CRITICAL"
]
Expand Down
104 changes: 66 additions & 38 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ type Config struct {
UpdateInterval time.Duration `json:"update_interval"`
Port int `json:"port"`
Severity []string `json:"severity"`
PackageFile string `json:"package_file,omitempty"`
}

type configHelper struct {
NVDFeedURL string `json:"nvd_feed_url"`
UpdateInterval string `json:"update_interval"`
Port int `json:"port"`
Severity []string `json:"severity"`
PackageFile string `json:"package_file,omitempty"`
}

func NewConfig() *Config {
Expand All @@ -40,31 +42,40 @@ func NewConfig() *Config {
}

func Load() (*Config, error) {
cfg := NewConfig()
configFile := parseFlags(cfg)

if configFile != "" {
if err := loadConfigFile(cfg, configFile); err != nil {
return nil, fmt.Errorf("error loading config file: %w", err)
}
}

overrideWithEnv(cfg)

if err := validateConfig(cfg); err != nil {
return nil, err
}

fmt.Print(prettyfyCfg(cfg))
return cfg, nil
}

func parseFlags(cfg *Config) string {
var configFile string
flag.StringVar(&configFile, "config", "", "path to config file")

cfg := NewConfig()
flag.StringVar(&cfg.NVDFeedURL, "nvd-feed-url", defaultNVDFeedURL, "URL for the NVD feed")
flag.DurationVar(&cfg.UpdateInterval, "update-interval", defaultUpdateInterval, "Update interval duration")
flag.IntVar(&cfg.Port, "port", defaultPort, "Port to run the server on")
flag.StringVar(&cfg.PackageFile, "package-file", "", "Path to file containing packages and versions")

var severity string
flag.StringVar(&severity, "severity", defaultSeverity, "Comma separated list of severity levels for vulnerabilities")

flag.Parse()

cfg.Severity = parseSeverity(severity)

if configFile != "" {
if err := loadConfigFile(cfg, configFile); err != nil {
return nil, fmt.Errorf("error loading config file: %w", err)
}
}

cfg = overrideWithEnv(cfg)

prettyPrintCfg(cfg)
return cfg, nil
return configFile
}

func loadConfigFile(cfg *Config, filename string) error {
Expand All @@ -81,6 +92,7 @@ func loadConfigFile(cfg *Config, filename string) error {
cfg.NVDFeedURL = helper.NVDFeedURL
cfg.Port = helper.Port
cfg.Severity = helper.Severity
cfg.PackageFile = helper.PackageFile

duration, err := time.ParseDuration(helper.UpdateInterval)
if err != nil {
Expand All @@ -91,32 +103,41 @@ func loadConfigFile(cfg *Config, filename string) error {
return nil
}

func overrideWithEnv(cfg *Config) *Config {
if value := os.Getenv("PCE_NVD_JSON_GZ_FEED_URL"); value != "" {
cfg.NVDFeedURL = value
func overrideWithEnv(cfg *Config) {
envVars := map[string]func(string){
"PCE_NVD_JSON_GZ_FEED_URL": func(value string) { cfg.NVDFeedURL = value },
"PCE_UPDATE_INTERVAL": func(value string) {
if duration, err := time.ParseDuration(value); err == nil {
cfg.UpdateInterval = duration
} else {
fmt.Printf("Warning: invalid PCE_UPDATE_INTERVAL, using current value: %v\n", cfg.UpdateInterval)
}
},
"PCE_PORT": func(value string) {
if port, err := parseIntEnv(value); err == nil {
cfg.Port = port
} else {
fmt.Printf("Warning: invalid PCE_PORT, using current value: %d\n", cfg.Port)
}
},
"PCE_SEVERITY": func(value string) { cfg.Severity = parseSeverity(value) },
"PCE_PACKAGE_FILE": func(value string) { cfg.PackageFile = value },
}

if value := os.Getenv("PCE_UPDATE_INTERVAL"); value != "" {
if duration, err := time.ParseDuration(value); err == nil {
cfg.UpdateInterval = duration
} else {
fmt.Printf("Warning: invalid PCE_UPDATE_INTERVAL, using current value: %v\n", cfg.UpdateInterval)
for envVar, action := range envVars {
if value := os.Getenv(envVar); value != "" {
action(value)
}
}
}

if value := os.Getenv("PCE_PORT"); value != "" {
if port, err := parseIntEnv(value); err == nil {
cfg.Port = port
} else {
fmt.Printf("Warning: invalid PCE_PORT, using current value: %d\n", cfg.Port)
func validateConfig(cfg *Config) error {
if cfg.PackageFile != "" {
if _, err := os.Stat(cfg.PackageFile); os.IsNotExist(err) {
return fmt.Errorf("the file %s does not exist", cfg.PackageFile)
}
}

if value := os.Getenv("PCE_SEVERITY"); value != "" {
cfg.Severity = parseSeverity(value)
}

return cfg
return nil
}

func parseSeverity(severity string) []string {
Expand All @@ -129,10 +150,17 @@ func parseIntEnv(value string) (int, error) {
return result, err
}

func prettyPrintCfg(cfg *Config) {
fmt.Println("Current configuration:")
fmt.Printf(" NVD Feed URL: %s\n", cfg.NVDFeedURL)
fmt.Printf(" Update Interval: %s\n", cfg.UpdateInterval.String())
fmt.Printf(" Severity Levels: %v\n", cfg.Severity)
fmt.Printf(" Port: %d\n", cfg.Port)
func prettyfyCfg(cfg *Config) string {
var output strings.Builder

output.WriteString("Current configuration:\n")
output.WriteString(fmt.Sprintf(" NVD Feed URL: %s\n", cfg.NVDFeedURL))
output.WriteString(fmt.Sprintf(" Update Interval: %s\n", cfg.UpdateInterval.String()))
output.WriteString(fmt.Sprintf(" Severity Levels: %v\n", cfg.Severity))
output.WriteString(fmt.Sprintf(" Port: %d\n", cfg.Port))
if cfg.PackageFile != "" {
output.WriteString(fmt.Sprintf(" Package file: %s\n", cfg.PackageFile))
}

return output.String()
}
Loading

0 comments on commit 8e74fe6

Please sign in to comment.