Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions docs/checks/commands/check_drivesize.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,19 @@ Naemon Config

## Check Specific Arguments

| Argument | Description |
| ------------------------- | ----------------------------------------------------------------------------------------- |
| drive | The drives to check, e.g. C:\ or / |
| exclude | List of drives to exclude from check |
| folder | The folders to check (parent mountpoint) |
| freespace-ignore-reserved | When false, root-reserved space is subtracted from the total size. Default: true |
| ignore-unreadable | Deprecated, use filter instead |
| magic | Magic number for use with scaling drive sizes. Note there is also a more generic magic factor in the perf-config option. |
| mounted | Deprecated, use filter instead |
| total | Include the total of all matching drives |
| Argument | Description |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| add-persistent-network-drives | Include persistent network drives (net use /persistent), even if currently disconnected, in the all/all-shares listing |
| drive | The drives to check, e.g. C:\ or / |
| exclude | List of drives to exclude from check |
| folder | The folders to check (parent mountpoint) |
| freespace-ignore-reserved | When false, root-reserved space is subtracted from the total size. Default: true |
| ignore-unreadable | Deprecated, use filter instead |
| magic | Magic number for use with scaling drive sizes. Note there is also a more generic magic factor in the perf-config option. |
| mounted | Deprecated, use filter instead |
| share-password | Windows only: password used to authenticate to the network shares given in this check. Note: the password is transmitted as part of the check request. |
| share-user | Windows only: username used to authenticate to the network shares given in this check. The connection is established on demand and removed again after the check. |
| total | Include the total of all matching drives |

## Attributes

Expand Down Expand Up @@ -125,4 +128,6 @@ these can be used in filters and thresholds (along with the default attributes):
| hotplug | Windows only: flag drive is hotplugable (0/1) |
| remote_name | Windows only: the remote name of the drive, if it uses a network name |
| persistent | Windows only: if the network drive is mounted as persistent (0/1) |
| connected | Windows only: if the network drive is currently connected (0/1) |
| hidden | Windows only: if the network share is a hidden share, i.e. the share name ends with a dollar sign like C\$ (0/1) |
| localised_remote_path | Windows only: If the path is given as a remote path, and that remote path has an assigned logical drive, this is the replaced path under that logical drive. |
50 changes: 50 additions & 0 deletions docs/configuration/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,56 @@ This is the order of inheritance for the example above:

The first defined value will be used.

## Network Share Credentials

On Windows, network shares are accessed with the credentials of the account the snclient
service runs under. Shares that were never opened before need credentials before they can
be queried. These can be provided in the `[/settings/credentials]` section, they are stored
in the Windows Credential Manager of the snclient account, without mounting anything.

```ini
[/settings/credentials]
[[share1]]
type = windows-share
target = 192.168.178.21
username = svc
password = secret
strategy = on-demand

[[share2]]
type = windows-share
target = fileserver
username = CORP\svc
password = secret2
strategy = on-start
```

The credential is added for the target server and is automatically used by the SMB
redirector (NTLM/Kerberos) when the snclient connects to that server.

### Keys

| Key | Description |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| type | Type of the credential. Only `windows-share` is supported for now. |
| target | Server to store the credential for, e.g. the host name or IP as used in the UNC path. A full UNC path like `\\server\share` works as well. |
| username | Account used to connect, e.g. `CORP\svc`. If it contains no domain, the domain of the account the snclient runs as is added automatically. |
| password | Plaintext password of the account. Required because the Windows authentication packages need the real secret to authenticate. |
| strategy | When to load the credential, see below. Default: `on-demand` |

### Loading Strategies

| Strategy | Description |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| on-start | The credential is loaded once when the snclient starts and stays for the lifetime of the snclient logon session. It is gone after a reboot. |
| on-demand | The credential is loaded right before a share is queried and removed again immediately after the check finished. This minimizes the time the credential exists. |

When a credential is loaded on demand and a credential for the same target already exists
in the Credential Manager, it is left untouched and removed again after the check. The
credentials never survive a reboot. Note that an established SMB session stays cached in
the snclient logon session even after the credential was removed, so other processes in the
same session could still reuse that connection.

## Includes

It is possible and encouraged to include other ini files to organize your settings.
Expand Down
9 changes: 9 additions & 0 deletions pkg/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@ func Num2StringE(raw any) (string, error) {
}
}

// converts a bool into a "1"/"0" string, used for flag attributes like connected and hidden
func BoolTo01String(value bool) string {
if value {
return "1"
}

return "0"
}

