You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

132 lines
4.5 KiB

package snmpagent
import (
"context"
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
type recordingRunner struct {
actions []string
fail bool
}
func (r *recordingRunner) Run(_ context.Context, _ string, args ...string) ([]byte, error) {
r.actions = append(r.actions, strings.Join(args, " "))
if r.fail && len(r.actions) == 1 {
return []byte("restart failed"), errors.New("exit status 1")
}
return nil, nil
}
func TestOSControllerRendersVersionedCommunitiesAndV3Users(t *testing.T) {
directory := t.TempDir()
mainPath := filepath.Join(directory, "etc", "snmpd.conf")
persistentPath := filepath.Join(directory, "state", "snmpd.conf")
runner := &recordingRunner{}
controller, err := NewOSController(OSOptions{
ConfigPath: mainPath, PersistentPath: persistentPath,
InitScriptPath: filepath.Join(directory, "S59snmpd"), Runner: runner,
})
if err != nil {
t.Fatal(err)
}
config := Config{
Enabled: true, Location: `Rack "A"`, Contact: "noc@example.local",
V1Enabled: true, V2CEnabled: true, V3Enabled: true,
ReadOnlyCommunity: "read-only-secret", ReadWriteCommunity: "write-secret",
V3Users: []User{{
Username: "snmp-admin", Access: "write", AuthProtocol: "sha256", PrivacyProtocol: "aes",
AuthKey: "auth-secret-123", PrivacyKey: "privacy-secret-123", Enabled: true,
}},
}
if err := controller.Apply(context.Background(), config); err != nil {
t.Fatal(err)
}
mainConfig, err := os.ReadFile(mainPath)
if err != nil {
t.Fatal(err)
}
persistentConfig, err := os.ReadFile(persistentPath)
if err != nil {
t.Fatal(err)
}
mainText := string(mainConfig)
for _, expected := range []string{
"agentAddress udp:161", `sysLocation "Rack \"A\""`,
"group tera_ro_v1 v1 tera_ro", "group tera_ro_v2c v2c tera_ro",
"access tera_ro_v1 \"\" any noauth exact tera_all none none",
"access tera_rw_v2c \"\" any noauth exact tera_all tera_all none",
"rwuser snmp-admin priv .1",
} {
if !strings.Contains(mainText, expected) {
t.Fatalf("main configuration missing %q:\n%s", expected, mainText)
}
}
if strings.Contains(mainText, "auth-secret") || strings.Contains(mainText, "privacy-secret") {
t.Fatalf("SNMPv3 credentials leaked into main configuration: %s", mainText)
}
if !strings.Contains(string(persistentConfig), `createUser snmp-admin SHA-256 "auth-secret-123" AES "privacy-secret-123"`) {
t.Fatalf("persistent configuration = %s", persistentConfig)
}
if len(runner.actions) != 1 || runner.actions[0] != "restart" {
t.Fatalf("service actions = %v", runner.actions)
}
if runtime.GOOS != "windows" {
for _, path := range []string{mainPath, persistentPath} {
info, err := os.Stat(path)
if err != nil || info.Mode().Perm() != 0o600 {
t.Fatalf("%s mode = %v, err=%v", path, info.Mode().Perm(), err)
}
}
}
}
func TestOSControllerRestoresFilesWhenRestartFails(t *testing.T) {
directory := t.TempDir()
mainPath := filepath.Join(directory, "snmpd.conf")
persistentPath := filepath.Join(directory, "persistent.conf")
if err := os.WriteFile(mainPath, []byte("agentAddress udp:161\nold-main\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(persistentPath, []byte("old-persistent\n"), 0o600); err != nil {
t.Fatal(err)
}
runner := &recordingRunner{fail: true}
controller, err := NewOSController(OSOptions{
ConfigPath: mainPath, PersistentPath: persistentPath,
InitScriptPath: filepath.Join(directory, "S59snmpd"), Runner: runner,
})
if err != nil {
t.Fatal(err)
}
config := Config{Enabled: true, V2CEnabled: true, ReadOnlyCommunity: "community-secret"}
if err := controller.Apply(context.Background(), config); err == nil || !strings.Contains(err.Error(), "restart failed") {
t.Fatalf("apply error = %v", err)
}
mainConfig, _ := os.ReadFile(mainPath)
persistentConfig, _ := os.ReadFile(persistentPath)
if string(mainConfig) != "agentAddress udp:161\nold-main\n" || string(persistentConfig) != "old-persistent\n" {
t.Fatalf("rollback files = %q, %q", mainConfig, persistentConfig)
}
if len(runner.actions) != 2 || runner.actions[1] != "restart" {
t.Fatalf("rollback actions = %v", runner.actions)
}
}
func TestValidateRejectsConfigInjection(t *testing.T) {
config := Config{Enabled: true, V3Enabled: true, V3Users: []User{{
Username: "bad user", Access: "read", AuthProtocol: "none", PrivacyProtocol: "none", Enabled: true,
}}}
if err := Validate(config); err == nil {
t.Fatal("unsafe SNMPv3 username accepted")
}
config = Config{Enabled: true, V2CEnabled: true, ReadOnlyCommunity: "bad\ncommunity"}
if err := Validate(config); err == nil {
t.Fatal("control character in community accepted")
}
}