Skip to content
Merged
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
11 changes: 7 additions & 4 deletions doc/plugin_agent_workloadattestor_windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
The `windows` plugin generates Windows-based selectors for workloads calling the agent.
It does so by opening an access token associated with the workload process. The system is then interrogated to retrieve user and group account information from that access token.

| Configuration | Description | Default |
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| `discover_workload_path` | If true, the workload path will be discovered by the plugin and used to provide additional selectors | false |
| `workload_size_limit` | The limit of workload binary sizes when calculating certain selectors (e.g. sha256). If zero, no limit is enforced. If negative, never calculate the hash. | 0 |
| Configuration | Description | Default |
|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| `discover_workload_path` | If true, the workload path will be discovered by the plugin and used to provide additional selectors | false |
| `workload_size_limit` | The limit of workload binary sizes when calculating certain selectors (e.g. sha256). If zero, no limit is enforced. If negative, never calculate the hash. | 0 |
| `disable_group_name_selectors` | If true, skips resolving group SIDs to human-readable names, avoiding potentially expensive account name resolution (e.g. against a Domain Controller). Group SID selectors (`group_sid`) are always collected regardless. Only `group_name` selectors are affected. | false |

## Workload Selectors

Expand Down Expand Up @@ -47,6 +48,8 @@ Defenses against this are:

- User and group account names are expressed using the [down-level logon name format](https://docs.microsoft.com/en-us/windows/win32/secauthn/user-name-formats#down-level-logon-name).

- Enabling `disable_group_name_selectors` will cause existing workload registration entries that use `group_name` selectors to stop matching. Operators should audit those entries and switch them to `group_sid` selectors before enabling this flag.

## Configuration

This plugin does not require any configuration setting. It can be added in the following way in the agent configuration file:
Expand Down
32 changes: 22 additions & 10 deletions pkg/agent/plugin/workloadattestor/windows/windows_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"fmt"
"sync"
"time"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/hcl"
Expand Down Expand Up @@ -33,8 +34,9 @@ func New() *Plugin {
}

type Configuration struct {
DiscoverWorkloadPath bool `hcl:"discover_workload_path"`
WorkloadSizeLimit int64 `hcl:"workload_size_limit"`
DiscoverWorkloadPath bool `hcl:"discover_workload_path"`
WorkloadSizeLimit int64 `hcl:"workload_size_limit"`
DisableGroupNameSelectors bool `hcl:"disable_group_name_selectors"`
}

func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginconf.Status) *Configuration {
Expand Down Expand Up @@ -77,7 +79,7 @@ func (p *Plugin) Attest(_ context.Context, req *workloadattestorv1.AttestRequest
return nil, err
}

process, err := p.newProcessInfo(req.Pid, config.DiscoverWorkloadPath)
process, err := p.newProcessInfo(req.Pid, config.DiscoverWorkloadPath, config.DisableGroupNameSelectors)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get process information: %v", err)
}
Expand Down Expand Up @@ -119,7 +121,7 @@ func (p *Plugin) AttestReference(_ context.Context, _ *workloadattestorv1.Attest
return nil, status.Error(codes.Unimplemented, "AttestReference not implemented")
}

func (p *Plugin) newProcessInfo(pid int32, queryPath bool) (*processInfo, error) {
func (p *Plugin) newProcessInfo(pid int32, queryPath bool, disableGroupNames bool) (*processInfo, error) {
p.log = p.log.With(telemetry.PID, pid)

h, err := p.q.OpenProcess(pid)
Expand Down Expand Up @@ -167,20 +169,26 @@ func (p *Plugin) newProcessInfo(pid int32, queryPath bool) (*processInfo, error)
}
groups := p.q.AllGroups(tokenGroups)

start := time.Now()
for _, group := range groups {
// Each group has a set of attributes that control how
// the system uses the SID in an access check.
// We are interested in the SE_GROUP_ENABLED attribute.
// https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-attributes-in-an-access-token
enabledSelector := getGroupEnabledSelector(group.Attributes)
processInfo.groupsSIDs = append(processInfo.groupsSIDs, enabledSelector+":"+group.Sid.String())
groupAccount, groupDomain, err := p.q.LookupAccount(group.Sid)
if err != nil {
p.log.Warn("failed to lookup account from group SID", "sid", group.Sid, "error", err)
continue
if !disableGroupNames {
groupAccount, groupDomain, err := p.q.LookupAccount(group.Sid)
if err != nil {
p.log.Warn("failed to lookup account from group SID", "sid", group.Sid, "error", err)
continue
}
// If the LookupAccount call succeeded, we know that groupAccount is not empty
processInfo.groups = append(processInfo.groups, enabledSelector+":"+parseAccount(groupAccount, groupDomain))
}
// If the LookupAccount call succeeded, we know that groupAccount is not empty
processInfo.groups = append(processInfo.groups, enabledSelector+":"+parseAccount(groupAccount, groupDomain))
}
if !disableGroupNames {
p.log.Debug("Group account name lookups completed", "count", len(groups), "duration_ms", time.Since(start).Milliseconds())
}

if queryPath {
Expand All @@ -202,6 +210,10 @@ func (p *Plugin) Configure(_ context.Context, req *configv1.ConfigureRequest) (*
defer p.mu.Unlock()
p.config = newConfig

if newConfig.DisableGroupNameSelectors {
p.log.Info("Group name selectors disabled; only group_sid selectors will be produced for groups")
}

return &configv1.ConfigureResponse{}, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,32 @@ func TestAttest(t *testing.T) {
},
expectCode: codes.OK,
},
{
name: "successful with groups, group name selectors disabled",
trustDomain: "example.org",
config: "disable_group_name_selectors = true",
pq: &fakeProcessQuery{
handle: windows.InvalidHandle,
tokenUser: &windows.Tokenuser{User: windows.SIDAndAttributes{Sid: sidUser}},
tokenGroups: &windows.Tokengroups{Groups: [1]windows.SIDAndAttributes{sidAndAttrGroup1}},
account: "user1",
domain: "domain1",
sidAndAttributes: []windows.SIDAndAttributes{sidAndAttrGroup1, sidAndAttrGroup3},
},
expectSelectors: []string{
"windows:user_name:domain1\\user1",
"windows:user_sid:" + sidUser.String(),
"windows:group_sid:se_group_enabled:true:" + sidGroup1.String(),
"windows:group_sid:se_group_enabled:true:" + sidGroup3.String(),
},
expectCode: codes.OK,
expectLogs: []spiretest.LogEntry{
{
Level: logrus.InfoLevel,
Message: "Group name selectors disabled; only group_sid selectors will be produced for groups",
},
},
},
{
name: "successful getting path and hashing process binary",
trustDomain: "example.org",
Expand Down
Loading