Bom, para aqueles que leram o artigo Brigando com o erro Cannot generate SSPI e Login failed for user ANONYMOUS LOGON , nele falo sobre um dos motivos da ocorrência do erro NT AUTHORITYANONYMOUS LOGON. Na verdade, quanto este tipo de erro ocorre existem “basicamente” dois motivos:
1) MSDTC não instalado ou quando instalado, configuração incorretamente. Principalmente se estiver utilizando um Windows Server 2003 com SP2 (http://support.microsoft.com/kb/301600)
2) Configuração incorreta dos SPNs. Seja por falta de registro, entradas duplicadas ou erros de digitação
É verdade que este erro também pode ser consequência de entradas de nome de máquina duplicada no DNS, quando a conta de computador no AD não está configurada como “Trust Computer for Delegation” ou ainda quando a conta de serviço do SQL Server no AD não está configurada como “Account is trusted for delegation“, etc, mas diria que em 99% dos casos, a causa é configuração de SPN e configuração do MSDTC.
Bom, com o problema de ontem segui o checklist básico….
1) MSDTC instalado e configurado? Sim
2) Conta de computador no AD configurada como “Trust Computer for Delegation”? Sim
3) Conta de serviço do SQL Server no AD configurada como “Account is trusted for delegation”? Sim
Ok, faltou então o ítem relacionado ao SPN. Como no meu ambiente possuo contas específicas para os ambientes de desenvolvimento e produção, sempre costumo validar as entradas de SPN para as duas contas. Para isso uso a sintaxe abaixo:
C:/temp>setspn -L contoso/sqlservice_des
Registered ServicePrincipalNames for …
.
.
.
MSSQLSvc/SQL01.contoso.com.br:1433
MSSQLSvc/SQL02.contoso.com.br:1433
MSSQLSvc/SQL04.contoso.com.br:1433
Neste ponto identifiquei que existiam entradas duplicadas para a conta de desenvolvimento e de produção. Conversando com os DBAs foi explicado que a instalação do SQL Server havia sido feita várias vezes e poderia ter sido feita também usando a conta de serviço de produção.
Ok, resolvi então excluir todas as entradas de SPN das duas máquinas em questão para as duas contas e cadastrar novamente apenas para a conta de desenvolvimento. Supondo que minhas máquinas tenham os respectivos nomes SQL01 e SQL02, o cadastro de SPN seria como segue…
setspn -A MSSQLSvc/SQL01.contoso.com.br:1433 contoso/sqlservice_des
setspn -A MSSQLSvc/SQL02.contoso.com.br:1433 contoso/sqlservice_des
Consultando então os SPNs registrados para a conta sqlservice_des obtive o seguinte output…
C:/temp>setspn -L contoso/sqlservice_des
Registered ServicePrincipalNames for …
.
.
.
MSSQLSvc/SQL01.contoso.com.br:1433
MSSQLSvc/SQL02.contoso.com.br:1433
Bom, tudo então parecia estar correto, no entanto, ao fazer um novo teste de utilização do linked server, o erro ainda ocorria!! Pensei…bom, então o problema é mais complicado pois o básico já foi “checado” e está ok. Pensei até em abrir um chamado na MS, foi então quando lembrei de um .vbs bala utilizado por um engenheiro do time de PFE PLATAFORM, quando tive um problema de AD no ano passado.
O script faz uma varredura de todos os SPNs registrados em um Domain Controler e lista-os informando a respectiva conta para a qual o SPN está registrado. Com isso executei então o .vbs informando meu Domain Controler e salvando o output para um .txt
SPN.vbs MSSQLSvc/* SRVDM001.contoso.com.br > SRVDM001.txt
O output gerado foi similar ao abaixo…
CN=sqlservice_des,OU=Member Servers,DC=contoso,DC=com,DC=br
Class: user
User Logon: sqlservice_des
— MSSQLSvc/SQL01.contoso.com.br:1433
— MSSQLSvc/SQL02.contoso.com.br:1433
— MSSQLSvc/SQL03.contoso.com.br:1433
CN=adminrede_prod,OU=Member Servers,DC=contoso,DC=com,DC=br
Class: user
User Logon: adminrede_prod
— MSSQLSvc/SQL01.contoso.com.br:1433
Bimba… foi aí que descobri que ainda existia uma duplicidade de SPN para um dos servidores, neste exemplo, o SQL01. Porém, a conta para a qual o SPN estava duplicado não era uma conta de serviço de SQL Server. Bom, esta fora do escopo explicar como este registro foi parar aí nesta conta, mas o que interessa é que após removar a entrada para a conta adminrede_prod (nome fictício é claro), meu linked server passou a funcionar corretamente.
É isso aí pessoal… está aí mais um exemplo de como o uso de autenticação KERBEROS é sensível e devemos ficar bem atento aos registros de SPNs.
Antes que alguém me envie e-mail pedindo o .vbs, segue abaixo o script completo. Basta salvá-lo como qualquercoisa.vbs e chamá-lo como fiz no exemplo acima.
Abs
Nilton Pinheiro
********* Script VBS
‘ Copyright (c) Microsoft Corporation 2004 –
‘ Contents: Query a given SPN in a given forest to find the owners
‘ History: 7/7/2004 Craig Wiand Created
Option Explicit
Const DUMP_SPNs = True
Dim oConnection, oCmd, oRecordSet
Dim oGC, oNSP
Dim strGCPath, strClass, strSPN, strADOQuery
Dim vObjClass, vSPNs, vName
ParseCommandLine()
‘— Set up the connection —
Set oConnection = CreateObject(“ADODB.Connection”)
Set oCmd = CReateObject(“ADODB.Command”)
oConnection.Provider = “ADsDSOObject”
oConnection.Open “ADs Provider”
Set oCmd.ActiveConnection = oConnection
oCmd.Properties(“Page Size”) = 1000
‘— Build the query string —
strADOQuery = “<” + strGCPath + “>;(servicePrincipalName=” + strSPN + “);” & _
“dnsHostName,distinguishedName,servicePrincipalName,objectClass,” & _
“samAccountName;subtree”
oCmd.CommandText = strADOQuery
‘— Execute the query for the object in the directory —
Set oRecordSet = oCmd.Execute
If oRecordSet.EOF and oRecordSet.Bof Then
Wscript.Echo “No SPNs found!”
Else
While Not oRecordset.Eof
Wscript.Echo oRecordset.Fields(“distinguishedName”)
vObjClass = oRecordset.Fields(“objectClass”)
strClass = vObjClass( UBound(vObjClass) )
Wscript.Echo “Class: ” & strClass
If UCase(strClass) = “COMPUTER” Then
Wscript.Echo “Computer DNS: ” & oRecordset.Fields(“dnsHostName”)
Else
Wscript.Echo “User Logon: ” & oRecordset.Fields(“samAccountName”)
End If
If DUMP_SPNs Then
‘— Display the SPNs on the object —
vSPNs = oRecordset.Fields(“servicePrincipalName”)
For Each vName in vSPNs
Wscript.Echo “– ” + vName
Next
End If
Wscript.Echo
oRecordset.MoveNext
Wend
End If
oRecordset.Close
oConnection.Close
Sub ShowUsage()
Wscript.Echo ” USAGE: ” & WScript.ScriptName & _
” SpnToFind [GC Servername or Forestname]”
Wscript.Echo
Wscript.Echo ” EXAMPLES: ”
Wscript.Echo ” ” & WScript.ScriptName & _
” MSSQLSvc/MySQL.company.com:1433″
Wscript.Echo ” ” & WScript.ScriptName & _
” HOST/Server1 Corp.com”
Wscript.Quit 0
End Sub
Sub ParseCommandLine()
If WScript.Arguments.Count <> 1 And WScript.Arguments.Count <> 2 Then
ShowUsage()
Else
strSPN = WScript.Arguments(0)
If WScript.Arguments.Count = 2 Then
strGCPath = “GC://” & WScript.Arguments(1)
Else
‘— Get GC —
Set oNSP = GetObject(“GC:”)
For Each oGC in oNSP
strGCPath = oGC.ADsPath
Next
End If
End If
End Sub