Log access denied opening a service at debug level (#4674)

This commit is contained in:
Daniel Nelson 2018-09-11 16:04:16 -07:00 committed by GitHub
parent eff7f0f083
commit 4c571d2cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 156 deletions

View File

@ -1,7 +1,9 @@
# Telegraf Plugin: win_services # Windows Services Input Plugin
Input plugin to report Windows services info.
Reports information about Windows service status.
Monitoring some services may require running Telegraf with administrator privileges.
It requires that Telegraf must be running under the administrator privileges.
### Configuration: ### Configuration:
```toml ```toml
@ -25,7 +27,7 @@ The `state` field can have the following values:
- 3 - stop pending - 3 - stop pending
- 4 - running - 4 - running
- 5 - continue pending - 5 - continue pending
- 6 - pause pending - 6 - pause pending
- 7 - paused - 7 - paused
The `startup_mode` field can have the following values: The `startup_mode` field can have the following values:
@ -33,7 +35,7 @@ The `startup_mode` field can have the following values:
- 1 - system start - 1 - system start
- 2 - auto start - 2 - auto start
- 3 - demand start - 3 - demand start
- 4 - disabled - 4 - disabled
### Tags: ### Tags:
@ -43,14 +45,13 @@ The `startup_mode` field can have the following values:
### Example Output: ### Example Output:
``` ```
* Plugin: inputs.win_services, Collection 1 win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state=4i,startup_mode=2i 1500040669000000000
> win_services,host=WIN2008R2H401,display_name=Server,service_name=LanmanServer state=4i,startup_mode=2i 1500040669000000000 win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state=1i,startup_mode=3i 1500040669000000000
> win_services,display_name=Remote\ Desktop\ Services,service_name=TermService,host=WIN2008R2H401 state=1i,startup_mode=3i 1500040669000000000
``` ```
### TICK Scripts ### TICK Scripts
A sample TICK script for a notification about a not running service. A sample TICK script for a notification about a not running service.
It sends a notification whenever any service changes its state to be not _running_ and when it changes that state back to _running_. It sends a notification whenever any service changes its state to be not _running_ and when it changes that state back to _running_.
The notification is sent via an HTTP POST call. The notification is sent via an HTTP POST call.
``` ```

View File

@ -4,32 +4,52 @@ package win_services
import ( import (
"fmt" "fmt"
"log"
"os"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
"golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr" "golang.org/x/sys/windows/svc/mgr"
) )
//WinService provides interface for svc.Service type ServiceErr struct {
Message string
Service string
Err error
}
func (e *ServiceErr) Error() string {
return fmt.Sprintf("%s: '%s': %v", e.Message, e.Service, e.Err)
}
func IsPermission(err error) bool {
if err, ok := err.(*ServiceErr); ok {
return os.IsPermission(err.Err)
}
return false
}
// WinService provides interface for svc.Service
type WinService interface { type WinService interface {
Close() error Close() error
Config() (mgr.Config, error) Config() (mgr.Config, error)
Query() (svc.Status, error) Query() (svc.Status, error)
} }
//WinServiceManagerProvider sets interface for acquiring manager instance, like mgr.Mgr // ManagerProvider sets interface for acquiring manager instance, like mgr.Mgr
type WinServiceManagerProvider interface { type ManagerProvider interface {
Connect() (WinServiceManager, error) Connect() (WinServiceManager, error)
} }
//WinServiceManager provides interface for mgr.Mgr // WinServiceManager provides interface for mgr.Mgr
type WinServiceManager interface { type WinServiceManager interface {
Disconnect() error Disconnect() error
OpenService(name string) (WinService, error) OpenService(name string) (WinService, error)
ListServices() ([]string, error) ListServices() ([]string, error)
} }
//WinSvcMgr is wrapper for mgr.Mgr implementing WinServiceManager interface // WinSvcMgr is wrapper for mgr.Mgr implementing WinServiceManager interface
type WinSvcMgr struct { type WinSvcMgr struct {
realMgr *mgr.Mgr realMgr *mgr.Mgr
} }
@ -45,7 +65,7 @@ func (m *WinSvcMgr) ListServices() ([]string, error) {
return m.realMgr.ListServices() return m.realMgr.ListServices()
} }
//MgProvider is an implementation of WinServiceManagerProvider interface returning WinSvcMgr // MgProvider is an implementation of WinServiceManagerProvider interface returning WinSvcMgr
type MgProvider struct { type MgProvider struct {
} }
@ -71,7 +91,7 @@ var description = "Input plugin to report Windows services info."
//WinServices is an implementation if telegraf.Input interface, providing info about Windows Services //WinServices is an implementation if telegraf.Input interface, providing info about Windows Services
type WinServices struct { type WinServices struct {
ServiceNames []string `toml:"service_names"` ServiceNames []string `toml:"service_names"`
mgrProvider WinServiceManagerProvider mgrProvider ManagerProvider
} }
type ServiceInfo struct { type ServiceInfo struct {
@ -79,7 +99,6 @@ type ServiceInfo struct {
DisplayName string DisplayName string
State int State int
StartUpMode int StartUpMode int
Error error
} }
func (m *WinServices) Description() string { func (m *WinServices) Description() string {
@ -91,93 +110,102 @@ func (m *WinServices) SampleConfig() string {
} }
func (m *WinServices) Gather(acc telegraf.Accumulator) error { func (m *WinServices) Gather(acc telegraf.Accumulator) error {
scmgr, err := m.mgrProvider.Connect()
if err != nil {
return fmt.Errorf("Could not open service manager: %s", err)
}
defer scmgr.Disconnect()
serviceInfos, err := listServices(m.mgrProvider, m.ServiceNames) serviceNames, err := listServices(scmgr, m.ServiceNames)
if err != nil { if err != nil {
return err return err
} }
for _, service := range serviceInfos { for _, srvName := range serviceNames {
if service.Error == nil { service, err := collectServiceInfo(scmgr, srvName)
fields := make(map[string]interface{}) if err != nil {
tags := make(map[string]string) if IsPermission(err) {
log.Printf("D! Error in plugin [inputs.win_services]: %v", err)
//display name could be empty, but still valid service } else {
if len(service.DisplayName) > 0 { acc.AddError(err)
tags["display_name"] = service.DisplayName
} }
tags["service_name"] = service.ServiceName continue
fields["state"] = service.State
fields["startup_mode"] = service.StartUpMode
acc.AddFields("win_services", fields, tags)
} else {
acc.AddError(service.Error)
} }
tags := map[string]string{
"service_name": service.ServiceName,
}
//display name could be empty, but still valid service
if len(service.DisplayName) > 0 {
tags["display_name"] = service.DisplayName
}
fields := map[string]interface{}{
"state": service.State,
"startup_mode": service.StartUpMode,
}
acc.AddFields("win_services", fields, tags)
} }
return nil return nil
} }
//listServices gathers info about given services. If userServices is empty, it return info about all services on current Windows host. Any a critical error is returned. // listServices returns a list of services to gather.
func listServices(mgrProv WinServiceManagerProvider, userServices []string) ([]ServiceInfo, error) { func listServices(scmgr WinServiceManager, userServices []string) ([]string, error) {
scmgr, err := mgrProv.Connect() if len(userServices) != 0 {
return userServices, nil
}
names, err := scmgr.ListServices()
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not open service manager: %s", err) return nil, fmt.Errorf("Could not list services: %s", err)
} }
defer scmgr.Disconnect() return names, nil
var serviceNames []string
if len(userServices) == 0 {
//Listing service names from system
serviceNames, err = scmgr.ListServices()
if err != nil {
return nil, fmt.Errorf("Could not list services: %s", err)
}
} else {
serviceNames = userServices
}
serviceInfos := make([]ServiceInfo, len(serviceNames))
for i, srvName := range serviceNames {
serviceInfos[i] = collectServiceInfo(scmgr, srvName)
}
return serviceInfos, nil
} }
//collectServiceInfo gathers info about a service from WindowsAPI // collectServiceInfo gathers info about a service.
func collectServiceInfo(scmgr WinServiceManager, serviceName string) (serviceInfo ServiceInfo) { func collectServiceInfo(scmgr WinServiceManager, serviceName string) (*ServiceInfo, error) {
serviceInfo.ServiceName = serviceName
srv, err := scmgr.OpenService(serviceName) srv, err := scmgr.OpenService(serviceName)
if err != nil { if err != nil {
serviceInfo.Error = fmt.Errorf("Could not open service '%s': %s", serviceName, err) return nil, &ServiceErr{
return Message: "could not open service",
Service: serviceName,
Err: err,
}
} }
defer srv.Close() defer srv.Close()
srvStatus, err := srv.Query() srvStatus, err := srv.Query()
if err == nil { if err != nil {
serviceInfo.State = int(srvStatus.State) return nil, &ServiceErr{
} else { Message: "could not query service",
serviceInfo.Error = fmt.Errorf("Could not query service '%s': %s", serviceName, err) Service: serviceName,
//finish collecting info on first found error Err: err,
return }
} }
srvCfg, err := srv.Config() srvCfg, err := srv.Config()
if err == nil { if err != nil {
serviceInfo.DisplayName = srvCfg.DisplayName return nil, &ServiceErr{
serviceInfo.StartUpMode = int(srvCfg.StartType) Message: "could not get config of service",
} else { Service: serviceName,
serviceInfo.Error = fmt.Errorf("Could not get config of service '%s': %s", serviceName, err) Err: err,
}
} }
return
serviceInfo := &ServiceInfo{
ServiceName: serviceName,
DisplayName: srvCfg.DisplayName,
StartUpMode: int(srvCfg.StartType),
State: int(srvStatus.State),
}
return serviceInfo, nil
} }
func init() { func init() {
inputs.Add("win_services", func() telegraf.Input { return &WinServices{mgrProvider: &MgProvider{}} }) inputs.Add("win_services", func() telegraf.Input {
return &WinServices{
mgrProvider: &MgProvider{},
}
})
} }

