ansible-collections / ansible.windows

Windows core collection for Ansible
https://galaxy.ansible.com/ansible/windows
GNU General Public License v3.0
244 stars 164 forks source link

Unauthorised error when Win_Shell and UNC #383

Closed jordanjthomas closed 2 years ago

jordanjthomas commented 2 years ago

Summary

When I try and call or access a UNC path with 'win_shell' I receive an access denied error. This is for the functionality of running a script from a remote location (server).

I created a simple Powershell script file to test this. When the exact same command (" . \HOST2\c$\temp\updates\CDiskFacts.ps1") is run on the target host it works fine.

As the credentials used are exactly the same, I'm wondering if this has something to do Credential Delegation as this is using NTLM currently?

Issue Type

Bug Report

Component Name

win_shell

Ansible Version

ansible 2.9.27
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/ssm-user/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Aug 13 2020, 02:51:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

Configuration

N/A

OS / Environment

Windows Server 2016/2019

Steps to Reproduce

---
- name: 'Test running remote script'
  hosts: all
  gather_facts: yes

  tasks:
  - name: 'Run test CDiskFacts script from HOST2'
    win_shell: . \\HOST2\c$\temp\updates\CDiskFacts.ps1
    register: diskfacts

  - debug: var=diskfacts.stdout_lines
...

Expected Results

The standard results from 'Get-PSDrive C'

Actual Results

fatal: [TARGETHOST]: FAILED! => {
    "changed": true,
    "cmd": ". \\\\TARGETHOST\\c$\\temp\\updates\\CDiskFacts.ps1",
    "delta": "0:00:01.340083",
    "end": "2022-07-03 02:30:11.004178",
    "msg": "non-zero return code",
    "rc": 1,
    "start": "2022-07-03 02:30:09.664095",
    "stderr": "Access is denied\r\nAt line:1 char:65\r\n+ ... ext.UTF8Encoding $false; . \\\\HOST2\\c$\\temp\\updates\\CDiskFacts.ps1\r\n+                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n    + CategoryInfo          : OperationStopped: (:) [], UnauthorizedAccessExce \r\n   ption\r\n    + FullyQualifiedErrorId : System.UnauthorizedAccessException",
    "stderr_lines": [
        "Access is denied",
        "At line:1 char:65",
        "+ ... ext.UTF8Encoding $false; . \\\\HOST2\\c$\\temp\\updates\\CDiskFacts.ps1",
    …

Code of Conduct

mwtrigg commented 2 years ago

This is an example of the double-hop problem in Windows. In order to access a remote resource in a remote session, one needs to configure Kerberos or CredSSP delegation. NTLM credentials cannot be delegated; you would - in essence - have to supply them in the remote session on HOST1 in order to access resources on HOST2.

Adding the following to your task (if the environment does not prohibit delegation on the account) should resolve your issue:

vars:
  ansible_winrm_transport: kerberos
  ansible_winrm_kerberos_delegation: true

You could, of course, just throw this in a group_var somewhere and enable delegation on all tasks - but I find this to be overkill; I feel yo should request delegation only when necessary.

Alternatively (and often my preference depending upon the situation) is to avoid the call the remote resource altogether by keeping the script in source control in the ansible repository (in the same play or role) and using ‘win_copy’ or ‘win_template’ to deploy the script resource locally on the target of the task for execution.

jordanjthomas commented 2 years ago

Thanks very much @mwtrigg for the confirmation. I was leaning towards that being the culprit.

In my scenario it may be a bit difficult to copy the resources across to the target for execution as the resources include patch files that can be ~1GB. From various other threads online I have found that WinRM is not the best for copying large files across.

The desired use case is to have a final fail safe when SCCM doesn't push an update out to a server. The patch files are stored on a distribution point server (MSUs from MS Catalogue) and in these failed cases I wanted to run a script that deploys the update file on the target host.

jborean93 commented 2 years ago

This is definitely the credential delegation/double hop problem. Your main options are:

The simplest option that works on pretty much any task and doesn't require any special connection authentication options. By using become on the task you can tell it to use explicit credentials for outbound authentication attempts. For example

- win_copy:
    src: \\server\share\file.exe
    dest: C:\local\file.exe
    remote_src: true
  become: yes
  become_method: runas
  become_flags: logon_type=new_credentials logon_flags=netcredentials_only
  vars:
    ansible_become_user: RemoteUser
    ansible_become_pass: RemotePass

You can set ansible_become_user: '{{ ansible_user }}' andansible_become_pass: '{{ ansible_password }}'` if you just want to re-use the same credentials as your connection user.

This is mentioned in https://github.com/ansible-collections/ansible.windows/issues/383#issuecomment-1173012691. You need to use Kerberos authentication and enable delegation through the connection vars. When delegation is enabled the remote task is able to re-use the Kerberos ticket to talk to downstream servers. You can also enable constrained delegation or resource based constrained delegation in AD to achieve a similar thing. I've written https://www.bloggingforlogging.com/2021/11/03/kerberos-delegation/ that goes through the various Kerberos delegation types.

This is simply use CredSSP as the auth type on the connection. Like Kerberos with delegation it allows you to authenticate with further servers. This is the auth method that RDP uses and why it is able to talk to further servers in an RDP session.

Store the files in a web server and use something like win_get_url to download the file. This is probably the most involved option and unless your org has something like this already may not be viable.