Skip to content

Commit

Permalink
Merge pull request #31 from rundeck-plugins/support-kerbreos
Browse files Browse the repository at this point in the history
adding support to kerberos authentication
  • Loading branch information
ltamaster authored Jun 13, 2019
2 parents 10415d1 + 460c261 commit 6f6710c
Show file tree
Hide file tree
Showing 9 changed files with 583 additions and 42 deletions.
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="[email protected]"
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

0 comments on commit 6f6710c

Please sign in to comment.