forked from vmware-archive/pyvmomi-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation for using existing xml credential store files made wit…
…h credstore_admin.pl (Solving opened issue vmware-archive#20)
- Loading branch information
1 parent
1eff01d
commit f0cb809
Showing
3 changed files
with
455 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,205 @@ | ||
# Copyright (c) 2014 VMware, Inc. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
''' | ||
Minimal functionality to read and use passwords from vSphere Credential Store XML file | ||
''' | ||
|
||
from __future__ import print_function | ||
|
||
__author__ = 'Osvaldo Demo' | ||
|
||
import xml.etree.ElementTree as ET | ||
from sys import platform as _platform | ||
import os | ||
import os.path | ||
|
||
|
||
class PasswordEntry(object): | ||
""" | ||
Abstraction object that translates from obfuscated password to usable password text | ||
""" | ||
|
||
def __init__ (self, host=None, username=None, password=None): | ||
self.__host = host | ||
self.__username = username | ||
self.__password = password | ||
|
||
def __str__ (self): | ||
return '{ Host: ' + self.__host + ' User: ' + self.__username + ' Pwd: ' + self.__password + ' }' | ||
|
||
def __unicode__ (self): | ||
return self.__str__() | ||
|
||
def __repr__(self): | ||
return self.__str__() | ||
|
||
def __hash__(self): | ||
return hash(self.__host) + hash(self.__username) + hash(self.__password) | ||
|
||
def __eq__(self, other): | ||
if isinstance(other, self.__class__): | ||
return hash(self) == hash(other) | ||
else: | ||
return False | ||
|
||
def _compute_hash (self, text): | ||
""" | ||
Generates a hash based on the following formula: | ||
hash = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] | ||
:param text: String | ||
:return: Computed hash value | ||
:rtype: int | ||
""" | ||
|
||
boundary = 0x7FFFFFFF | ||
negative = 0x80000000 | ||
hash_value = 0 | ||
for my_char in text: | ||
hash_value = hash_value * 31 + ord(my_char) | ||
if (hash_value & negative): | ||
hash_value |= ~boundary | ||
else: | ||
hash_value &= boundary | ||
|
||
return hash_value | ||
|
||
def _deobfuscate (self): | ||
""" | ||
Convert the obfuscated string to the actual password in clear text | ||
Functionality taken from the perl module VICredStore.pm since the goal was to emulate its behaviour. | ||
""" | ||
|
||
hashmod = 256 | ||
password = self.__password.decode('base64') | ||
hash_value = self._compute_hash(self.__host + self.__username) % hashmod | ||
crypt = chr(hash_value & 0xFF) * len(password) | ||
password_final = [] | ||
for n in range(0, len(password)): | ||
password_final.append(ord(password[n]) ^ ord(crypt[n])) | ||
decrypted_pwd = '' | ||
for ci in password_final: | ||
if ci == 0: | ||
break | ||
decrypted_pwd += chr(ci) | ||
|
||
return decrypted_pwd | ||
|
||
def getPwd (self): | ||
return self._deobfuscate() | ||
|
||
def getUser (self): | ||
return self.__username | ||
|
||
def getHost (self): | ||
return self.__host | ||
|
||
|
||
class HostNotFoundException(Exception): | ||
""" | ||
Exception raised when the host/server was not found in the credentials file. | ||
""" | ||
pass | ||
|
||
|
||
class NoCredentialsFileFound(Exception): | ||
""" | ||
Exception raised when the credentials xml file was not found. | ||
""" | ||
pass | ||
|
||
|
||
class VICredStore(object): | ||
""" | ||
Helper class that mimicks VICredStore perl module. | ||
Functionality implemented to decode the existing credentials file only. | ||
""" | ||
|
||
__hostdata = {} | ||
FILE_PATH_UNIX = '/.vmware/credstore/vicredentials.xml' | ||
FILE_PATH_WIN = '/VMware/credstore/vicredentials.xml' | ||
|
||
def __init__ (self, path=None): | ||
if path is None: | ||
try: | ||
if os.environ['VI_CREDSTORE'] is not None: | ||
self.__path = os.environ['VI_CREDSTORE'] | ||
except KeyError: | ||
|
||
if _platform == "linux" or _platform == "linux2": | ||
self.__path = os.environ['HOME'] + self.FILE_PATH_UNIX | ||
elif _platform == "darwin": | ||
raise Exception('Unsupported platform! (' + _platform + ')') | ||
elif _platform == "win32": | ||
self.__path = os.environ['APPDATA'] + self.FILE_PATH_WIN | ||
else: | ||
self.__path = path | ||
|
||
if os.path.exists(self.__path): | ||
self.__tree = ET.parse(self.__path) | ||
self.__root = self.__tree.getroot() | ||
self.__hostdata = self.__populate_data() | ||
else: | ||
self.__root = None | ||
self.__tree = None | ||
raise NoCredentialsFileFound('Credential filename [' + self.__path + '] doesn\'t exist!') | ||
|
||
def get_userpwd (self, hostname): | ||
try: | ||
entry = self.__hostdata[hostname] | ||
except KeyError: | ||
raise HostNotFoundException("Host " + hostname + " does not exist in the credential store!") | ||
|
||
return (entry.getUser(), entry.getPwd()) | ||
|
||
def _get_pwd_entry_list (self): | ||
tmp_list = [] | ||
for entry in self.__root: | ||
if entry.tag == "passwordEntry": | ||
tmp_list.append(entry) | ||
|
||
pwdentries = [] | ||
for entry in tmp_list: | ||
host = None | ||
user = None | ||
pwd = None | ||
for child in entry: | ||
if child.tag == "host": | ||
host = child.text | ||
if child.tag == "username": | ||
user = child.text | ||
if child.tag == "password": | ||
pwd = child.text | ||
|
||
if host is not None and user is not None and pwd is not None: | ||
pwdentries.append(PasswordEntry(host, user, pwd)) | ||
|
||
return pwdentries | ||
|
||
def list_entries (self): | ||
for entry in sorted(self.__hostdata.keys()): | ||
print(entry) | ||
|
||
def __populate_data (self): | ||
pwd_list = self._get_pwd_entry_list() | ||
new_hostdata = {} | ||
for entry in pwd_list: | ||
new_hostdata[entry.getHost()] = entry | ||
|
||
return new_hostdata |
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,176 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) 2014 VMware, Inc. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
from __future__ import print_function | ||
|
||
""" | ||
A Python script for power cycling a virtual machine. Demonstrates the use | ||
of tasks in an asynchronous way, how to answer virtual machine | ||
questions in the middle of power operations using a credentials xml file. | ||
In order to use it you need to create your credentials file via: | ||
# credstore_admin.pl add -s <server> -u <user> -p <password> | ||
get_args method has been modified to illustrate the functionality. | ||
This could be integrated to the cli package in the args file. | ||
""" | ||
|
||
import atexit | ||
import argparse | ||
from six import PY2 | ||
import sys | ||
import textwrap | ||
|
||
from pyvmomi_tools import cli | ||
from pyVim import connect | ||
from pyVmomi import vim | ||
from pyvmomi_tools.extensions.credstore import VICredStore, NoCredentialsFileFound, HostNotFoundException | ||
|
||
if PY2: | ||
input = raw_input | ||
|
||
def get_args(): | ||
""" | ||
Supports the command-line arguments and/or credentials xml file. | ||
""" | ||
parser = argparse.ArgumentParser(description='Process args for retrieving all the Virtual Machines') | ||
parser.add_argument('-s', '--host', | ||
required=True, | ||
action='store', | ||
help='Remote host to connect to') | ||
parser.add_argument('-o', '--port', | ||
type=int, | ||
default=443, | ||
action='store', | ||
help='Port to connect on') | ||
parser.add_argument('-u', '--user', | ||
action='store', | ||
help='User name to use when connecting to host') | ||
parser.add_argument('-p', '--password', | ||
action='store', | ||
help='Password to use when connecting to host') | ||
parser.add_argument('-n', '--name', | ||
required=True, | ||
action='store', | ||
help='Name of the virtual_machine to look for.') | ||
|
||
args = parser.parse_args() | ||
|
||
try: | ||
store = VICredStore() | ||
except NoCredentialsFileFound: | ||
print("ERROR: No credentials store file found. You need to enter credentials via command-line arguments!\n") | ||
sys.exit(1) | ||
|
||
try: | ||
(args.user, args.password) = store.get_userpwd(args.host) | ||
except HostNotFoundException: | ||
print("ERROR: Host [" + args.host + "] was not found on credentials file. You need to enter credentials via command-line!\n") | ||
parser.print_usage() | ||
sys.exit(1) | ||
|
||
return args | ||
|
||
args = get_args() | ||
|
||
# form a connection... | ||
si = connect.SmartConnect(host=args.host, user=args.user, pwd=args.password, | ||
port=args.port) | ||
|
||
# doing this means you don't need to remember to disconnect your script/objects | ||
atexit.register(connect.Disconnect, si) | ||
|
||
# search the whole inventory tree recursively... a brutish but effective tactic | ||
vm = si.content.rootFolder.find_by_name(args.name) | ||
if not isinstance(vm, vim.VirtualMachine): | ||
print("could not find a virtual machine with the name {0}", args.name) | ||
sys.exit(-1) | ||
|
||
print("Found VirtualMachine: {0} Name: {1}", vm, vm.name) | ||
|
||
if vm.runtime.powerState == vim.VirtualMachinePowerState.poweredOn: | ||
# using a dynamic class extension for power_off | ||
# this is a blocking method call and the script will pause | ||
# while the machine powers off. | ||
print("powering off...") | ||
vm.power_off() | ||
print("power is off.") | ||
|
||
|
||
def answer_question(vm): | ||
print("\n") | ||
choices = vm.runtime.question.choice.choiceInfo | ||
default_option = None | ||
if vm.runtime.question.choice.defaultIndex is not None: | ||
ii = vm.runtime.question.choice.defaultIndex | ||
default_option = choices[ii] | ||
choice = None | ||
while choice not in [o.key for o in choices]: | ||
print("VM power on is paused by this question:\n\n") | ||
print("\n".join(textwrap.wrap(vm.runtime.question.text, 60))) | ||
for option in choices: | ||
print("\t {0}: {1} ", option.key, option.label) | ||
if default_option is not None: | ||
print("default ({0}): {1}\n", default_option.label, | ||
default_option.key) | ||
choice = input("\nchoice number: ").strip() | ||
print("...") | ||
return choice | ||
|
||
|
||
# Sometimes we don't want a task to block execution completely | ||
# we may want to execute or handle concurrent events. In that case we can | ||
# poll our task repeatedly and also check for any run-time issues. This | ||
# code deals with a common problem, what to do if a VM question pops up | ||
# and how do you handle it in the API? | ||
print("powering on VM {0}", vm.name) | ||
if vm.runtime.powerState != vim.VirtualMachinePowerState.poweredOn: | ||
|
||
# now we get to work... calling the vSphere API generates a task... | ||
task = vm.PowerOn() | ||
|
||
# We track the question ID & answer so we don't end up answering the same | ||
# questions repeatedly. | ||
answers = {} | ||
|
||
def handle_question(current_task, virtual_machine): | ||
# we'll check for a question, if we find one, handle it, | ||
# Note: question is an optional attribute and this is how pyVmomi | ||
# handles optional attributes. They are marked as None. | ||
if virtual_machine.runtime.question is not None: | ||
question_id = virtual_machine.runtime.question.id | ||
if question_id not in answers.keys(): | ||
answer = answer_question(virtual_machine) | ||
answers[question_id] = answer | ||
virtual_machine.AnswerVM(question_id, answer) | ||
|
||
# create a spinning cursor so people don't kill the script... | ||
cli.cursor.spinner(task.info.state) | ||
|
||
task.poll(vm, periodic=handle_question) | ||
|
||
if task.info.state == vim.TaskInfo.State.error: | ||
# some vSphere errors only come with their class and no other message | ||
print("error type: {0}", task.info.error.__class__.__name__) | ||
print("found cause: {0}", task.info.error.faultCause) | ||
for fault_msg in task.info.error.faultMessage: | ||
print(fault_msg.key) | ||
print(fault_msg.message) | ||
sys.exit(-1) | ||
|
||
print(".") | ||
sys.exit(0) |
Oops, something went wrong.