Skip to content

Commit 8d247bf

Browse files
committed
adds system for regression tests
* adds mitmproxy (https://github.com/saironiq/mitmproxy), using it in testing * tests for fence_apc, fence_docker, fence_ipmilan
1 parent 117faf2 commit 8d247bf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+6362
-5
lines changed

fence/agents/cisco_ucs/fence_cisco_ucs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
sys.path.append("@FENCEAGENTSLIBDIR@")
88
from fencing import *
99
from fencing import fail, EC_STATUS, EC_LOGIN_DENIED, run_delay
10+
import fencing_pycurl
1011

1112
#BEGIN_VERSION_GENERATION
1213
RELEASE_VERSION="New Cisco UCS Agent - test release on steroids"
@@ -85,7 +86,7 @@ def send_command(opt, command, timeout):
8586
url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + "/nuova"
8687

8788
## send command through pycurl
88-
conn = pycurl.Curl()
89+
conn = fencing_pycurl.FencingPyCurl()
8990
web_buffer = StringIO.StringIO()
9091
conn.setopt(pycurl.URL, url)
9192
conn.setopt(pycurl.HTTPHEADER, ["Content-type: text/xml"])

fence/agents/docker/fence_docker.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
sys.path.append("@FENCEAGENTSLIBDIR@")
1111
from fencing import fail_usage, all_opt, fence_action, atexit_handler, check_input, process_input, show_docs, run_delay
12+
import fencing_pycurl
1213

1314
#BEGIN_VERSION_GENERATION
1415
RELEASE_VERSION = ""
@@ -50,7 +51,7 @@ def get_list(conn, options):
5051

5152
def send_cmd(options, cmd, post = False):
5253
url = "http%s://%s:%s/v1.11/%s" % ("s" if "--ssl" in options else "", options["--ip"], options["--ipport"], cmd)
53-
conn = pycurl.Curl()
54+
conn = fencing_pycurl.FencingPyCurl()
5455
output_buffer = StringIO.StringIO()
5556
if logging.getLogger().getEffectiveLevel() < logging.WARNING:
5657
conn.setopt(pycurl.VERBOSE, True)

fence/agents/lib/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
MAINTAINERCLEANFILES = Makefile.in
22

3-
TARGET = fencing.py fencing_snmp.py
3+
TARGET = fencing.py fencing_snmp.py fencing_pycurl.py
44

55
if BUILD_XENAPILIB
66
TARGET += XenAPI.py
77
endif
88

9-
SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py check_used_options.py
9+
SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py check_used_options.py fencing_pycurl.py.py
1010

1111
XSL = fence2man.xsl fence2rng.xsl fence2wiki.xsl
1212

fence/agents/lib/fencing.py.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,7 @@ def fence_login(options, re_login_string=r"(login\s*: )|((?!Last )Login Name: )
872872
return conn
873873

874874
def is_executable(path):
875+
path = shlex.split(path)[0]
875876
if os.path.exists(path):
876877
stats = os.stat(path)
877878
if stat.S_ISREG(stats.st_mode) and os.access(path, os.X_OK):

fence/agents/lib/fencing_pycurl.py.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
__author__ = 'Ondrej Mular <[email protected]>'
2+
__all__ = ["FencingPyCurl"]
3+
4+
import pycurl
5+
import sys
6+
import atexit
7+
import json
8+
import StringIO
9+
import time
10+
import logging
11+
import pprint
12+
13+
## do not add code here.
14+
#BEGIN_VERSION_GENERATION
15+
RELEASE_VERSION = ""
16+
REDHAT_COPYRIGHT = ""
17+
BUILD_DATE = ""
18+
#END_VERSION_GENERATION
19+
20+
class FencingPyCurl():
21+
active = False
22+
input_file = None
23+
output_file = None
24+
options = None
25+
actions = None
26+
request_index = 0
27+
output_buffer = None
28+
write_function = None
29+
pycurl_obj = None
30+
31+
def __init__(self):
32+
if not FencingPyCurl.active and (FencingPyCurl.input_file or FencingPyCurl.output_file):
33+
FencingPyCurl.active = True
34+
logging.debug("FencingPyCurl is active now")
35+
if FencingPyCurl.active:
36+
self.options = {
37+
"request": {},
38+
"response": {},
39+
"time": {}
40+
}
41+
42+
self.output_buffer = StringIO.StringIO()
43+
if FencingPyCurl.actions is None:
44+
if FencingPyCurl.input_file:
45+
logging.debug("Reading input file.")
46+
47+
try:
48+
with open(FencingPyCurl.input_file, "r") as f:
49+
FencingPyCurl.actions = json.load(f)
50+
except Exception as e:
51+
logging.debug("Reading input file (%s) failed: %s" % (FencingPyCurl.input_file, e.message))
52+
53+
if FencingPyCurl.output_file:
54+
logging.debug("output file detected")
55+
if not FencingPyCurl.actions:
56+
FencingPyCurl.actions = []
57+
FencingPyCurl.request_index = 0
58+
59+
self.pycurl_obj = pycurl.Curl()
60+
61+
def setopt(self, opt, value):
62+
if FencingPyCurl.active:
63+
if opt == pycurl.WRITEFUNCTION:
64+
self.write_function = value
65+
value = self.output_buffer.write
66+
else:
67+
self.options["request"][str(opt)] = value
68+
return self.pycurl_obj.setopt(opt, value)
69+
70+
def perform(self):
71+
if FencingPyCurl.active:
72+
if FencingPyCurl.input_file:
73+
perform_start = time.time()
74+
if self.options["request"] == FencingPyCurl.actions[FencingPyCurl.request_index]["request"]:
75+
self.options["response"] = FencingPyCurl.actions[FencingPyCurl.request_index]["response"]
76+
if self.write_function:
77+
self.write_function(self.options["response"]["output"])
78+
diff = FencingPyCurl.actions[FencingPyCurl.request_index]["time"]["perform_duration"] - (time.time() - perform_start)
79+
if diff > 0:
80+
logging.debug("sleeping for: %s" % str(diff))
81+
time.sleep(diff)
82+
FencingPyCurl.last_request_time = time.time()
83+
else:
84+
print "Request:"
85+
pprint.pprint(self.options["request"])
86+
print "Expected:"
87+
pprint.pprint(FencingPyCurl.actions[FencingPyCurl.request_index]["request"])
88+
raise Exception("Invalid request")
89+
else:
90+
start_time = time.time()
91+
self.pycurl_obj.perform()
92+
self.options["time"]["perform_duration"] = FencingPyCurl.last_request_time - start_time
93+
self.options["response"]["output"] = self.output_buffer.getvalue()
94+
if self.write_function:
95+
self.write_function(self.options["response"]["output"])
96+
if FencingPyCurl.output_file:
97+
FencingPyCurl.actions.append(self.options)
98+
FencingPyCurl.request_index += 1
99+
else:
100+
return self.pycurl_obj.perform()
101+
102+
def unsetopt(self, opt):
103+
if FencingPyCurl.active:
104+
del self.options["request"][str(opt)]
105+
return self.pycurl_obj.unsetopt(opt)
106+
107+
def getinfo(self, opt):
108+
value = self.pycurl_obj.getinfo(opt)
109+
if FencingPyCurl.active:
110+
if FencingPyCurl.input_file:
111+
value = self.options["response"][str(opt)]
112+
else:
113+
self.options["response"][opt] = value
114+
return value
115+
116+
def reset(self):
117+
if FencingPyCurl.active:
118+
self.options.clear()
119+
return self.pycurl_obj.reset()
120+
121+
def close(self):
122+
return self.pycurl_obj.close()
123+
124+
@staticmethod
125+
def save_log_to_file():
126+
if FencingPyCurl.output_file and FencingPyCurl.actions:
127+
logging.debug("Writing log to file: %s" % FencingPyCurl.output_file)
128+
try:
129+
with open(FencingPyCurl.output_file, "w") as f:
130+
json.dump(FencingPyCurl.actions, f, sort_keys=True, indent=4, separators=(',', ': '))
131+
except Exception as e:
132+
logging.debug("Writing log to file (%s) failed: %s" % (FencingPyCurl.output_file, e.message))
133+
134+
135+
def get_and_remove_arg(arg, has_value=True):
136+
logging.debug("Getting arg: %s (has_value: %s)" % (arg, str(has_value)))
137+
if not has_value:
138+
if arg in sys.argv:
139+
sys.argv.remove(arg)
140+
logging.debug("%s: True" % arg)
141+
return True
142+
if arg in sys.argv:
143+
index = sys.argv.index(arg)
144+
sys.argv.remove(arg)
145+
if len(sys.argv) > index:
146+
value = sys.argv[index]
147+
sys.argv.remove(value)
148+
logging.debug("%s: %s" % (arg, value))
149+
return value
150+
return None
151+
152+
FencingPyCurl.input_file = get_and_remove_arg("--fencing_pycurl-log-in")
153+
FencingPyCurl.output_file = get_and_remove_arg("--fencing_pycurl-log-out")
154+
155+
if FencingPyCurl.output_file:
156+
atexit.register(FencingPyCurl.save_log_to_file)

fence/agents/pve/fence_pve.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import logging
1414
sys.path.append("@FENCEAGENTSLIBDIR@")
1515
from fencing import fail, EC_LOGIN_DENIED, atexit_handler, all_opt, check_input, process_input, show_docs, fence_action, run_delay
16+
import fencing_pycurl
1617

1718
#BEGIN_VERSION_GENERATION
1819
RELEASE_VERSION=""
@@ -93,7 +94,7 @@ def get_ticket(options):
9394

9495
def send_cmd(options, cmd, post=None):
9596
url = options["url"] + cmd
96-
conn = pycurl.Curl()
97+
conn = fencing_pycurl.FencingPyCurl()
9798
output_buffer = StringIO.StringIO()
9899
if logging.getLogger().getEffectiveLevel() < logging.WARNING:
99100
conn.setopt(pycurl.VERBOSE, True)

mitm/mitmproxy-0.1/INTERNAL.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
MITMPROXY LIBRARY INTERNALS
2+
===========================
3+
4+
5+
Introduction
6+
------------
7+
The `mitmproxy` library consists of 2 modules: `mitmproxy.py` and `sshdebug.py`.
8+
Module `mitmproxy.py` contains implementation of proxy and replay servers
9+
for supported network protocols. Module `sshdebug.py` serves for more human-friendly
10+
debugging output of unencrypted SSH messages.
11+
12+
The library is built on the Python Twisted network framework - see the project homepage
13+
at [twistedmatrix.com](http://twistedmatrix.com/).
14+
15+
16+
Supported network protocols
17+
---------------------------
18+
* Telnet
19+
* HTTP
20+
* HTTPS/SSL
21+
* SSH
22+
* SNMP
23+
24+
25+
MITM proxy server in general
26+
----------------------------
27+
```
28+
+----------------------MITM PROXY---------------------+
29+
| +--------------+ +-------+ +--------------+ |
30+
| | (receive) | <<< | Queue | <<< | (transmit) | |
31+
+--------+ | | | +-------+ | | | +--------+
32+
| Client | <--> | | Proxy Server | | Proxy Client | | <--> | Server |
33+
+--------+ | | | +-------+ | | | +--------+
34+
| | (transmit) | >>> | Queue | >>> | (receive) | |
35+
| +--------------+ +-------+ +--------------+ |
36+
+-----------------------------------------------------+
37+
```
38+
39+
As you can see in the above diagram, the MITM proxy has 2 componets - proxy server
40+
and proxy client. These components communicate internally via deferred queues
41+
and all of the communication is logged for later use by the replay server.
42+
For details about deferred queues RTFM at [defer-intro](http://twistedmatrix.com/documents/current/core/howto/defer-intro.html).
43+
44+
More info about TCP/UDP clients/servers in Twisted is available on the Twisted core [doc pages](http://twistedmatrix.com/documents/current/core/howto/index.html).
45+
Most of the proxies are quite similar - one exception is SSH because it has multiple layers.
46+
MITM SSH proxy and replay servers are using implementation of SSHv2 protocol
47+
from `twisted.conch.ssh` package. There are some examples of SSH clients and
48+
servers on twisted conch documentation pages:
49+
[conch doc](http://twistedmatrix.com/documents/current/conch/index.html).
50+
Another `twisted.conch.ssh` howto can be found here:
51+
[ticket-5474](http://twistedmatrix.com/trac/ticket/5474)
52+
and
53+
[ticket-6001](http://twistedmatrix.com/trac/ticket/6001).
54+
55+
56+
Notes on SSH
57+
------------
58+
### SSH keypairs
59+
SSH proxy/replay requires SSH keypairs even when they are not being used,
60+
so you should generate them with either `mitmkeygen` or by hand (and specify
61+
their location on command-line if you put them in non-default location).
62+
63+
### Communication
64+
The communication between proxy components starts during authentication.
65+
Proxy server first negotiates authentication method with the client
66+
and then forwards it to the proxy client, which in turn tries authenticating
67+
against the real server. Then the proxy client informs proxy server about
68+
the authentication result. After SSH transport layer connection is established,
69+
the proxy simply forwards (decrypts and re-encrypts) connection layer packets.
70+
The forwarding is done by the `mitmproxy.ProxySSHConnection` class which is a
71+
subclass of `ssh.connection.SSHConnection`. The `mitmproxy.ProxySSHConnection` class simply
72+
overrides the `packetReceived` method which puts received packets into a queue and logs
73+
the SSH channel data (`SSH_MSG_CHANNEL_DATA` message type), which is assumed to be
74+
the interactive shell channel.
75+
76+
### Authentication
77+
The client connects to proxy, the proxy in turn creates a connection
78+
to real server and forwards the SSH banner to the client. The client then chooses
79+
an authentication method to use and authenticates against the _proxy_.
80+
In case of password auth the proxy simply forwards it to the real server.
81+
With public key auth, however, the proxy needs to have the corresponding private
82+
key to be able to authenticate client - so the proxy has its own keypair
83+
to use for client connection. This also means that the proxy has to have the client's
84+
keypair (or any other keypair that is accepted as valid for the given user on the real server).
85+
Thus for the proxy to work with pubkey auth you need to add the public key of _proxy_ to
86+
the list of allowed keys for the give user at the given real server (usually ~/.ssh/authorized_keys).
87+
88+
89+
The proxy server uses Twisted's [Pluggable Authentication](http://twistedmatrix.com/documents/current/core/howto/cred.html) system.
90+
Proxy authentication is implemented in these classes:
91+
* ProxySSHUserAuthServer
92+
* ProxySSHUserAuthClient
93+
* SSHCredentialsChecker
94+
95+
96+
Proxy server side is implemented mostly in the `SSHCredentialsChecker` class.
97+
The `ProxySSHUserAuthServer` is a hack to be able to properly end the communication.
98+
The authentication result is evaluted in callback method `SSHCredentialsChecker.is_auth_succes`.
99+
There are three possible results:
100+
* auth succeeded
101+
* auth failed, more auth methods available - try another one
102+
* auth failed, no more auth methods - disconnect
103+
104+
105+
Proxy client auth is implemented in `ProxySSHUserAuthClient`.
106+
It sends/receives information to/from proxy server through deferred queues.
107+
After successful auth the ssh-connection service is started.
108+
109+
110+
There are some issues with proxy auth:
111+
* proxy and real client auth method attempt order must be the same
112+
* real server might support less auth methods than proxy
113+
114+
First issue is solved by sending the name of current auth method used by client to proxy.
115+
Second issue is solved by pretending method failure and waiting for another auth method.
116+
117+
The proxy tries to be as transparent as possible - everything depends only on server and client configuration
118+
(eg. the number of allowed password auth attemps) - well, at least it *should*. ;)
119+
120+
121+
### SSH Replay server
122+
SSH replay server always successfully authenticates client on the first authentication method attempt.
123+
The replay server is implemented in `mitmproxy.SSHReplayServerProtocol` and it is connected to the SSH service in
124+
`mitmproxy.ReplayAvatar` class.
125+
126+
### SSH proxy drawbacks
127+
The proxy only supports logging of SSH sessions with _only one_ open channel.
128+
It does not support other SSH features like port forwarding.
129+
130+
131+
Notes about SNMP proxy and replay server
132+
----------------------------------------
133+
SNMP proxy is ordinary UDP server that intercepts and forwards UDP packets.
134+
Because of UDP protocol we do not know when the communication ends.
135+
You can save the PID of SNMP proxy process and when the client ends you can terminate the proxy.
136+
SNMP replay reads communication from log and compares received packets with expected packets.
137+
It copes with different request IDs in packets.
138+
There are functions for extracting and replacing the request-id from/in packets - `snmp_extract_request_id` and `snmp_replace_request_id`.
139+
SNMP replay server works only with UDP.
140+

0 commit comments

Comments
 (0)