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.6 KiB
132 lines
4.6 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) == 2 {
|
|
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) != 2 || runner.actions[0] != "stop" || runner.actions[1] != "start" {
|
|
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 strings.Join(runner.actions, ",") != "stop,start,stop,start" {
|
|
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")
|
|
}
|
|
}
|
|
|