【问题标题】:How to connect to remote machine via WinRM in Python (pywinrm) using domain account?如何使用域帐户在 Python (pywinrm) 中通过 WinRM 连接到远程计算机?
【发布时间】:2015-09-01 05:17:31
【问题描述】:

我想使用 pywinrm 库在 Python 中编写一个脚本,以便能够通过 WinRM 连接到远程机器。

import winrm

s = winrm.Session('MACHINEHOST', auth=('username@domain', 'password'))
r = s.run_cmd('ipconfig', ['/all'])

print r.status_code
print r.std_out

当我使用本地用户时,脚本运行良好。当我使用域用户时,我收到以下异常:

winrm.exceptions.UnauthorizedError: 401 Unauthorized.

关于远程机器上的 WinRM 配置:

/Client/Auth/Basic = True
/Client/TrustedHosts = *
/Service/Auth/Basic = True
/Service/AllowUnencrypted = True

您能建议如何解决这个问题吗?

谢谢。

【问题讨论】:

  • 建议对域中的远程主机使用 Kerberos。

标签: python winrm


【解决方案1】:

Pywinrm 使用域用户帐号连接:

在远程 Windows 机器中

  1. 确保在目标 Windows 计算机中网络连接类型为“私有”,如果为“公共”,则不会配置 winrm。
  2. 打开命令提示符并输入:

    winrm qc
    winrm set winrm/config/service @{AllowUnencrypted="true"}
    
  3. 打开 Powershell 并输入:

    enable-psremoting
    set-item WSMan:\localhost\Client\TrustedHosts * # ('*' is for all hosts, you may specify the host you want)
    

在您的 Python 代码中

  1. 在你的 python 脚本中:

    import winrm
    
    host = 'YourWindowsHost'
    domain = 'YourDomain'
    user = 'YourDomainUser'
    password = 'YourPassword'
    
    session = winrm.Session(host, auth=('{}@{}'.format(user,domain), password), transport='ntlm')
    
    result = session.run_cmd('ipconfig', ['/all']) # To run command in cmd
    
    result = session.run_ps('Get-Acl') # To run Powershell block
    

