This command behaves almost exactly like otptoken-add except: 1. The new token data is written directly to a YubiKey 2. The vendor/model/serial fields are populated from the YubiKey
=== NOTE === 1. This patch depends on the new Fedora package: python-yubico. If you would like to help with the package review, please assign yourself here: https://bugzilla.redhat.com/show_bug.cgi?id=1111334 2. This patch doesn't actually work and I could use some help. The call to api.Command.otptoken_add() fails with: ipa: ERROR: non-public: AttributeError: no context.rpcclient in thread 'MainThread' Traceback (most recent call last): File "/usr/lib/python2.7/site-packages/ipalib/backend.py", line 129, in execute result = self.Command[_name](*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 439, in __call__ ret = self.run(*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 1118, in run return self.forward(*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/plugins/otptoken.py", line 471, in forward **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 439, in __call__ ret = self.run(*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 755, in run return self.forward(*args, **options) File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 776, in forward return self.Backend.rpcclient.forward(self.name, *args, **kw) File "/usr/lib/python2.7/site-packages/ipalib/rpc.py", line 874, in forward command = getattr(self.conn, name) File "/usr/lib/python2.7/site-packages/ipalib/backend.py", line 97, in __get_conn self.id, threading.currentThread().getName()) AttributeError: no context.rpcclient in thread 'MainThread' ipa: ERROR: an internal error has occurred
From 75f1a74dd2476d61fc47e5ab184acaf84261fe54 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum <npmccal...@redhat.com> Date: Thu, 19 Jun 2014 12:28:32 -0400 Subject: [PATCH] Add the otptoken-add-yubikey command This command behaves almost exactly like otptoken-add except: 1. The new token data is written directly to a YubiKey 2. The vendor/model/serial fields are populated from the YubiKey --- freeipa.spec.in | 1 + ipalib/plugins/otptoken.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/freeipa.spec.in b/freeipa.spec.in index b719b4a21fd2263a6fb58b3dffd4784868e630e9..4228c88f939a361a32da6f875c48db8adee3cdc1 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -158,6 +158,7 @@ Requires(pre): certmonger >= 0.65 Requires(pre): 389-ds-base >= 1.3.2.11 Requires: fontawesome-fonts Requires: open-sans-fonts +Requires: python-yubico # With FreeIPA 3.3, package freeipa-server-selinux was obsoleted as the # entire SELinux policy is stored in the system policy diff --git a/ipalib/plugins/otptoken.py b/ipalib/plugins/otptoken.py index d834d582a16d95ab08c3f1fe1aef29160c77ae23..9746d2c96753111a05864b7151b12761553785a3 100644 --- a/ipalib/plugins/otptoken.py +++ b/ipalib/plugins/otptoken.py @@ -21,6 +21,7 @@ from ipalib.plugins.baseldap import DN, LDAPObject, LDAPAddMember, LDAPRemoveMem from ipalib.plugins.baseldap import LDAPCreate, LDAPDelete, LDAPUpdate, LDAPSearch, LDAPRetrieve from ipalib import api, Int, Str, Bool, Flag, Bytes, IntEnum, StrEnum, _, ngettext from ipalib.plugable import Registry +from ipalib.frontend import Local from ipalib.errors import PasswordMismatch, ConversionError, LastMemberError, NotFound from ipalib.request import context import base64 @@ -28,6 +29,7 @@ import uuid import urllib import qrcode import os +import yubico __doc__ = _(""" OTP Tokens @@ -383,3 +385,84 @@ class otptoken_remove_managedby(LDAPRemoveMember): __doc__ = _('Remove hosts that can manage this host.') member_attributes = ['managedby'] + +@register() +class otptoken_add_yubikey(Local): + __doc__ = _('Add a new YubiKey OTP token.') + + takes_args = ( + Str('ipatokenuniqueid?', + cli_name='id', + label=_('Unique ID'), + primary_key=True, + ), + ) + + takes_options = Local.takes_options + ( + IntEnum('slot?', + cli_name='slot', + label=_('YubiKey slot'), + values=(1, 2), + ), + ) + tuple([x for x in otptoken.takes_params if x.name in ( + 'description', + 'ipatokenowner', + 'ipatokendisabled', + 'ipatokennotbefore', + 'ipatokennotafter', + 'ipatokenotpkey', + 'ipatokenotpdigits' + )]) + + def write_yubikey(self, **kwargs): + assert len(kwargs['ipatokenotpkey']) == 20 + + # Open the YubiKey + yk = yubico.find_yubikey() + + # If no slot is specified, find the first free slot. + slot = kwargs.get('slot', None) + if slot is None: + try: + used = yk.status().valid_configs() + slot = sorted({1, 2}.difference(used))[0] + except IndexError: + raise ValueError('No free YubiKey slot!') + + # Write the config. + cfg = yk.init_config() + cfg.mode_oath_hotp(kwargs['ipatokenotpkey'], kwargs['ipatokenotpdigits']) + cfg.extended_flag('SERIAL_API_VISIBLE', True) + yk.write_config(cfg, slot=slot) + + return { + 'vendor': u'YubiCo', + 'model': unicode(yk.model), + 'serial': unicode(yk.serial()) + } + + def forward(self, *args, **kwargs): + yk = self.write_yubikey(**kwargs) + + id = args[0] + if id is None: + id = unicode(uuid.uuid4()) + + options = {k: v for k, v in kwargs.items() if k in ( + 'description', + 'ipatokenowner', + 'ipatokendisabled', + 'ipatokennotbefore', + 'ipatokennotafter', + 'ipatokenotpkey', + 'ipatokenotpdigits' + )} + + return self.api.Command.otptoken_add(id, + type=u'hotp', + ipatokenvendor=yk['vendor'], + ipatokenmodel=yk['model'], + ipatokenserial=yk['serial'], + ipatokenotpalgorithm=u'sha1', + ipatokenhotpcounter=0, + **options) -- 2.0.0
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel