diff --git a/.gitignore b/.gitignore index 4706462..49a7883 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ htmlcov .cache .eggs + +docs/_build diff --git a/README.md b/README.md index 46758aa..1bfd8c0 100644 --- a/README.md +++ b/README.md @@ -120,17 +120,17 @@ print "PASS!" if authen.valid else "FAIL!" # authorize user and command author = cli.authorize('username', arguments=[b"service=shell", b"cmd=show", b"cmdargs=version"]) -print "PASS! if author.valid else "FAIL!" +print "PASS!" if author.valid else "FAIL!" # start accounting session for command acct = cli.account('username', TAC_PLUS_ACCT_FLAG_START, arguments=[b"service=shell", b"cmd=show", b"cmdargs=version"]) -print "PASS! if acct.valid else "FAIL!" +print "PASS!" if acct.valid else "FAIL!" # continue accounting session for another command acct = cli.account('username', TAC_PLUS_ACCT_FLAG_WATCHDOG, arguments=[b"service=shell", b"cmd=debug", b"cmdargs=aaa"]) -print "PASS! if acct.valid else "FAIL!" +print "PASS!" if acct.valid else "FAIL!" # close accounting session acct = cli.account('username', TAC_PLUS_ACCT_FLAG_STOP, arguments=[b"service=shell", b"cmd=exit"]) -print "PASS! if acct.valid else "FAIL!" +print "PASS!" if acct.valid else "FAIL!" ``` diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..e88d997 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = tacacs_plus +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/code.rst b/docs/code.rst new file mode 100644 index 0000000..cffb9e6 --- /dev/null +++ b/docs/code.rst @@ -0,0 +1,91 @@ +API documentation +================= + +This is the tacacs_plus API documentation. It contains the documentation extracted from the docstrings of the various classes, methods, and functions in the tacacs_plus package. If you want to know what a certain function/method does, this is the place to look. + +.. contents:: + :depth: 2 + + +:mod:`tacacs_plus.client` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source: https://github.com/ansible/tacacs_plus/blob/master/tacacs_plus/client.py + +.. autoclass:: tacacs_plus.client.TACACSClient + :members: + :private-members: + :undoc-members: + + +:mod:`tacacs_plus.packet` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source: https://github.com/ansible/tacacs_plus/blob/master/tacacs_plus/packet.py + +.. autoclass:: tacacs_plus.client.TACACSHeader + :members: + :undoc-members: + + +.. autoclass:: tacacs_plus.client.TACACSPacket + :members: + :undoc-members: + + +:mod:`tacacs_plus.authentication` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source: https://github.com/ansible/tacacs_plus/blob/master/tacacs_plus/authentication.py + +.. autoclass:: tacacs_plus.authentication.TACACSAuthenticationStart + :members: + :undoc-members: + + +.. autoclass:: tacacs_plus.authentication.TACACSAuthenticationContinue + :members: + :undoc-members: + + +.. autoclass:: tacacs_plus.authentication.TACACSAuthenticationReply + :members: + :undoc-members: + + +:mod:`tacacs_plus.authorization` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source: https://github.com/ansible/tacacs_plus/blob/master/tacacs_plus/authorization.py + +.. autoclass:: tacacs_plus.authorization.TACACSAuthorizationStart + :members: + :undoc-members: + + +.. autoclass:: tacacs_plus.authorization.TACACSAuthorizationReply + :members: + :undoc-members: + + +:mod:`tacacs_plus.accounting` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source: https://github.com/ansible/tacacs_plus/blob/master/tacacs_plus/accounting.py + +.. autoclass:: tacacs_plus.accounting.TACACSAccountingStart + :members: + :undoc-members: + + +.. autoclass:: tacacs_plus.accounting.TACACSAccountingReply + :members: + :undoc-members: + + +:mod:`tacacs_plus.flags` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source: https://github.com/ansible/tacacs_plus/blob/master/tacacs_plus/flags.py + +this module contains all the constant flags used to implement the tacacs+ RFC. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..1a901e3 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# tacacs_plus documentation build configuration file, created by +# sphinx-quickstart on Mon May 15 16:59:13 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'tacacs_plus' +copyright = '2017, Ryan Petrello' +author = 'Ryan Petrello' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = 'alpha' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..9962ed2 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +.. tacacs_plus documentation master file, created by + sphinx-quickstart on Mon May 15 16:59:13 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to tacacs_plus's documentation! +======================================= + +.. toctree:: + :maxdepth: 5 + :caption: Index: + + readme + code + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..1a1d5ae --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1,144 @@ +TACACS+ Python client +===================== + +|Build Status| + +A TACACS+ client that supports authentication, authorization and +accounting. + +Unlike RADIUS, which was designed for similar purposes, the TACACS+ +protocol offers basic packet encryption but, as with most crypto +designed back then, it's `not +secure `__ +and definitely should not be used over untrusted networks. + +This package has been successfully used with the free +`tac\_plus `__ TACACS+ server on a +variety of operating systems. + +Basic Installation and Usage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + $ pip install tacacs_plus + + $ tacacs_client -u myuser -H localhost authenticate + $ tacacs_client -u myuser -H localhost authenticate -t pap + $ tacacs_client -u myuser -H localhost -v authenticate -t chap + status: PASS + + $ tacacs_client -u myuser -H localhost authorize -c service=shell cmd=show cmdarg=version + $ tacacs_client -u myuser -H localhost -v authorize -t pap -c service=shell cmd=show cmdarg=version + status: PASS + + $ tacacs_client -u myuser -H localhost -v authorize -t pap -c service=junos-exec + status: REPL + av-pairs: + allow-commands=^acommandregex$ + deny-commands=^anothercommandregex$ + + $ tacacs_client -u myuser -H localhost account -f start -c service=shell cmd=show cmdarg=version + $ tacacs_client -u myuser -H localhost account -f stop -c service=shell cmd=show cmdarg=version + + $ tacacs_client -h + usage: tacacs_client [-h] -u USERNAME -H HOST [-p PORT] [-l PRIV_LVL] + [-t {ascii,pap,chap}] [-r REM_ADDR] [-P VIRTUAL_PORT] + [--timeout TIMEOUT] [-d] [-v] [-k KEY] + {authenticate,authorize,account} ... + + Tacacs+ client with full AAA support: + + * Authentication supports both ascii, pap and chap. + * Authorization supports AV pairs and single commands. + * Accounting support AV pairs and single commands. + + NOTE: shared encryption key can be set via environment variable TACACS_PLUS_KEY or via argument. + NOTE: user password can be setup via environment variable TACACS_PLUS_PWD or via argument. + + + positional arguments: + {authenticate,authorize,account} + action to perform over the tacacs+ server + authenticate authenticate against a tacacs+ server + authorize authorize a command against a tacacs+ server + account account commands with accounting flags against a tacacs+ server + + optional arguments: + -h, --help show this help message and exit + -u USERNAME, --username USERNAME + user name + -H HOST, --host HOST tacacs+ server address + -p PORT, --port PORT tacacs+ server port (default 49) + -l PRIV_LVL, --priv-lvl PRIV_LVL + user privilege level + -t {ascii,pap,chap}, --authen-type {ascii,pap,chap} + authentication type + -r REM_ADDR, --rem-addr REM_ADDR + remote address (logged by tacacs server) + -P VIRTUAL_PORT, --virtual-port VIRTUAL_PORT + console port used in connection (logged by tacacs server) + --timeout TIMEOUT + -d, --debug enable debugging output + -v, --verbose print responses + -k KEY, --key KEY tacacs+ shared encryption key + + $ tacacs_client authenticate -h + usage: tacacs_client authenticate [-h] [-p PASSWORD] + + optional arguments: + -h, --help show this help message and exit + -p PASSWORD, --password PASSWORD + user password + + $ tacacs_client authorize -h + usage: tacacs_client authorize [-h] -c CMDS [CMDS ...] + + optional arguments: + -h, --help show this help message and exit + -c CMDS [CMDS ...], --cmds CMDS [CMDS ...] + list of cmds to authorize + + $ tacacs_client account -h + usage: tacacs_client account [-h] -c CMDS [CMDS ...] -f {start,stop,update} + + optional arguments: + -h, --help show this help message and exit + -c CMDS [CMDS ...], --cmds CMDS [CMDS ...] + list of cmds to authorize + -f {start,stop,update}, --flag {start,stop,update} + accounting flag + +Programmatic Usage +~~~~~~~~~~~~~~~~~~ + +.. code:: python + + #!/usr/bin/env python + from tacacs_plus.client import TACACSClient + from tacacs_plus.flags import TAC_PLUS_ACCT_FLAG_START, TAC_PLUS_ACCT_FLAG_WATCHDOG, TAC_PLUS_ACCT_FLAG_STOP + + cli = TACACSClient('host', 49, 'secret', timeout=10) + + # authenticate user and pass + authen = cli.authenticate('username', 'password') + print "PASS!" if authen.valid else "FAIL!" + + # authorize user and command + author = cli.authorize('username', arguments=[b"service=shell", b"cmd=show", b"cmdargs=version"]) + print "PASS!" if author.valid else "FAIL!" + + # start accounting session for command + acct = cli.account('username', TAC_PLUS_ACCT_FLAG_START, arguments=[b"service=shell", b"cmd=show", b"cmdargs=version"]) + print "PASS!" if acct.valid else "FAIL!" + + # continue accounting session for another command + acct = cli.account('username', TAC_PLUS_ACCT_FLAG_WATCHDOG, arguments=[b"service=shell", b"cmd=debug", b"cmdargs=aaa"]) + print "PASS!" if acct.valid else "FAIL!" + + # close accounting session + acct = cli.account('username', TAC_PLUS_ACCT_FLAG_STOP, arguments=[b"service=shell", b"cmd=exit"]) + print "PASS!" if acct.valid else "FAIL!" + +.. |Build Status| image:: https://travis-ci.org/ansible/tacacs_plus.svg?branch=master + :target: https://travis-ci.org/ansible/tacacs_plus diff --git a/tacacs_plus/client.py b/tacacs_plus/client.py index 5ea3812..a312525 100644 --- a/tacacs_plus/client.py +++ b/tacacs_plus/client.py @@ -32,8 +32,6 @@ class TACACSClient(object): http://www.shrubbery.net/tac_plus/ """ - _sock = None - def __init__(self, host, port, secret, timeout=10, session_id=None, version_max=TAC_PLUS_MAJOR_VER, version_min=TAC_PLUS_MINOR_VER): @@ -47,6 +45,7 @@ def __init__(self, host, port, secret, timeout=10, session_id=None, :param version_max: TACACS+ major version number, 12 :param version_min: TACACS+ minor version number, 0 or 1 """ + self._sock = None self.host = host self.port = port self.secret = secret @@ -85,7 +84,7 @@ def send(self, body, req_type, seq_no=1): """ Send a TACACS+ message body - :param body: packed bytes, i.e., `struct.pack(...) + :param body: packed bytes, i.e., `struct.pack(...)` :param req_type: TAC_PLUS_AUTHEN, TAC_PLUS_AUTHOR, TAC_PLUS_ACCT diff --git a/tacacs_plus/packet.py b/tacacs_plus/packet.py index f945116..f3e1919 100644 --- a/tacacs_plus/packet.py +++ b/tacacs_plus/packet.py @@ -33,11 +33,11 @@ def crypt(header, body_bytes, secret): version, seq_no, MD5_n-1} :param header: a TACACSHeader object - :param body_bytes: packed bytes, i.e., `struct.pack(...) + :param body_bytes: packed bytes, i.e., `struct.pack(...)` :param secret: a key used to encrypt/obfuscate packets according to the TACACS+ spec - :return: packed bytes, i.e., `struct.pack(...) representing the + :return: packed bytes, i.e., `struct.pack(...)` representing the obfuscated packet body """ # noqa @@ -75,7 +75,7 @@ class TACACSPacket(object): def __init__(self, header, body_bytes, secret): """ :param header: a TACACSHeader object - :param body_bytes: packed bytes, i.e., `struct.pack(...) + :param body_bytes: packed bytes, i.e., `struct.pack(...)` :param secret: a key used to encrypt/obfuscate packets according to the TACACS+ spec """