What Service Account PSO Coverage Must Prove
Service account PSO coverage is the evidence that every in-scope service identity has been reviewed against its actual credential-management model and approved password baseline. For a traditional domain user running a service, that means reading the resultant Password Settings Object (PSO), or recording the domain default when no PSO applies. Finding one policy linked to one service account cannot establish coverage for the rest of the cohort.
The practical deliverable is a matrix: account, service, owner, resultant PSO, domain-default evidence, and exception decision. Keep collection failures visible in that matrix. An inaccessible policy is an unresolved evidence gap; it must not silently become either “protected” or “no policy.”
This distinction matters when interpreting an automated finding. The etc-collector implementation reviewed here asks whether any selected service account has a qualifying FGPP link. It does not answer whether every selected account receives an approved resultant policy. A quiet finding therefore needs a coverage review before it supports a closure decision.
How a Policy Link Differs from a Resultant Policy
Microsoft's Get-ADUserResultantPasswordPolicy reference identifies msDS-ResultantPSO as the resultant policy attribute. Direct assignments take priority over group-linked policies. Within the applicable tier, the lowest msDS-PasswordSettingsPrecedence value wins; equal values are resolved by the lowest objectGUID, as specified in Microsoft's resultant-PSO algorithm. A candidate policy link therefore does not prove which policy wins.
The coverage review should preserve the returned policy identity and settings rather than infer the winner from a group name. If an apparently strong group-linked policy coexists with a different direct assignment, investigate the resultant object before accepting the group as evidence of protection. This is a reasoning example, not a report of an observed misconfiguration.
The separate question is which identities belong in the review. Microsoft's service-account guidance distinguishes traditional domain-user service accounts from managed and virtual identities. It describes automatic gMSA password management and no universal service-account minimum length. The review must preserve those distinctions instead of treating every service-like name as an ordinary user with a manually maintained password.
Detection and Verification: Build the Coverage Matrix
1. Agree the cohort with service owners
Start from the service inventory and reconcile it with directory candidates. Record the account's stable identifier, associated services, operating owner, enabled state, identity type, and credential-management responsibility. Where one account serves several applications, preserve every dependency rather than choosing one convenient service name.
The detector's candidate list is useful input, but it is not the authoritative service inventory. A naming heuristic can include unrelated users and miss a service running under an unexpected name without an SPN. Resolve those differences with owners. Our account-naming review and effective-policy checks explain why names select candidates without proving password weakness; use that existing tutorial for the broader naming investigation.
Keep disabled accounts visible with a separate disposition: retained for a documented reason, pending retirement, or excluded from the active-service baseline with approval. Do not count a disabled account's policy as evidence that the active service population is covered. The detector does not make that distinction.
For managed identities, the documented filter capability of Get-ADServiceAccount supports this read-only inventory adaptation:
Get-ADServiceAccount -Filter * -Server dc01.contoso.com
The command returns managed-service-account objects, not a complete inventory of all services or traditional user-backed service accounts. Use object identity and type to reconcile the results with the service inventory. Do not request managed password material for this review. Record gMSAs under their managed-credential review path rather than marking them defective solely because a traditional-user PSO field is empty or inapplicable.
2. Read a resultant policy for every traditional user account
Run the following documented identity/server parameter adaptation for each approved traditional user-backed service account, replacing the illustrative DN and domain controller with the verified target:
Get-ADUserResultantPasswordPolicy -Identity "CN=svc-sql,OU=Service Accounts,DC=contoso,DC=com" -Server dc01.contoso.com -ErrorAction Stop
The cmdlet reference documents these parameters, common error handling, and directory-permission errors. Preserve the command, query identity, server, timestamp, returned object, and error record. A successful policy-object response supplies the resultant PSO identity and settings. An error supplies evidence of an unsuccessful read; it supplies no verdict about policy coverage.
Repeat the read for every account in the traditional-user cohort. Preserve successful rows when another account fails, and keep the failed row open. Do not convert a failed call into a null policy value inside an export, and do not omit failed rows from the denominator. Any automation used to collect the matrix should carry a distinct read-status field alongside the policy field.
3. Record the domain default and explain competing links
Use the identity example and server parameter documented by Get-ADDefaultDomainPasswordPolicy, adapted to the intended domain:
Get-ADDefaultDomainPasswordPolicy -Identity contoso.com -Server dc01.contoso.com
Retain the default policy as domain-scoped evidence. For a successful user read with no returned PSO, verify the identity and query context, then record the applicable default and its baseline assessment. Leave the row unresolved if read completeness or permissions remain uncertain. An empty PSO field alone is insufficient evidence to approve the row.
To investigate candidate policy links, use the documented filtering capability of Get-ADFineGrainedPasswordPolicy:
Get-ADFineGrainedPasswordPolicy -Filter * -Server dc01.contoso.com
Inspect returned AppliesTo, Precedence, and settings alongside the per-user result. The policy inventory explains candidate assignments; it does not replace the resultant read. Save only the policy and identity evidence needed for the review.
If a nested membership path is relevant, document the account-to-group chain, the policy target, the authorized membership evidence, and the returned resultant policy. Label an incomplete chain as unverified. Do not infer an effective PSO from a recursive-looking diagram or from the detector's direct-member comparison. Ask the directory owner to reconcile inconsistent membership and resultant-policy evidence before closing that row.
4. Use explicit row outcomes
The following is a suggested evidence schema, not a sample of real accounts or test results:
| Matrix field | Evidence to retain | Decision it supports |
|---|---|---|
| Account and type | DN or stable identifier; traditional user or managed identity; enabled state | Establishes the reviewed population and correct control path |
| Service and owner | All dependencies and accountable owner | Identifies who validates changes and accepts exceptions |
| Read status | Success with PSO, success without PSO, denied, failed, or applicability pending | Prevents unknown collection from becoming a coverage verdict |
| Resultant PSO | Returned identity and settings, with query context | Supports comparison with the approved baseline |
| Domain default | Domain identity and separately captured policy | Supports the fallback assessment where no PSO applies |
| Assignment evidence | Direct targets and any documented membership chain | Explains a result without substituting for it |
| Baseline and exception | Approved baseline, gap, justification, approver, and review date | Records remediation or an explicit retained exception |
A completed row may be approved through a verified resultant PSO, through an approved domain default, or through a separately justified managed-identity control path. Those are different outcomes. “Denied,” “failed,” and “applicability pending” remain open evidence gaps. An exception is a documented decision, not an automatic pass.
Reconcile the matrix against the agreed cohort before summarizing it. Report unresolved reads and excluded identities explicitly. The absence of an automated finding must not erase those rows.
Remediation: Resolve Each Account's Baseline Gap
1. Approve settings by service context
Use the matrix to identify which traditional accounts fall short of their approved baseline. Have the service and directory owners agree the required password settings, operational compatibility, credential maintenance, and rollout plan. Review the returned settings as a whole; a minimum-length comparison alone is insufficient for a locally approved baseline.
The detector's value of 20 is an implementation threshold. Do not turn it into a Microsoft requirement for all services, and do not copy demonstration policy settings as production recommendations. Keep the service's approved baseline and its justification with the account row.
2. Fix assignments and evidence gaps deliberately
Where an approved FGPP is intended, plan the necessary assignment or precedence correction through the directory change process. Consider competing direct assignments and shared policy consumers before making the change. Avoid creating one qualifying link merely to lower the detector count.
A denied read needs authorized access or another approved evidence collection path before a coverage decision. Capture the original error and the subsequent successful read together. Correcting collection is a prerequisite to interpreting the policy; it does not itself remediate account credentials.
For retained exceptions, record the service dependency, owner, compensating measures, approver, and next review date. Keep the row's exception visible in future coverage reports. An unowned or expired exception should return to review.
3. Separate managed-account and retirement decisions
For a service eligible for gMSA, assess a managed-identity migration with its owner and validate compatibility and authorized credential retrieval. That is a separate change from assigning an FGPP to a traditional account. Keep the classification decision and migration evidence in the matrix.
For disabled identities, resolve dependencies and the reason for retention before retirement. Do not bulk-remove accounts because they appear in a coverage review. Conversely, do not let an unused but qualifying identity conceal gaps in active services.
For attack exposure, our Kerberoasting guide on service tickets and service-account secrets covers the separate ticket, secret, and privilege investigation. This matrix establishes policy-review coverage; it does not prove resistance to offline cracking or the absence of malicious activity.
4. Repeat the reads after approved changes
Re-run the per-user resultant-policy command and the relevant inventory reads after the approved change. Attach before-and-after evidence to every affected row, then have owners validate service operation and the credential-maintenance plan. A changed PSO result proves a policy outcome, not the quality of an existing secret.
How EtcSec Detects This
The etc-collector check SERVICE_ACCOUNT_WEAK_PASSWORD_POLICY has category Password and severity Medium. This description comes from public/internal/audit/detectors/ad/password/service-account-password-policy.go:39 at collector commit 0423094759d50002457031317098417993e19510.
It selects collected users with an SPN or a likely service name: prefixes svc_, svc-, sa_, sa-, sql, iis, or app, or the substring service, after lowercasing sAMAccountName. Selected accounts must have a nonempty DN. The resulting DN set is deduplicated without excluding disabled accounts or separating gMSAs.
The check then searches collected FGPPs whose minimum length is at least 20. A direct AppliesTo match to any selected account, or a match to a group's direct Members list containing such an account, sets its coverage flag. The search stops after the first qualifying relationship.
Consequently, Count=1 means selected accounts exist and no qualifying relationship was found in the collected data. Count=0 means either no accounts were selected or at least one qualifying relationship was found. Count is binary, not the number of uncovered accounts. The details include serviceAccountCount, which is the size of the selected DN set, not a coverage total.
The check does not read msDS-ResultantPSO, calculate effective precedence, expand nested groups, compare every account with a baseline, inspect an existing password, or verify MFA. Its descriptive text must not be treated as evidence that those controls were tested.
Collection also limits the conclusion. In public/internal/providers/ldap/client.go:1835, GetFGPPs returns nil, nil when its policy-container search fails. The engine accepts a nil error at public/internal/audit/engine.go:557; this path does not distinguish an inaccessible container from an empty policy list for this detector. With selected accounts, that empty input can lead to Count=1. Investigate collection before claiming that no policies exist.
Decisions to Record Before Closing
Resolve three questions with the accountable owners:
- Which identities genuinely run services, including disabled and managed accounts, and which control path applies to each?
- How were denied reads, failed queries, and relevant nested memberships recorded and reconciled?
- Which baseline is approved for each service, and which retained exceptions have an approver and review date?
Store the cohort definition, policy reads, membership explanations, change evidence, and decisions together without exporting secrets. The final coverage statement should describe the accounts actually reviewed and the gaps still open. It should never infer universal protection from one qualifying FGPP relationship.
Explore the identity security pages that support this topic
