Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding support to kerberos authentication #31

Merged
merged 1 commit into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 64 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,7 @@ Download from the releases page and copy the py-winrm-plugin-X.X.X.zip to the li

## Requierments

The plugin needs the python module pywinrm. It can be installed with the following command: ```pip install pywinrm```

Additional, it could be added the support for kerberos and credSSP autentication:

### To use Kerberos authentication you need these optional dependencies
#### for Debian/Ubuntu/etc:

```
$ sudo apt-get install python-dev libkrb5-dev
$ pip install pywinrm[kerberos]
```

#### for RHEL/CentOS/etc:
```
$ sudo yum install gcc krb5-devel krb5-workstation
$ pip install pywinrm[kerberos]
```

### To use CredSSP authentication you need these optional dependencies
```
pip install pywinrm[credssp]
```

The plugin needs the python module **pywinrm**. It can be installed with the following command: ```pip install pywinrm```

For further information see:
[https://pypi.python.org/pypi/pywinrm
Expand All @@ -39,7 +17,7 @@ For further information see:

## Configuration

* **Authentication Type**: The authentication type used for the connection: basic, ntlm, credssp. It can be overwriting at node level using `winrm-authtype`
* **Authentication Type**: The authentication type used for the connection: basic, ntlm, credssp, kerberos. It can be overwriting at node level using `winrm-authtype`
* **Username**: (Optional) Username that will connect to the remote node. This value can be set also at node level or as a job input option (with the name `username)
* **Password Storage Path**: Key storage path of the window's user password. It can be overwriting at node level using `winrm-password-storage-path`.
Also the password can be overwritten on the job level using an input secure option called `winrmpassword`
Expand All @@ -48,6 +26,10 @@ For further information see:
* **WinRM Port**: WinRM port (Default: 5985/5986 for http/https). It can be overwriting at node level using `winrm-port`
* **Shell**: Windows Shell interpreter (powershell o cmd). It can be overwriting at node level using `winrm-shell`

For Kerberos
* **krb5 Config File**: path of the krb5.conf (default: /etc/krb5.conf)
* **Kinit Command**: `kinit` command used for create ticket (default: kinit)

## Node definition example


Expand All @@ -63,11 +45,68 @@ For further information see:
osVersion="6.3"
username="rundeckuser@domain.local"
winrm-password-storage-path="keys/node/windows.password"
winrm-authtype="credssp"/>
winrm-authtype="basic"/>
```

The username can be overwritten using a job input option called "username"` or it can be set at project level.

## Transport methods
The transport methods supported are:

* basic
* kerberos
* ntlm
* credssp

Further information [here](https://github.com/diyan/pywinrm#valid-transport-options)


### CredSSP

To use CredSSP authentication you need these optional dependencies
```
pip install pywinrm[credssp]
```

## Kerberos

The pywinrm library has support for kerberos authentication, but it cannot create the kerberos ticket, which needs to be initiate outside the pywinrm scope:

```
kerberos: Will use Kerberos authentication for domain accounts which only works when the client is in the same domain as the server and the required dependencies are installed. Currently a Kerberos ticket needs to be initialized outside of pywinrm using the kinit command.

```
Source [here](https://github.com/diyan/pywinrm#valid-transport-options)


So, in order to connect to a windows box using kerberos we added a call to the `kinit username` command before connecting to the node.

In resume, to use Kerberos authentication the following requirements are needed:

* domain accounts which only works when the client is in the same domain as the server
* kerberos client installed
* domain set on krb5.conf file (default /etc/krb5.conf)
* python `pexpect` library
* python `kerberos` library
* Kerberos authentication enabled on remote windows node (WINRM settings)

### Install Basic dependencies
#### for Debian/Ubuntu/etc:

```
$ sudo apt-get install python-dev libkrb5-dev
$ pip install pywinrm[kerberos]
$ pip install pexpect
```

#### for RHEL/CentOS/etc:
```
$ sudo yum install gcc krb5-devel krb5-workstation
$ pip install pywinrm[kerberos]
$ pip install pexpect
```


## Limitations

Don't use the file copier to transfer big files, the performance is not the best to transfer large files. It works OK passing inline scripts to remote windows nodes
Expand Down
106 changes: 106 additions & 0 deletions contents/kerberosauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import os
import json

try:
import pexpect
except ImportError as e:
pass


class KerberosAuth(object):
def __init__(self, krb5config, kinit_command, log, username, password):
self.krb5config = krb5config
self.kinit_command = kinit_command
self.log = log
self.username = username
self.password = password



def get_ticket(self):
kinit = [self.kinit_command]

kinit_arg = []
kinit_arg.append("-f")
kinit_arg.append("-V")
kinit_arg.append(self.username)

self.log.debug("running kinit %s" %kinit)

krb5env=()
if(self.krb5config):
os.environ["KRB5_CONFIG"]=self.krb5config
krb5env = dict(KRB5_CONFIG=self.krb5config)

try:
process = pexpect.spawn(kinit.pop(0), kinit_arg, timeout=60, env=krb5env)
except pexpect.ExceptionPexpect as err:
msg = "Error creating kerberos ticket %s" % err
self.log.error(msg)
raise Exception(msg)

process.expect(".*:")
process.sendline(self.password)

output = process.read()
process.wait()
self.log.debug("Exist status: %s" %process.exitstatus)
self.log.debug("kinit finish with message %s" %output)

exitCode = process.exitstatus

if exitCode != 0:
msg = "kinit failed %s" % output
self.log.error(msg)
raise Exception(msg)

self.log.debug("kinit succeeded for %s" % self.username)


#just for macos (skipped by the moment)
def check_ticket(self):
try:

klist_command = ["klist"]
kinit_arg = []
kinit_arg.append("--list-all")
kinit_arg.append("--json")
self.log.debug("running klist %s %s" % (klist_command,kinit_arg))

krb5env = ()
if (self.krb5config):
os.environ["KRB5_CONFIG"] = self.krb5config
krb5env = dict(KRB5_CONFIG=self.krb5config)

try:
process = pexpect.spawn(klist_command.pop(0), kinit_arg, timeout=60,
env=krb5env, echo=False)
except pexpect.ExceptionPexpect as err:
msg = "Error checking klist %s" % err
self.log.error(msg)
return False

process.expect(".*")
output = process.read()
process.wait()
if process.exitstatus!=0:
return False

self.log.debug("klist result %s" % output)
results = json.loads(output)

for item in results:
ticket_name=item["Name"]
expired=item["Expired"]

if ticket_name.upper() == self.username.upper():
self.log.debug("Ticket found for user %s, expired: %s"%(ticket_name, expired))
if expired == "no":
self.log.debug("Ticket not expired, skipping kinit")

return True

return False
except Exception as e:
self.log.debug("error running klist command : %s" %e)
return False
Loading