Skip to content

Commit

Permalink
Implementation for using existing xml credential store files made wit…
Browse files Browse the repository at this point in the history
…h credstore_admin.pl (Solving opened issue vmware-archive#20)
  • Loading branch information
borland667 committed May 22, 2015
1 parent 1eff01d commit f0cb809
Show file tree
Hide file tree
Showing 3 changed files with 455 additions and 0 deletions.
205 changes: 205 additions & 0 deletions pyvmomi_tools/extensions/credstore.py
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
176 changes: 176 additions & 0 deletions samples/virtual_machine_power_cycle_credstore.py
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)
Loading

0 comments on commit f0cb809

Please sign in to comment.