【讨论】:

    【解决方案2】:

    正如 Steve Barnes 所说,您的用户应该使用您的域帐户进行 kerberos 连接。

    您首先需要为您的帐户设置一个 kerberos 票证。 Windows 会自动给你这个,但在 linux 下你需要 kinit。使用 klist 查看您当前和默认的票证。

    session = winrm.Session(server, auth=('user@DOMAIN', 'doesNotMatterBecauseYouAreUsingAKerbTicket'), transport='kerberos')
    

    我认为您的域帐户需要在 windows 主机上具有管理员权限。

    另请注意,在 0.0.3 版本的 pywinrm 中,您可以将 auth 参数指定为:

    auth=(None, None)
    

    这是因为 pywinrm 正在使用您的默认 kerberos 票证。

    【讨论】:

      【解决方案3】:

      使用此 powershell 脚本启用 winrm 并将 winrm 配置到所有 Windows 节点

      https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1

      [CmdletBinding()]
      
      Param (
          [string]$SubjectName = $env:COMPUTERNAME,
          [int]$CertValidityDays = 1095,
          [switch]$SkipNetworkProfileCheck,
          $CreateSelfSignedCert = $true,
          [switch]$ForceNewSSLCert,
          [switch]$GlobalHttpFirewallAccess,
          [switch]$DisableBasicAuth = $false,
          [switch]$EnableCredSSP
      )
      
      Function Write-Log
      {
          $Message = $args[0]
          Write-EventLog -LogName Application -Source $EventSource -EntryType Information -EventId 1 -Message $Message
      }
      
      Function Write-VerboseLog
      {
          $Message = $args[0]
          Write-Verbose $Message
          Write-Log $Message
      }
      
      Function Write-HostLog
      {
          $Message = $args[0]
          Write-Output $Message
          Write-Log $Message
      }
      
      Function New-LegacySelfSignedCert
      {
          Param (
              [string]$SubjectName,
              [int]$ValidDays = 1095
          )
      
          $name = New-Object -COM "X509Enrollment.CX500DistinguishedName.1"
          $name.Encode("CN=$SubjectName", 0)
      
          $key = New-Object -COM "X509Enrollment.CX509PrivateKey.1"
          $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
          $key.KeySpec = 1
          $key.Length = 4096
          $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
          $key.MachineContext = 1
          $key.Create()
      
          $serverauthoid = New-Object -COM "X509Enrollment.CObjectId.1"
          $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
          $ekuoids = New-Object -COM "X509Enrollment.CObjectIds.1"
          $ekuoids.Add($serverauthoid)
          $ekuext = New-Object -COM "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
          $ekuext.InitializeEncode($ekuoids)
      
          $cert = New-Object -COM "X509Enrollment.CX509CertificateRequestCertificate.1"
          $cert.InitializeFromPrivateKey(2, $key, "")
          $cert.Subject = $name
          $cert.Issuer = $cert.Subject
          $cert.NotBefore = (Get-Date).AddDays(-1)
          $cert.NotAfter = $cert.NotBefore.AddDays($ValidDays)
          $cert.X509Extensions.Add($ekuext)
          $cert.Encode()
      
          $enrollment = New-Object -COM "X509Enrollment.CX509Enrollment.1"
          $enrollment.InitializeFromRequest($cert)
          $certdata = $enrollment.CreateRequest(0)
          $enrollment.InstallResponse(2, $certdata, 0, "")
      
          # extract/return the thumbprint from the generated cert
          $parsed_cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
          $parsed_cert.Import([System.Text.Encoding]::UTF8.GetBytes($certdata))
      
          return $parsed_cert.Thumbprint
      }
      
      Function Enable-GlobalHttpFirewallAccess
      {
          Write-Verbose "Forcing global HTTP firewall access"
          # this is a fairly naive implementation; could be more sophisticated about rule matching/collapsing
          $fw = New-Object -ComObject HNetCfg.FWPolicy2
      
          # try to find/enable the default rule first
          $add_rule = $false
          $matching_rules = $fw.Rules | ? { $_.Name -eq "Windows Remote Management (HTTP-In)" }
          $rule = $null
          If ($matching_rules) {
              If ($matching_rules -isnot [Array]) {
                  Write-Verbose "Editing existing single HTTP firewall rule"
                  $rule = $matching_rules
              }
              Else {
                  # try to find one with the All or Public profile first
                  Write-Verbose "Found multiple existing HTTP firewall rules..."
                  $rule = $matching_rules | % { $_.Profiles -band 4 }[0]
      
                  If (-not $rule -or $rule -is [Array]) {
                      Write-Verbose "Editing an arbitrary single HTTP firewall rule (multiple existed)"
                      # oh well, just pick the first one
                      $rule = $matching_rules[0]
                  }
              }
          }
      
          If (-not $rule) {
              Write-Verbose "Creating a new HTTP firewall rule"
              $rule = New-Object -ComObject HNetCfg.FWRule
              $rule.Name = "Windows Remote Management (HTTP-In)"
              $rule.Description = "Inbound rule for Windows Remote Management via WS-Management. [TCP 5985]"
              $add_rule = $true
          }
      
          $rule.Profiles = 0x7FFFFFFF
          $rule.Protocol = 6
          $rule.LocalPorts = 5985
          $rule.RemotePorts = "*"
          $rule.LocalAddresses = "*"
          $rule.RemoteAddresses = "*"
          $rule.Enabled = $true
          $rule.Direction = 1
          $rule.Action = 1
          $rule.Grouping = "Windows Remote Management"
      
          If ($add_rule) {
              $fw.Rules.Add($rule)
          }
      
          Write-Verbose "HTTP firewall rule $($rule.Name) updated"
      }
      
      # Setup error handling.
      Trap
      {
          $_
          Exit 1
      }
      $ErrorActionPreference = "Stop"
      
      # Get the ID and security principal of the current user account
      $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
      $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
      
      # Get the security principal for the Administrator role
      $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
      
      # Check to see if we are currently running "as Administrator"
      if (-Not $myWindowsPrincipal.IsInRole($adminRole))
      {
          Write-Output "ERROR: You need elevated Administrator privileges in order to run this script."
          Write-Output "       Start Windows PowerShell by using the Run as Administrator option."
          Exit 2
      }
      
      $EventSource = $MyInvocation.MyCommand.Name
      If (-Not $EventSource)
      {
          $EventSource = "Powershell CLI"
      }
      
      If ([System.Diagnostics.EventLog]::Exists('Application') -eq $False -or [System.Diagnostics.EventLog]::SourceExists($EventSource) -eq $False)
      {
          New-EventLog -LogName Application -Source $EventSource
      }
      
      # Detect PowerShell version.
      If ($PSVersionTable.PSVersion.Major -lt 3)
      {
          Write-Log "PowerShell version 3 or higher is required."
          Throw "PowerShell version 3 or higher is required."
      }
      
      # Find and start the WinRM service.
      Write-Verbose "Verifying WinRM service."
      If (!(Get-Service "WinRM"))
      {
          Write-Log "Unable to find the WinRM service."
          Throw "Unable to find the WinRM service."
      }
      ElseIf ((Get-Service "WinRM").Status -ne "Running")
      {
          Write-Verbose "Setting WinRM service to start automatically on boot."
          Set-Service -Name "WinRM" -StartupType Automatic
          Write-Log "Set WinRM service to start automatically on boot."
          Write-Verbose "Starting WinRM service."
          Start-Service -Name "WinRM" -ErrorAction Stop
          Write-Log "Started WinRM service."
      
      }
      
      # WinRM should be running; check that we have a PS session config.
      If (!(Get-PSSessionConfiguration -Verbose:$false) -or (!(Get-ChildItem WSMan:\localhost\Listener)))
      {
        If ($SkipNetworkProfileCheck) {
          Write-Verbose "Enabling PS Remoting without checking Network profile."
          Enable-PSRemoting -SkipNetworkProfileCheck -Force -ErrorAction Stop
          Write-Log "Enabled PS Remoting without checking Network profile."
        }
        Else {
          Write-Verbose "Enabling PS Remoting."
          Enable-PSRemoting -Force -ErrorAction Stop
          Write-Log "Enabled PS Remoting."
        }
      }
      Else
      {
          Write-Verbose "PS Remoting is already enabled."
      }
      
      # Make sure there is a SSL listener.
      $listeners = Get-ChildItem WSMan:\localhost\Listener
      If (!($listeners | Where {$_.Keys -like "TRANSPORT=HTTPS"}))
      {
          # We cannot use New-SelfSignedCertificate on 2012R2 and earlier
          $thumbprint = New-LegacySelfSignedCert -SubjectName $SubjectName -ValidDays $CertValidityDays
          Write-HostLog "Self-signed SSL certificate generated; thumbprint: $thumbprint"
      
          # Create the hashtables of settings to be used.
          $valueset = @{
              Hostname = $SubjectName
              CertificateThumbprint = $thumbprint
          }
      
          $selectorset = @{
              Transport = "HTTPS"
              Address = "*"
          }
      
          Write-Verbose "Enabling SSL listener."
          New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset
          Write-Log "Enabled SSL listener."
      }
      Else
      {
          Write-Verbose "SSL listener is already active."
      
          # Force a new SSL cert on Listener if the $ForceNewSSLCert
          If ($ForceNewSSLCert)
          {
      
              # We cannot use New-SelfSignedCertificate on 2012R2 and earlier
              $thumbprint = New-LegacySelfSignedCert -SubjectName $SubjectName -ValidDays $CertValidityDays
              Write-HostLog "Self-signed SSL certificate generated; thumbprint: $thumbprint"
      
              $valueset = @{
                  CertificateThumbprint = $thumbprint
                  Hostname = $SubjectName
              }
      
              # Delete the listener for SSL
              $selectorset = @{
                  Address = "*"
                  Transport = "HTTPS"
              }
              Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset
      
              # Add new Listener with new SSL cert
              New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset
          }
      }
      
      # Check for basic authentication.
      $basicAuthSetting = Get-ChildItem WSMan:\localhost\Service\Auth | Where-Object {$_.Name -eq "Basic"}
      
      If ($DisableBasicAuth) 
      {
          If (($basicAuthSetting.Value) -eq $true) 
          {
              Write-Verbose "Disabling basic auth support."
              Set-Item -Path "WSMan:\localhost\Service\Auth\Basic" -Value $false
              Write-Log "Disabled basic auth support."
          }
          Else
          {
              Write-Verbose "Basic auth is already disabled."
          }
      } 
      Else 
      {
          If (($basicAuthSetting.Value) -eq $false)
          {
              Write-Verbose "Enabling basic auth support."
              Set-Item -Path "WSMan:\localhost\Service\Auth\Basic" -Value $true
              Write-Log "Enabled basic auth support."
          }
          Else
          {
              Write-Verbose "Basic auth is already enabled."
          }
      }
      
      # If EnableCredSSP if set to true
      If ($EnableCredSSP)
      {
          # Check for CredSSP authentication
          $credsspAuthSetting = Get-ChildItem WSMan:\localhost\Service\Auth | Where {$_.Name -eq "CredSSP"}
          If (($credsspAuthSetting.Value) -eq $false)
          {
              Write-Verbose "Enabling CredSSP auth support."
              Enable-WSManCredSSP -role server -Force
              Write-Log "Enabled CredSSP auth support."
          }
      }
      
      If ($GlobalHttpFirewallAccess) {
          Enable-GlobalHttpFirewallAccess
      }
      
      # Configure firewall to allow WinRM HTTPS connections.
      $fwtest1 = netsh advfirewall firewall show rule name="Allow WinRM HTTPS"
      $fwtest2 = netsh advfirewall firewall show rule name="Allow WinRM HTTPS" profile=any
      If ($fwtest1.count -lt 5)
      {
          Write-Verbose "Adding firewall rule to allow WinRM HTTPS."
          netsh advfirewall firewall add rule profile=any name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow
          Write-Log "Added firewall rule to allow WinRM HTTPS."
      }
      ElseIf (($fwtest1.count -ge 5) -and ($fwtest2.count -lt 5))
      {
          Write-Verbose "Updating firewall rule to allow WinRM HTTPS for any profile."
          netsh advfirewall firewall set rule name="Allow WinRM HTTPS" new profile=any
          Write-Log "Updated firewall rule to allow WinRM HTTPS for any profile."
      }
      Else
      {
          Write-Verbose "Firewall rule already exists to allow WinRM HTTPS."
      }
      
      # Test a remoting connection to localhost, which should work.
      $httpResult = Invoke-Command -ComputerName "localhost" -ScriptBlock {$env:COMPUTERNAME} -ErrorVariable httpError -ErrorAction SilentlyContinue
      $httpsOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
      
      $httpsResult = New-PSSession -UseSSL -ComputerName "localhost" -SessionOption $httpsOptions -ErrorVariable httpsError -ErrorAction SilentlyContinue
      
      If ($httpResult -and $httpsResult)
      {
          Write-Verbose "HTTP: Enabled | HTTPS: Enabled"
      }
      ElseIf ($httpsResult -and !$httpResult)
      {
          Write-Verbose "HTTP: Disabled | HTTPS: Enabled"
      }
      ElseIf ($httpResult -and !$httpsResult)
      {
          Write-Verbose "HTTP: Enabled | HTTPS: Disabled"
      }
      Else
      {
          Write-Log "Unable to establish an HTTP or HTTPS remoting session."
          Throw "Unable to establish an HTTP or HTTPS remoting session."
      }
      Write-VerboseLog "PS Remoting has been successfully configured for Ansible."
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-01-11
        • 1970-01-01
        • 2017-09-14
        • 1970-01-01
        • 1970-01-01
        • 2018-11-02
        • 1970-01-01
        相关资源
        最近更新 更多