Skip to content

Commit

Permalink
add multi-factor authentication docs (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
greenpau authored Apr 24, 2017
1 parent 4e3b37a commit 6dbc991
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export USER
PLUGIN_NAME="ndmtk"
PLUGIN_NAME_EGG := $(subst -,_,$(PLUGIN_NAME))
PLUGIN_VER=0.1.7
PLUGIN_VER=0.1.8
DOCKER_IMAGE_NAME="greenpau/ndmtk"
DOCKER_CONTAINER_NAME="ndmtk"
DOCKER_CONTAINER_SHELL="/bin/sh"
Expand Down
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ machine:
version: 2.7.5
environment:
PACKAGE: ndmtk
VERSION: '0.1.7'
VERSION: '0.1.8'
TAG1: ${VERSION}-$(date +%Y%m%dT%H%M)-git-${CIRCLE_SHA1:0:7}
TAG2: ${CIRCLE_PR_USERNAME}_${CIRCLE_BRANCH/pull\//pr_}
services:
Expand Down
4 changes: 2 additions & 2 deletions docker/alpine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ COPY demo/firewall/files/ndmtk/spec/*.yml /etc/ansible/files/ndmtk/spec/
COPY demo/firewall/files/ndmtk/os/*.yml /etc/ansible/files/ndmtk/os/
COPY demo/firewall/files/ndmtk/host/*.yml /etc/ansible/files/ndmtk/host/
COPY demo/firewall/files/ndmtk/exceptions.yml /etc/ansible/files/ndmtk/
COPY dist/ndmtk-0.1.7.tar.gz /usr/local/src/
RUN pip install /usr/local/src/ndmtk-0.1.7.tar.gz
COPY dist/ndmtk-0.1.8.tar.gz /usr/local/src/
RUN pip install /usr/local/src/ndmtk-0.1.8.tar.gz

ENTRYPOINT ["/bin/sh"]
4 changes: 2 additions & 2 deletions docker/centos/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ COPY demo/firewall/files/ndmtk/spec/*.yml /etc/ansible/files/ndmtk/spec/
COPY demo/firewall/files/ndmtk/os/*.yml /etc/ansible/files/ndmtk/os/
COPY demo/firewall/files/ndmtk/host/*.yml /etc/ansible/files/ndmtk/host/
COPY demo/firewall/files/ndmtk/exceptions.yml /etc/ansible/files/ndmtk/
COPY dist/ndmtk-0.1.7.tar.gz /usr/local/src/
RUN pip install /usr/local/src/ndmtk-0.1.7.tar.gz
COPY dist/ndmtk-0.1.8.tar.gz /usr/local/src/
RUN pip install /usr/local/src/ndmtk-0.1.8.tar.gz

ENTRYPOINT ["/bin/bash"]
85 changes: 85 additions & 0 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,88 @@ Considerations:
present

:arrow_up: [Back to top](#top)

*****

## Multi-factor Authentication Internals

When an Ansible playbook contains tasks related to `ndmtk` plugin,
Ansible invokes `ndmtk` callback plugin. The plugin performs lookup the
lookup of access credentials in Ansible Vault.

By default, the plugin looks for `safe` and `lockpick` task arguments.
If they are not defined, the plugin attempts to read
`~/.ansible.vault.yml` (safe) and `~/.ansible.vault.key` (lockpick)
files. The looked up access credentials are stored in
`task['args']['credentials']` list and passed to `ndmtk` action plugin.

The action plugin invokes `_load_credentials()` function to parse the
list. The function returns a list of dictionaries.

``` {.sourceCode .json}
[
{u'description': u'SDN Production Cisco Nexus Leaf Switches',
u'password': u'pin,token',
u'password_enable': u'pin,token',
u'pin': u'4526',
u'priority': 1,
u'regex': u'^ny-fw02$',
u'token': u'~/token.bypass',
u'username': u'greenpau'},
{u'default': True,
u'description': u'my default password',
u'password': u'POC123',
u'password_enable': u'POC123',
u'priority': 1,
u'username': u'admin'}
]
```

When the plugin prepares for the connectivity it takes out one access
credentials set (FIFO) and puts it in `self.activekey` variable.

Later, when prompted for the password by a remote device. It fetches the
credentials from that variable using `_get_item_from_key()` function.

When a credential set fails, the plugin will lookup additional
credentials.

``` {.sourceCode .bash}
fatal: [ny-fw02]: FAILED! => {
"changed": false,
"data_dir": "/opt/data/ansible/poc-conf-20170221190959/ny-fw02",
"failed": true,
"junit": "/opt/data/ansible/poc-conf-20170221190959/ny-fw02/ny-fw02.junit.xml",
"msg": "authentication failed",
"temp_dir": "/Users/greenpau/.ansible/tmp/ndmtk/56cce459-f869-11e6-94e9-f45c89b1bb39/56d9c178-f869-11e6-a3a9-f45c89b1bb39/ny-fw02"
}
```

When dealing with credentials requiring PIN and Soft or Hard Token, a
user must provide the path to read tokens via `token` key inside of
access credentials hash. For example, the below hash instructs the
plugin to user PIN plus Token combination for password. The PIN is
`1234` and the token can be found in `~/token.bypass`.

``` {.sourceCode .yaml}
- regex: '^ny-fw01$'
username: 'greenpau'
password: 'pin,token'
password_enable: 'pin,token'
token: '~/.token.bypass'
pin: '1234'
priority: 1
description: 'Token-authenticated device'
```

A user populates the `~/token.bypass` file via CLI command. For example,
the below command send Token `4562356` to `~/token.bypass`.
Additionally, the user specifies the amount of time the Token will be
active, i.e. `10`. The plugin uses the information to determine whether
the token is valid or not.

``` {.sourceCode .bash}
date "+%s;572680;10" > ~/.token.bypass
```

:arrow_up: [Back to top](#top)
81 changes: 81 additions & 0 deletions docs/auth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,84 @@ Considerations:
- There should be no credential with both ``regex`` and ``default`` fields present

|Back to Top|_ `Back to Top`_

Multi-factor Authentication Internals
-------------------------------------

When an Ansible playbook contains tasks related to ``ndmtk`` plugin, Ansible
invokes ``ndmtk`` callback plugin. The plugin performs lookup the lookup of
access credentials in Ansible Vault.

By default, the plugin looks for ``safe`` and ``lockpick`` task arguments. If they
are not defined, the plugin attempts to read ``~/.ansible.vault.yml`` (safe) and
``~/.ansible.vault.key`` (lockpick) files. The looked up access credentials are stored
in ``task['args']['credentials']`` list and passed to ``ndmtk`` action plugin.

The action plugin invokes ``_load_credentials()`` function to parse the list.
The function returns a list of dictionaries.

.. code-block:: json
[
{u'description': u'SDN Production Cisco Nexus Leaf Switches',
u'password': u'pin,token',
u'password_enable': u'pin,token',
u'pin': u'4526',
u'priority': 1,
u'regex': u'^ny-fw02$',
u'token': u'~/token.bypass',
u'username': u'greenpau'},
{u'default': True,
u'description': u'my default password',
u'password': u'POC123',
u'password_enable': u'POC123',
u'priority': 1,
u'username': u'admin'}
]
When the plugin prepares for the connectivity it takes out one access
credentials set (FIFO) and puts it in ``self.activekey`` variable.

Later, when prompted for the password by a remote device. It fetches the
credentials from that variable using ``_get_item_from_key()`` function.

When a credential set fails, the plugin will lookup additional credentials.

.. code-block:: bash
fatal: [ny-fw02]: FAILED! => {
"changed": false,
"data_dir": "/opt/data/ansible/poc-conf-20170221190959/ny-fw02",
"failed": true,
"junit": "/opt/data/ansible/poc-conf-20170221190959/ny-fw02/ny-fw02.junit.xml",
"msg": "authentication failed",
"temp_dir": "/Users/greenpau/.ansible/tmp/ndmtk/56cce459-f869-11e6-94e9-f45c89b1bb39/56d9c178-f869-11e6-a3a9-f45c89b1bb39/ny-fw02"
}
When dealing with credentials requiring PIN and Soft or Hard Token, a user
must provide the path to read tokens via ``token`` key inside of access
credentials hash. For example, the below hash instructs the plugin to
user PIN plus Token combination for password. The PIN is ``1234`` and
the token can be found in ``~/token.bypass``.

.. code-block:: yaml
- regex: '^ny-fw01$'
username: 'greenpau'
password: 'pin,token'
password_enable: 'pin,token'
token: '~/.token.bypass'
pin: '1234'
priority: 1
description: 'Token-authenticated device'
A user populates the ``~/token.bypass`` file via CLI command. For example, the
below command send Token ``4562356`` to ``~/token.bypass``. Additionally, the user
specifies the amount of time the Token will be active, i.e. ``10``. The plugin
uses the information to determine whether the token is valid or not.

.. code-block:: bash
date "+%s;572680;10" > ~/.token.bypass
|Back to Top|_ `Back to Top`_
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@
# built documents.
#
# The short X.Y version.
version = u'0.1.7'
version = u'0.1.8'
# The full version, including alpha/beta/rc tags.
release = u'0.1.7'
release = u'0.1.8'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
68 changes: 35 additions & 33 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import unittest;

pkg_name = 'ndmtk';
pkg_ver = '0.1.7';
pkg_ver = '0.1.8';

cmdclass = {};

Expand All @@ -54,11 +54,11 @@ def pre_build_toolkit():
return [];
print("[INFO] the path to 'ansible' python package is: " + str(ansible_dirs));
for ansible_dir in ansible_dirs:
for suffix in ['.py', '.pyc']:
for plugin_type in ['plugins/action', 'plugins/callback']:
plugin_file = os.path.join(ansible_dir, plugin_type , pkg_name + suffix);
try:
os.unlink(plugin_file);
for suffix in ['.py', '.pyc']:
for plugin_type in ['plugins/action', 'plugins/callback']:
plugin_file = os.path.join(ansible_dir, plugin_type , pkg_name + suffix);
try:
os.unlink(plugin_file);
except:
pass;
try:
Expand All @@ -68,7 +68,7 @@ def pre_build_toolkit():
if os.path.exists(plugin_file):
print("[ERROR] 'ansible' python package contains traces '" + pkg_name + "' package ("+ plugin_file +"), failed to delete, aborting!");
else:
print("[INFO] 'ansible' python package contains traces '" + pkg_name + "' package ("+ plugin_file +"), deleted!");
print("[INFO] 'ansible' python package contains traces '" + pkg_name + "' package ("+ plugin_file +"), deleted!");
return ansible_dirs;

def _find_utility(name):
Expand Down Expand Up @@ -96,8 +96,8 @@ def _find_py_package(name):
def _post_build_toolkit(ansible_dirs, plugin_dir=None):
if plugin_dir is None:
plugin_dirs = _find_py_package(pkg_name);
if len(plugin_dirs) > 0:
print("[INFO] the path to '" + pkg_name + "' python package is: " + str(plugin_dirs));
if len(plugin_dirs) > 0:
print("[INFO] the path to '" + pkg_name + "' python package is: " + str(plugin_dirs));
for d in plugin_dirs:
if re.search('bdist', d) or re.search('build', d):
continue;
Expand All @@ -119,24 +119,26 @@ def _post_build_toolkit(ansible_dirs, plugin_dir=None):
for i in ['action', 'callback']:
symlink_target = os.path.join(plugin_dir, 'plugins/' + i + '/ndmtk.py');
symlink_name = os.path.join(ansible_dir, 'plugins/' + i + '/ndmtk.py');
try:
try:
os.symlink(symlink_target, symlink_name);
os.chmod(symlink_name, stat.S_IRUSR | stat.S_IWUSR);
_egg_files.append(symlink_name);
_egg_files.append(symlink_name);
_egg_files.append(symlink_name + 'c');
print("[INFO] created symlink '" + symlink_name + "' to plugin '" + symlink_target + "'");
except:
print('[ERROR] an attempt to create a symlink ' + symlink_name + ' to plugin ' + symlink_target + ' failed, aborting!');
exc_type, exc_value, exc_traceback = sys.exc_info();
print('[ERROR] an attempt to create a symlink ' + symlink_name + ' to plugin ' + symlink_target + ' failed, aborting!');
print(traceback.format_exception(exc_type, exc_value, exc_traceback));
return;

class install_(install):
def run(self):
ansible_dirs = pre_build_toolkit();
if len(ansible_dirs) == 0:
return 1;
if len(ansible_dirs) == 0:
return 1;
install.run(self);
if len(ansible_dirs) > 0:
self.execute(_post_build_toolkit, (ansible_dirs, self.install_lib, ), msg="running post_install_scripts");
if len(ansible_dirs) > 0:
self.execute(_post_build_toolkit, (ansible_dirs, self.install_lib, ), msg="running post_install_scripts");

cmdclass['install'] = install_;
cmdclass['bdist_wheel'] = install_;
Expand All @@ -145,34 +147,34 @@ class uninstall_(develop):
def run(self):
plugin_dirs = [];
for dp in sys.path:
if not re.search('site-packages$', dp):
continue;
if not re.search('site-packages$', dp):
continue;
ds = [name for name in os.listdir(dp) if os.path.isdir(os.path.join(dp, name))];
if ds:
for d in ds:
if not re.match(pkg_name, d):
continue;
if os.path.join(dp, d) not in plugin_dirs:
if ds:
for d in ds:
if not re.match(pkg_name, d):
continue;
if os.path.join(dp, d) not in plugin_dirs:
plugin_dirs.append(os.path.join(dp, d));
if plugin_dirs:
for dp in plugin_dirs:
try:
for root, dirs, files in os.walk(dp, topdown=False):
for dp in plugin_dirs:
try:
for root, dirs, files in os.walk(dp, topdown=False):
for name in files:
if os.path.islink(os.path.join(root, name)):
os.unlink(os.path.join(root, name));
os.unlink(os.path.join(root, name));
else:
os.remove(os.path.join(root, name));
for name in dirs:
os.rmdir(os.path.join(root, name));
os.rmdir(dp);
os.rmdir(dp);
print("[INFO] deleted '" + dp + "'");
except:
print("[INFO] failed to delete '" + dp + "'");
exc_type, exc_value, exc_traceback = sys.exc_info();
print(traceback.format_exception(exc_type, exc_value, exc_traceback));
except:
print("[INFO] failed to delete '" + dp + "'");
exc_type, exc_value, exc_traceback = sys.exc_info();
print(traceback.format_exception(exc_type, exc_value, exc_traceback));
else:
print("[INFO] no relevant files for the uninstall found, all clean");
print("[INFO] no relevant files for the uninstall found, all clean");

ansible_dirs = _find_py_package('ansible');
if len(ansible_dirs) == 0:
Expand Down

0 comments on commit 6dbc991

Please sign in to comment.