// StateString returns the string corresponding to a monitoring plugin exit code
func StateString(state int64) string {
switch state {
Expand Down
118 changes: 105 additions & 13 deletions pkg/snclient/check_drivesize.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func init() {
}

const (
DiskDetailsTimeout = 30 * time.Second
DiskDetailsTimeout = 10 * time.Second
)

func defaultExcludedFsTypes() []string {
Expand Down Expand Up @@ -61,15 +61,18 @@ func defaultExcludedFsTypes() []string {
}

type CheckDrivesize struct {
drives []string
folders []string
excludes []string
total bool
magic float64
mounted bool
ignoreUnreadable bool
hasCustomPath bool
freespaceIgnoreReserved bool
drives []string
folders []string
excludes []string
total bool
magic float64
mounted bool
ignoreUnreadable bool
hasCustomPath bool
freespaceIgnoreReserved bool
addPersistentNetworkDrives bool
shareUser string
sharePassword string
}

func NewCheckDrivesize() CheckHandler {
Expand All @@ -81,6 +84,7 @@ func NewCheckDrivesize() CheckHandler {
}
}

//nolint:funlen // there are lots of attributes in this check
func (l *CheckDrivesize) Build() *CheckData {
return &CheckData{
name: "check_drivesize",
Expand All @@ -100,6 +104,17 @@ func (l *CheckDrivesize) Build() *CheckData {
"mounted": {value: &l.mounted, description: "Deprecated, use filter instead"}, // deprecated and unused, but should not result in unknown argument
"ignore-unreadable": {value: &l.ignoreUnreadable, description: "Deprecated, use filter instead"}, // same
"freespace-ignore-reserved": {value: &l.freespaceIgnoreReserved, description: "When false, root-reserved space is subtracted from the total size. Default: true"},
"add-persistent-network-drives": {
value: &l.addPersistentNetworkDrives, description: "Include persistent network drives (net use /persistent), even if currently disconnected, in the all/all-shares listing",
},
"share-user": {
value: &l.shareUser, description: "Windows only: username used to authenticate to the network shares given in this check. " +
"The connection is established on demand and removed again after the check.",
},
"share-password": {
value: &l.sharePassword, description: "Windows only: password used to authenticate to the network shares given in this check. " +
"Note: the password is transmitted as part of the check request.",
},
},
defaultFilter: l.getDefaultFilter(),
defaultWarning: "used_pct > 80",
Expand Down Expand Up @@ -152,6 +167,8 @@ func (l *CheckDrivesize) Build() *CheckData {

{name: "remote_name", description: "Windows only: the remote name of the drive, if it uses a network name"},
{name: "persistent", description: "Windows only: if the network drive is mounted as persistent (0/1)", unit: UBool},
{name: "connected", description: "Windows only: if the network drive is currently connected (0/1)", unit: UBool},
{name: "hidden", description: "Windows only: if the network share is a hidden share, i.e. the share name ends with a dollar sign like C$ (0/1)", unit: UBool},
{name: "localised_remote_path", description: "Windows only: If the path is given as a remote path, and that remote path has an assigned logical drive," +
" this is the replaced path under that logical drive."},
},
Expand All @@ -165,7 +182,7 @@ func (l *CheckDrivesize) Build() *CheckData {
}
}

//nolint:funlen // no need to split the function, it is simple as is
//nolint:funlen,gocyclo,maintidx,contextcheck,nolintlint // no need to split the function, it is simple as is , context is constructed when needed
func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData, _ []Argument) (*CheckResult, error) {
enabled, _, _ := snc.config.Section("/modules").GetBool("CheckDisk")
if !enabled {
Expand Down Expand Up @@ -234,6 +251,77 @@ func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData

l.tidyThresholdDriveValues(check)

// resolve the credential to use for each UNC share in this check.
// share-user / share-password override any credentials from the config section.
shareCredentials := map[string]Credential{}
for _, k := range keys {
drive := requiredDisks[k]
if !isNetworkSharePath(drive["drive_or_id"]) {
continue
}
root := shareRoot(drive["drive_or_id"])
if root == "" {
continue
}
if _, ok := shareCredentials[root]; ok {
continue
}

if l.shareUser != "" {
// user is always needed, but password can be empty for a valid login
shareCredentials[root] = Credential{
Type: CredentialTypeWindowsShare,
Target: shareTargetFromUNCPath(root),
Username: qualifyUsername(l.shareUser, currentUserDomain()),
Password: l.sharePassword,
Strategy: CredentialStrategyOnDemand,
}

continue
}

if cred, ok := findOnDemandCredential(snc.config, shareTargetFromUNCPath(root)); ok {
shareCredentials[root] = cred
}
}

// keep track of the connections snclient established, so the cleanup only tears down the ones it added itself
addedConnections := map[string]bool{}

for root, cred := range shareCredentials {
// drop a stale session first, otherwise SMB redirector keeps reusing it and the new credential would not take effect
if err := deleteShareConnection(root); err != nil {
log.Debugf("credentials: could not drop existing connection for %s: %s", root, err.Error())
}

if err := addShareConnection(&cred, root); err != nil {
// a connection with different credentials may still be around, force it away and try once more
if errors.Is(err, errSessionCredentialConflict) {
_ = deleteShareConnection(root)
err = addShareConnection(&cred, root)
}
if err != nil {
log.Errorf("credentials: failed to connect to %s: %s", root, err.Error())

continue
}
}
log.Debugf("credentials: established connection for %s", root)
addedConnections[root] = true
}

// remove all newly added connections again after the check finished
defer func() {
for root := range addedConnections {
if err := deleteShareConnection(root); err != nil {
log.Errorf("credentials: failed to remove connection for %s: %s", root, err.Error())

continue
}
log.Debugf("credentials: removed connection for %s", root)
}
}()

for _, k := range keys {
if ctxErr := ctx.Err(); ctxErr != nil {
return nil, fmt.Errorf("disk scan canceled: %w", ctxErr)
Expand Down Expand Up @@ -267,10 +355,14 @@ func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData

// remove errored paths unless custom path is specified
if !l.hasCustomPath {
for i, entry := range check.listData {
for idx, entry := range check.listData {
if errMsg, ok := entry["_error"]; ok {
// persistent network drives added via add-persistent-network-drives are treated like custom paths, so surface their errors instead of skipping them
if l.addPersistentNetworkDrives && entry["persistent"] == "1" {
continue
}
log.Debugf("drivesize failed for %s: %s", entry["drive_or_id"], errMsg)
check.listData[i]["_skip"] = "1"
check.listData[idx]["_skip"] = "1"
}
}
}
Expand Down
Loading
Loading