Active Directory admins are probably well aware of how Kerberos works. If you need a little refresher, check out the article over at askds: Kerberos for the busy admin. Kerberos requires a service principle name (SPN) for each Kerberos enabled network service offered in the forest: a file service, KDC, web farm, whatever. Typical examples of SPNs: HOST/s1.contoso.com, HTTP/webfarm.contoso.com:8080.  

There are a few things to know about SPNs:

It is easy to spot duplicate SPNs; just run: setspn -x -f. It is not so easy to spot missing SPNs. For services that are missing the correct SPNs it simply means that the cannot use Kerberos. With luck, they fall back to NTLM. With no luck, they fail.

There is one simple case where an automated inspection can show missing SPNs. For a lot of services, you need a registration for both the FQDN and the hostname. For instance:

Or,

So of one of them is missing, there could be a problem. If one of the SPNs will never be used, you are OK. If not, add the missing one. To have a look, simply run the script.

 

PowerShell
Edit|Remove
<# 
.Synopsis 
    To find possibly missing SPN registrations due to manual mistakes. 
.DESCRIPTION 
    Goal: find possibly missing SPN registrations due to manual mistakes. 
    The expected correct pattern is that each SPN has a host-only and a FQDN version. 
 
    Example full SPN:     HTTP/s1.sol.local:8080/sol.local 
    Structure of the SPN: serviceclass/host.domain:port/service 
    We need to find SPNs that 
    - are part of specific (interesting) service classes.  
    - do not have an explicit service (those with services are very likely OK) 
    - have a host but no domain, while no other SPN with host.domain exists 
    - have a host+domain, while no other SPN with just the hostname (--> no domain) exists 
.EXAMPLE 
   .\Find-PossibleMissingSPN.ps1 
.EXAMPLE 
   .\Find-PossibleMissingSPN.ps1 "ou=myOU,dc=sol,dc=local" 
#> 
 
[CmdletBinding()] 
Param 
( 
    # start the search at this DN. Default is to search all of the domain. 
    [string]$DN = (Get-ADDomain).DistinguishedName 
) 
 
# 
# define the SPN service classes to look for. Other types are mostly automated and should be OK. 
# 
$servicesclasses2check = @("host""cifs""nfs""http""mssql"# 
# get computers and users with a nonzero SPN within the given DN. 
# 
$filter = '(&(servicePrincipalname=*)(|(objectcategory=computer)(objectcategory=person)))' 
$propertylist = @("servicePrincipalname""samaccountname") 
Get-ADObject -LDAPFilter $filter -SearchBase $DN -SearchScope Subtree -Properties $propertylist -PipelineVariable account | ForEach-Object { 
    # 
    # Create list of interesting SPNs for each account. Strong assumption for all code: SPN is syntactically correct.  
    # 
    $spnlist = $account.servicePrincipalName | Where-Object { 
        ($serviceclass$hostname$service) = $_ -split '/' 
        ($servicesclasses2check -contains $serviceclass-and -not $service 
    } 
 
    # 
    # Look for cases where there is no pair of (host, host.domain) SPNs. 
    # 
    foreach ($spn in $spnlist) 
    { 
        ($serviceclass$hostname$service) = $spn -split '/' 
        if ($service) { $service = "/$service" } 
        ($fullname$port) = $hostname -split ':' 
        if ($port) { $port = ":$port" } 
        ($shortname$domain) = $fullname -split '[.]' 
        # 
        # define the regexp matching the missing SPN and go look for it  
        # 
        if ($domain) { 
            $needsSPN =  "${serviceclass}/${shortname}${port}${service}`$" 
            $needsSPNtxt = "${serviceclass}/${shortname}${port}${service}" 
        } else { 
            $needsSPN = "$serviceclass/${shortname}[.][a-zA-Z0-9-]+.*${port}${service}`$" 
            $needsSPNtxt = "$serviceclass/${shortname}.<domain>${port}${service}" 
        } 
        # 
        # search the array of SPNs to see if the _other_ SPN is there. If not, we have problem case.  
        # 
        if (-not ($spnlist -match $needsSPN)) 
        { 
            [PSCustomobject] @{ 
                samaccountname = $account.samaccountname 
                presentSPN = $spn 
                missingSPN = $needsSPNtxt 
            } 
        } 
    } 
}
 Find more discussion about this script on my blog: https://blogs.technet.microsoft.com/389thoughts/2017/03/19/find-missing-spn-registrations/