feat: add NATS NKey authentication support

Allow authentication to NATS using NKey seed files as an alternative to
credentials files. NKeys use Ed25519 key pairs for authentication.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 00:32:23 +01:00
parent acfb142788
commit 5aa5f7275b
5 changed files with 23 additions and 3 deletions

View File

@@ -48,6 +48,7 @@ nix build
| `--flake.nats.url` | `nats://localhost:4222` | NATS server URL | | `--flake.nats.url` | `nats://localhost:4222` | NATS server URL |
| `--flake.nats.subject` | `nixos-exporter.remote-rev` | NATS subject for revision updates | | `--flake.nats.subject` | `nixos-exporter.remote-rev` | NATS subject for revision updates |
| `--flake.nats.credentials-file` | | NATS credentials file (optional) | | `--flake.nats.credentials-file` | | NATS credentials file (optional) |
| `--flake.nats.nkey-seed-file` | | NATS NKey seed file (optional) |
## NixOS Module Options ## NixOS Module Options
@@ -68,6 +69,7 @@ services.prometheus.exporters.nixos = {
url = "nats://localhost:4222"; url = "nats://localhost:4222";
subject = "nixos-exporter.remote-rev"; subject = "nixos-exporter.remote-rev";
credentialsFile = null; # Optional path to credentials file credentialsFile = null; # Optional path to credentials file
nkeySeedFile = null; # Optional path to NKey seed file
}; };
}; };
}; };

View File

@@ -33,6 +33,7 @@ type FlakeCollectorConfig struct {
NATSURL string NATSURL string
NATSSubject string NATSSubject string
NATSCredentialsFile string NATSCredentialsFile string
NATSNkeySeedFile string
} }
// nixosVersionInfo holds the parsed output of nixos-version --json // nixosVersionInfo holds the parsed output of nixos-version --json
@@ -107,7 +108,7 @@ func NewFlakeCollectorWithNATS(cfg FlakeCollectorConfig) (*FlakeCollector, error
c.natsEnabled = true c.natsEnabled = true
c.natsSubject = cfg.NATSSubject c.natsSubject = cfg.NATSSubject
if err := c.connectNATS(cfg.NATSURL, cfg.NATSCredentialsFile); err != nil { if err := c.connectNATS(cfg.NATSURL, cfg.NATSCredentialsFile, cfg.NATSNkeySeedFile); err != nil {
// Log warning but continue without NATS // Log warning but continue without NATS
slog.Warn("Failed to connect to NATS, continuing without cache sharing", "error", err) slog.Warn("Failed to connect to NATS, continuing without cache sharing", "error", err)
} }
@@ -333,7 +334,7 @@ func getNixosVersionInfo() (*nixosVersionInfo, error) {
} }
// connectNATS establishes connection to NATS server with auto-reconnect // connectNATS establishes connection to NATS server with auto-reconnect
func (c *FlakeCollector) connectNATS(url, credentialsFile string) error { func (c *FlakeCollector) connectNATS(url, credentialsFile, nkeySeedFile string) error {
opts := []nats.Option{ opts := []nats.Option{
nats.MaxReconnects(-1), // Infinite reconnects nats.MaxReconnects(-1), // Infinite reconnects
nats.ReconnectWait(5 * time.Second), nats.ReconnectWait(5 * time.Second),
@@ -363,6 +364,12 @@ func (c *FlakeCollector) connectNATS(url, credentialsFile string) error {
if credentialsFile != "" { if credentialsFile != "" {
opts = append(opts, nats.UserCredentials(credentialsFile)) opts = append(opts, nats.UserCredentials(credentialsFile))
} else if nkeySeedFile != "" {
opt, err := nats.NkeyOptionFromSeed(nkeySeedFile)
if err != nil {
return fmt.Errorf("failed to load NKey seed file: %w", err)
}
opts = append(opts, opt)
} }
nc, err := nats.Connect(url, opts...) nc, err := nats.Connect(url, opts...)

View File

@@ -17,6 +17,7 @@ type Config struct {
FlakeNATSURL string FlakeNATSURL string
FlakeNATSSubject string FlakeNATSSubject string
FlakeNATSCredentialsFile string FlakeNATSCredentialsFile string
FlakeNATSNkeySeedFile string
} }
func Parse() (*Config, error) { func Parse() (*Config, error) {
@@ -32,6 +33,7 @@ func Parse() (*Config, error) {
flag.StringVar(&cfg.FlakeNATSURL, "flake.nats.url", "nats://localhost:4222", "NATS server URL") flag.StringVar(&cfg.FlakeNATSURL, "flake.nats.url", "nats://localhost:4222", "NATS server URL")
flag.StringVar(&cfg.FlakeNATSSubject, "flake.nats.subject", "nixos-exporter.remote-rev", "NATS subject for revision updates") flag.StringVar(&cfg.FlakeNATSSubject, "flake.nats.subject", "nixos-exporter.remote-rev", "NATS subject for revision updates")
flag.StringVar(&cfg.FlakeNATSCredentialsFile, "flake.nats.credentials-file", "", "NATS credentials file (optional)") flag.StringVar(&cfg.FlakeNATSCredentialsFile, "flake.nats.credentials-file", "", "NATS credentials file (optional)")
flag.StringVar(&cfg.FlakeNATSNkeySeedFile, "flake.nats.nkey-seed-file", "", "NATS NKey seed file (optional)")
flag.Parse() flag.Parse()

View File

@@ -15,7 +15,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
) )
const version = "0.3.0" const version = "0.4.0"
func main() { func main() {
cfg, err := config.Parse() cfg, err := config.Parse()
@@ -40,6 +40,7 @@ func main() {
NATSURL: cfg.FlakeNATSURL, NATSURL: cfg.FlakeNATSURL,
NATSSubject: cfg.FlakeNATSSubject, NATSSubject: cfg.FlakeNATSSubject,
NATSCredentialsFile: cfg.FlakeNATSCredentialsFile, NATSCredentialsFile: cfg.FlakeNATSCredentialsFile,
NATSNkeySeedFile: cfg.FlakeNATSNkeySeedFile,
}) })
if err != nil { if err != nil {
slog.Error("Failed to create flake collector", "error", err) slog.Error("Failed to create flake collector", "error", err)

View File

@@ -58,6 +58,12 @@ in
default = null; default = null;
description = "Path to NATS credentials file."; description = "Path to NATS credentials file.";
}; };
nkeySeedFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Path to NATS NKey seed file.";
};
}; };
}; };
@@ -133,6 +139,8 @@ in
"--flake.nats.subject=${cfg.flake.nats.subject}" "--flake.nats.subject=${cfg.flake.nats.subject}"
] ++ lib.optionals (cfg.flake.nats.enable && cfg.flake.nats.credentialsFile != null) [ ] ++ lib.optionals (cfg.flake.nats.enable && cfg.flake.nats.credentialsFile != null) [
"--flake.nats.credentials-file=${cfg.flake.nats.credentialsFile}" "--flake.nats.credentials-file=${cfg.flake.nats.credentialsFile}"
] ++ lib.optionals (cfg.flake.nats.enable && cfg.flake.nats.nkeySeedFile != null) [
"--flake.nats.nkey-seed-file=${cfg.flake.nats.nkeySeedFile}"
]); ]);
Restart = "on-failure"; Restart = "on-failure";
RestartSec = "5s"; RestartSec = "5s";