-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #31 from rundeck-plugins/support-kerbreos
adding support to kerberos authentication
- Loading branch information
Showing
9 changed files
with
583 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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` | ||
|
@@ -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 | ||
|
||
|
||
|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.