View File

@ -4,11 +4,10 @@
package win_services package win_services
import ( import (
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows/svc/mgr"
"testing" "testing"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
) )
var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"} var InvalidServices = []string{"XYZ1@", "ZYZ@", "SDF_@#"}
@ -18,57 +17,30 @@ func TestList(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("Skipping integration test in short mode") t.Skip("Skipping integration test in short mode")
} }
services, err := listServices(&MgProvider{}, KnownServices) provider := &MgProvider{}
scmgr, err := provider.Connect()
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, services, 2, "Different number of services") defer scmgr.Disconnect()
assert.Equal(t, services[0].ServiceName, KnownServices[0])
assert.Nil(t, services[0].Error) services, err := listServices(scmgr, KnownServices)
assert.Equal(t, services[1].ServiceName, KnownServices[1]) require.NoError(t, err)
assert.Nil(t, services[1].Error) require.Len(t, services, 2, "Different number of services")
require.Equal(t, services[0], KnownServices[0])
require.Equal(t, services[1], KnownServices[1])
} }
func TestEmptyList(t *testing.T) { func TestEmptyList(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("Skipping integration test in short mode") t.Skip("Skipping integration test in short mode")
} }
services, err := listServices(&MgProvider{}, []string{}) provider := &MgProvider{}
scmgr, err := provider.Connect()
require.NoError(t, err) require.NoError(t, err)
assert.Condition(t, func() bool { return len(services) > 20 }, "Too few service") defer scmgr.Disconnect()
}
func TestListEr(t *testing.T) { services, err := listServices(scmgr, []string{})
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
services, err := listServices(&MgProvider{}, InvalidServices)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, services, 3, "Different number of services") require.Condition(t, func() bool { return len(services) > 20 }, "Too few service")
for i := 0; i < 3; i++ {
assert.Equal(t, services[i].ServiceName, InvalidServices[i])
assert.NotNil(t, services[i].Error)
}
}
func TestGather(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ws := &WinServices{KnownServices, &MgProvider{}}
assert.Len(t, ws.ServiceNames, 2, "Different number of services")
var acc testutil.Accumulator
require.NoError(t, ws.Gather(&acc))
assert.Len(t, acc.Errors, 0, "There should be no errors after gather")
for i := 0; i < 2; i++ {
fields := make(map[string]interface{})
tags := make(map[string]string)
si := getServiceInfo(KnownServices[i])
fields["state"] = int(si.State)
fields["startup_mode"] = int(si.StartUpMode)
tags["service_name"] = si.ServiceName
tags["display_name"] = si.DisplayName
acc.AssertContainsTaggedFields(t, "win_services", fields, tags)
}
} }
func TestGatherErrors(t *testing.T) { func TestGatherErrors(t *testing.T) {
@ -76,40 +48,8 @@ func TestGatherErrors(t *testing.T) {
t.Skip("Skipping integration test in short mode") t.Skip("Skipping integration test in short mode")
} }
ws := &WinServices{InvalidServices, &MgProvider{}} ws := &WinServices{InvalidServices, &MgProvider{}}
assert.Len(t, ws.ServiceNames, 3, "Different number of services") require.Len(t, ws.ServiceNames, 3, "Different number of services")
var acc testutil.Accumulator var acc testutil.Accumulator
require.NoError(t, ws.Gather(&acc)) require.NoError(t, ws.Gather(&acc))
assert.Len(t, acc.Errors, 3, "There should be 3 errors after gather") require.Len(t, acc.Errors, 3, "There should be 3 errors after gather")
}
func getServiceInfo(srvName string) *ServiceInfo {
scmgr, err := mgr.Connect()
if err != nil {
return nil
}
defer scmgr.Disconnect()
srv, err := scmgr.OpenService(srvName)
if err != nil {
return nil
}
var si ServiceInfo
si.ServiceName = srvName
srvStatus, err := srv.Query()
if err == nil {
si.State = int(srvStatus.State)
} else {
si.Error = err
}
srvCfg, err := srv.Config()
if err == nil {
si.DisplayName = srvCfg.DisplayName
si.StartUpMode = int(srvCfg.StartType)
} else {
si.Error = err
}
srv.Close()
return &si
} }