[Freeipa-devel] [PATCH] Password vault

Endi Sukma Dewata edewata at redhat.com
Tue Jun 2 00:00:09 UTC 2015


Please take a look at the updated patch.

On 5/27/2015 12:39 AM, Jan Cholasta wrote:
>>>>>>> 21) vault_archive is not a retrieve operation, it should be based on
>>>>>>> LDAPUpdate instead of LDAPRetrieve. Or Command actually, since it
>>>>>>> does
>>>>>>> not do anything with LDAP. The same applies to vault_retrieve.
>>>>>>
>>>>>> The vault_archive does not actually modify the LDAP entry because it
>>>>>> stores the data in KRA. It is actually an LDAPRetrieve operation
>>>>>> because
>>>>>> it needs to get the vault info before it can perform the archival
>>>>>> operation. Same thing with vault_retrieve.
>>>>>
>>>>> It is not a LDAPRetrieve operation, because it has different
>>>>> semantics.
>>>>> Please use Command as base class and either use ldap2 for direct
>>>>> LDAP or
>>>>> call vault_show instead of hacking around LDAPRetrieve.
>>>>
>>>> It's been changed to inherit from LDAPQuery instead.
>>>
>>> NACK, it's not a LDAPQuery operation, because it has different
>>> semantics. There is more to a command than executing code, so you should
>>> use a correct base class.
>>
>> Changed to inherit from Command as requested. Now these commands no
>> longer have a direct access to the vault object (self.obj) although they
>> are accessing vault objects like other vault commands. Also now the
>> vault name argument has to be added explicitly on each command.
>
> You can inherit from crud.Retrieve and crud.Update to get self.obj and
> the argument back.

I tried this:

   class vault_retrieve(Command, crud.Retrieve):

and it gave me an error:

   TypeError: Error when calling the metaclass bases
       Cannot create a consistent method resolution
   order (MRO) for bases Retrieve, Command

I'm sticking with the original code since it works fine although not 
ideal. I'm not a Python expert, so if you know how to fix this properly 
please feel free to post a patch on top of this.

> If KRA is not installed, vault-archive and vault-retrieve fail with
> internal error.

Added a code to check KRA installation in all vault commands. If you 
know a way not to load the vault plugin if the KRA is not installed 
please let me know, that's probably even better. Not sure how that will 
work on the client side though.

> The commands still behave differently based on whether they were called
> from API which was initialized with in_server set to True or False.

That is unfortunately a restriction imposed by the framework. In order 
to guarantee the security, the vault is designed to have separate client 
and server code. The client code encrypts the secret, the server code 
forwards the encrypted secret to KRA. To archive a secret into a vault 
properly, you are supposed to call the client code. If you're calling 
the server code directly, you are responsible to do your own encryption 
(i.e. generating session key, nonce, and vault data).

If another plugin wants to use vault, it should implement a client code 
which calls the vault client code to perform the archival from the 
client side.

What is the use case for calling the vault API from the server side 
anyway? Wouldn't that defeat the purpose of having a vault? If a secret 
exists on the server side in an unencrypted form doesn't it mean the 
secret may already have been compromised?

> There is no point in exposing the session_key, nonce and vault_data
> options in CLI when their value is always overwritten in forward().

I agree there is no need to expose them in CLI, but in this framework 
the API also defines the CLI. If there's a way to keep them in the 
server API but not expose them in the CLI please let me know. Or, if 
there's a way to define completely separate server API (without a 
matching client CLI) and client CLI (without a matching server API) that 
will work too.

> Will this always succeed?
>
> +        # deactivate vault record in KRA
> +        response = kra_client.keys.list_keys(
> +            client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE)

Yes. If there's no active keys it will return an empty collection.

> +        for key_info in response.key_infos:
> +            kra_client.keys.modify_key_status(
> +                key_info.get_key_id(),
> +                pki.key.KeyClient.KEY_STATUS_INACTIVE)

This loop will do nothing given an empty collection.

> If not, we might get into an inconsistent state, where the vault is
> deleted in LDAP but still active in KRA. (I'm not sure if this is
> actually a problem or not.)

That can only happen if the server crashes after deleting the vault but 
before deactivating the key. Regardless, it will not be a problem 
because the key is identified by vault ID/path so it will not conflict 
with other vaults, and it will get overwritten if the same vault is 
recreated again.

-- 
Endi S. Dewata
-------------- next part --------------
>From d1123f07745fea856ced305a814d933cd793dbf2 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
Date: Tue, 21 Oct 2014 10:57:08 -0400
Subject: [PATCH] Added vault-archive and vault-retrieve commands.

New commands have been added to archive and retrieve
data into and from a vault, also to retrieve the
transport certificate.

https://fedorahosted.org/freeipa/ticket/3872
---
 API.txt                                   |  28 ++
 VERSION                                   |   4 +-
 ipalib/plugins/vault.py                   | 501 +++++++++++++++++++++++++++++-
 ipatests/test_xmlrpc/test_vault_plugin.py |  71 ++++-
 4 files changed, 600 insertions(+), 4 deletions(-)

diff --git a/API.txt b/API.txt
index da69f32de5c12c0d85a7d61d9027385aa3c0ee05..3741e6f16689e43838c2d31a44872d1ea47589c7 100644
--- a/API.txt
+++ b/API.txt
@@ -4768,6 +4768,24 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: vault_archive
+args: 1,9,1
+arg: Str('cn', cli_name='name', maxlength=255, pattern='^[a-zA-Z0-9_.-]+$')
+option: Bytes('data?')
+option: Str('in?')
+option: Str('nonce?')
+option: Str('service?')
+option: Str('session_key?')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('vault_data?')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
+command: vault_config
+args: 0,2,1
+option: Str('transport_out?')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
 command: vault_del
 args: 1,5,3
 arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
@@ -4814,6 +4832,16 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: vault_retrieve
+args: 1,6,1
+arg: Str('cn', cli_name='name', maxlength=255, pattern='^[a-zA-Z0-9_.-]+$')
+option: Str('out?')
+option: Str('service?')
+option: Str('session_key?')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
 command: vault_show
 args: 1,7,3
 arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
diff --git a/VERSION b/VERSION
index 07c00d000064a7687497b09524aa821dbcecc88a..2bfb2fe46b3760f30e1aa378841544a51f014728 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=121
-# Last change: pvoborni - added server-find and server-show
+IPA_API_VERSION_MINOR=122
+# Last change: edewata - added vault-archive and vault-retrieve
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index ebb9f9fd3cf3b5a7d6b44ac9e63e122e8f71aa1a..cb68d136e98efa73ce8281ee12ef897af67c3da9 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -17,8 +17,21 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import base64
+import json
+import os
+import sys
+import tempfile
+
+import nss.nss as nss
+
+import pki.account
+import pki.crypto
+import pki.key
+
+from ipalib.frontend import Command
 from ipalib import api, errors
-from ipalib import Str, Flag
+from ipalib import Bytes, Str, Flag
 from ipalib import output
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, LDAPDelete,\
@@ -26,7 +39,9 @@ from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, LDAPDelete,\
 from ipalib.request import context
 from ipalib.plugins.user import split_principal
 from ipalib import _, ngettext
+from ipaplatform.paths import paths
 from ipapython.dn import DN
+from ipapython.nsslib import current_dbdir
 
 __doc__ = _("""
 Vaults
@@ -94,6 +109,33 @@ EXAMPLES:
 """) + _("""
  Delete a user vault:
    ipa vault-del <name> --user <username>
+""") + _("""
+ Display vault configuration:
+   ipa vault-config
+""") + _("""
+ Archive data into private vault:
+   ipa vault-archive <name> --in <input file>
+""") + _("""
+ Archive data into service vault:
+   ipa vault-archive <name> --service <service name> --in <input file>
+""") + _("""
+ Archive data into shared vault:
+   ipa vault-archive <name> --shared --in <input file>
+""") + _("""
+ Archive data into user vault:
+   ipa vault-archive <name> --user <username> --in <input file>
+""") + _("""
+ Retrieve data from private vault:
+   ipa vault-retrieve <name> --out <output file>
+""") + _("""
+ Retrieve data from service vault:
+   ipa vault-retrieve <name> --service <service name> --out <output file>
+""") + _("""
+ Retrieve data from shared vault:
+   ipa vault-retrieve <name> --shared --out <output file>
+""") + _("""
+ Retrieve data from user vault:
+   ipa vault-retrieve <name> --user <user name> --out <output file>
 """)
 
 register = Registry()
@@ -243,6 +285,26 @@ class vault(LDAPObject):
         for entry in entries:
             self.backend.add_entry(entry)
 
+    def get_key_id(self, dn):
+        """
+        Generates a client key ID to archive/retrieve data in KRA.
+        """
+
+        # TODO: create container_dn after object initialization then reuse it
+        container_dn = DN(self.container_dn, self.api.env.basedn)
+
+        # make sure the DN is a vault DN
+        if not dn.endswith(container_dn, 1):
+            raise ValueError('Invalid vault DN: %s' % dn)
+
+        # construct the vault ID from the bottom up
+        id = u''
+        for rdn in dn[:-len(container_dn)]:
+            name = rdn['cn']
+            id = u'/' + name + id
+
+        return 'ipa:' + id
+
 
 @register()
 class vault_add(LDAPCreate):
@@ -256,6 +318,10 @@ class vault_add(LDAPCreate):
                      **options):
         assert isinstance(dn, DN)
 
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
         try:
             parent_dn = DN(*dn[1:])
             self.obj.create_container(parent_dn)
@@ -273,6 +339,38 @@ class vault_del(LDAPDelete):
 
     msg_summary = _('Deleted vault "%(value)s"')
 
+    def pre_callback(self, ldap, dn, *keys, **options):
+        assert isinstance(dn, DN)
+
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
+        return dn
+
+    def post_callback(self, ldap, dn, *args, **options):
+        assert isinstance(dn, DN)
+
+        kra_client = self.api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = self.obj.get_key_id(dn)
+
+        # deactivate vault record in KRA
+        response = kra_client.keys.list_keys(
+            client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE)
+
+        for key_info in response.key_infos:
+            kra_client.keys.modify_key_status(
+                key_info.get_key_id(),
+                pki.key.KeyClient.KEY_STATUS_INACTIVE)
+
+        kra_account.logout()
+
+        return True
+
 
 @register()
 class vault_find(LDAPSearch):
@@ -290,6 +388,10 @@ class vault_find(LDAPSearch):
                      **options):
         assert isinstance(base_dn, DN)
 
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
         base_dn = self.obj.get_dn(*args, **options)
 
         return (filter, base_dn, scope)
@@ -313,9 +415,406 @@ class vault_mod(LDAPUpdate):
 
     msg_summary = _('Modified vault "%(value)s"')
 
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list,
+                     *keys, **options):
+
+        assert isinstance(dn, DN)
+
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
+        return dn
+
 
 @register()
 class vault_show(LDAPRetrieve):
     __doc__ = _('Display information about a vault.')
 
     takes_options = LDAPRetrieve.takes_options + vault_options
+
+    def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
+        return dn
+
+
+ at register()
+class vault_config(Command):
+    __doc__ = _('Show vault configuration.')
+
+    takes_options = (
+        Str(
+            'transport_out?',
+            doc=_('Output file to store the transport certificate'),
+        ),
+    )
+
+    has_output_params = (
+        Str(
+            'transport_cert',
+            label=_('Transport Certificate'),
+        ),
+    )
+
+    def forward(self, *args, **options):
+
+        file = options.get('transport_out')
+
+        # don't send these parameters to server
+        if 'transport_out' in options:
+            del options['transport_out']
+
+        response = super(vault_config, self).forward(*args, **options)
+
+        if file:
+            with open(file, 'w') as f:
+                f.write(response['result']['transport_cert'])
+
+        return response
+
+    def execute(self, *args, **options):
+
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
+        kra_client = self.api.Backend.kra.get_client()
+        transport_cert = kra_client.system_certs.get_transport_cert()
+        return {
+            'result': {
+                'transport_cert': transport_cert.encoded
+            }
+        }
+
+
+ at register()
+class vault_archive(Command):
+    __doc__ = _('Archive data into a vault.')
+
+    takes_args = (
+        Str(
+            'cn',
+            cli_name='name',
+            label=_('Vault name'),
+            pattern='^[a-zA-Z0-9_.-]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            maxlength=255,
+        ),
+    )
+
+    takes_options = vault_options + (
+        Bytes(
+            'data?',
+            doc=_('Binary data to archive'),
+        ),
+        Str(  # TODO: use File parameter
+            'in?',
+            doc=_('File containing data to archive'),
+        ),
+        Str(
+            'session_key?',
+            doc=_(
+                'Session key wrapped with transport certificate'
+                ' and encoded in base-64'),
+        ),
+        Str(
+            'vault_data?',
+            doc=_(
+                'Vault data encrypted with session key'
+                ' and encoded in base-64'),
+        ),
+        Str(
+            'nonce?',
+            doc=_('Nonce encrypted encoded in base-64'),
+        ),
+    )
+
+    msg_summary = _('Archived data into vault "%(value)s"')
+
+    def forward(self, *args, **options):
+
+        data = options.get('data')
+        input_file = options.get('in')
+
+        # don't send these parameters to server
+        if 'data' in options:
+            del options['data']
+        if 'in' in options:
+            del options['in']
+
+        # get data
+        if data and input_file:
+            raise errors.MutuallyExclusiveError(
+                reason=_('Input data specified multiple times'))
+
+        if input_file:
+            with open(input_file, 'rb') as f:
+                data = f.read()
+
+        elif not data:
+            data = ''
+
+        # initialize NSS database
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+        current_dbdir = paths.IPA_NSSDB_DIR
+
+        # retrieve transport certificate
+        (file, filename) = tempfile.mkstemp()
+        os.close(file)
+        try:
+            self.api.Command.vault_config(transport_out=unicode(filename))
+            transport_cert_der = nss.read_der_from_file(filename, True)
+            nss_transport_cert = nss.Certificate(transport_cert_der)
+
+        finally:
+            os.remove(filename)
+
+        # generate session key
+        session_key = crypto.generate_session_key()
+
+        # wrap session key with transport certificate
+        wrapped_session_key = crypto.asymmetric_wrap(
+            session_key,
+            nss_transport_cert
+        )
+
+        options['session_key'] = base64.b64encode(wrapped_session_key)\
+            .decode('utf-8')
+
+        nonce = crypto.generate_nonce_iv()
+        options['nonce'] = base64.b64encode(nonce).decode('utf-8')
+
+        vault_data = {}
+        vault_data[u'data'] = base64.b64encode(data).decode('utf-8')
+
+        json_vault_data = json.dumps(vault_data)
+
+        # wrap vault_data with session key
+        wrapped_vault_data = crypto.symmetric_wrap(
+            json_vault_data,
+            session_key,
+            nonce_iv=nonce
+        )
+
+        options['vault_data'] = base64.b64encode(wrapped_vault_data)\
+            .decode('utf-8')
+
+        return super(vault_archive, self).forward(*args, **options)
+
+    def execute(self, *args, **options):
+
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
+        vault_name = args[0]
+
+        # retrieve vault info
+        vault = self.api.Command.vault_show(
+            vault_name,
+            service=options.get('service'),
+            shared=options.get('shared'),
+            user=options.get('user'),
+        )['result']
+
+        # connect to KRA
+        kra_client = self.api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = self.api.Object.vault.get_key_id(vault['dn'])
+
+        # deactivate existing vault record in KRA
+        response = kra_client.keys.list_keys(
+            client_key_id,
+            pki.key.KeyClient.KEY_STATUS_ACTIVE)
+
+        for key_info in response.key_infos:
+            kra_client.keys.modify_key_status(
+                key_info.get_key_id(),
+                pki.key.KeyClient.KEY_STATUS_INACTIVE)
+
+        wrapped_session_key = base64.b64decode(options['session_key'])
+        nonce = base64.b64decode(options['nonce'])
+
+        # forward wrapped data to KRA
+        wrapped_vault_data = base64.b64decode(options['vault_data'])
+
+        kra_client.keys.archive_encrypted_data(
+            client_key_id,
+            pki.key.KeyClient.PASS_PHRASE_TYPE,
+            wrapped_vault_data,
+            wrapped_session_key,
+            None,
+            nonce,
+        )
+
+        kra_account.logout()
+
+        response = {}
+        response['result'] = {}
+
+        return response
+
+
+ at register()
+class vault_retrieve(Command):
+    __doc__ = _('Retrieve a data from a vault.')
+
+    takes_args = (
+        Str(
+            'cn',
+            cli_name='name',
+            label=_('Vault name'),
+            pattern='^[a-zA-Z0-9_.-]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            maxlength=255,
+        ),
+    )
+
+    takes_options = vault_options + (
+        Str(
+            'out?',
+            doc=_('File to store retrieved data'),
+        ),
+        Str(
+            'session_key?',
+            doc=_(
+                'Session key wrapped with transport certificate'
+                ' and encoded in base-64'),
+        ),
+    )
+
+    has_output_params = (
+        Bytes(
+            'data',
+            label=_('Data'),
+        ),
+    )
+
+    msg_summary = _('Retrieved data from vault "%(value)s"')
+
+    def forward(self, *args, **options):
+
+        output_file = options.get('out')
+
+        # don't send these parameters to server
+        if 'out' in options:
+            del options['out']
+
+        # initialize NSS database
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+        current_dbdir = paths.IPA_NSSDB_DIR
+
+        # retrieve transport certificate
+        (file, filename) = tempfile.mkstemp()
+        os.close(file)
+        try:
+            self.api.Command.vault_config(transport_out=unicode(filename))
+            transport_cert_der = nss.read_der_from_file(filename, True)
+            nss_transport_cert = nss.Certificate(transport_cert_der)
+
+        finally:
+            os.remove(filename)
+
+        # generate session key
+        session_key = crypto.generate_session_key()
+
+        # wrap session key with transport certificate
+        wrapped_session_key = crypto.asymmetric_wrap(
+            session_key,
+            nss_transport_cert
+        )
+
+        # send retrieval request to server
+        options['session_key'] = base64.b64encode(wrapped_session_key)\
+            .decode('utf-8')
+
+        response = super(vault_retrieve, self).forward(*args, **options)
+
+        result = response['result']
+        nonce = base64.b64decode(result['nonce'])
+
+        # unwrap data with session key
+        wrapped_vault_data = base64.b64decode(result['vault_data'])
+
+        json_vault_data = crypto.symmetric_unwrap(
+            wrapped_vault_data,
+            session_key,
+            nonce_iv=nonce)
+
+        vault_data = json.loads(json_vault_data)
+        data = base64.b64decode(vault_data[u'data'].encode('utf-8'))
+
+        if output_file:
+            response = {}
+            response['result'] = {}
+            with open(output_file, 'w') as f:
+                f.write(data)
+
+        else:
+            response['result']['data'] = data
+            del response['result']['nonce']
+            del response['result']['vault_data']
+
+        return response
+
+    def execute(self, *args, **options):
+
+        if not self.api.env.enable_kra:
+            raise errors.InvocationError(
+                format=_('KRA service is not enabled'))
+
+        vault_name = args[0]
+
+        # retrieve vault info
+        vault = self.api.Command.vault_show(
+            vault_name,
+            service=options.get('service'),
+            shared=options.get('shared'),
+            user=options.get('user'),
+        )['result']
+
+        wrapped_session_key = base64.b64decode(options['session_key'])
+
+        # connect to KRA
+        kra_client = self.api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = self.api.Object.vault.get_key_id(vault['dn'])
+
+        # find vault record in KRA
+        response = kra_client.keys.list_keys(
+            client_key_id,
+            pki.key.KeyClient.KEY_STATUS_ACTIVE)
+
+        if not len(response.key_infos):
+            raise errors.NotFound(reason=_('No archived data.'))
+
+        key_info = response.key_infos[0]
+
+        # retrieve encrypted data from KRA
+        key = kra_client.keys.retrieve_key(
+            key_info.get_key_id(),
+            wrapped_session_key)
+
+        vault['vault_data'] = base64.b64encode(
+            key.encrypted_data).decode('utf-8')
+        vault['nonce'] = base64.b64encode(key.nonce_data).decode('utf-8')
+
+        kra_account.logout()
+
+        response = {}
+        response['result'] = vault
+
+        return response
diff --git a/ipatests/test_xmlrpc/test_vault_plugin.py b/ipatests/test_xmlrpc/test_vault_plugin.py
index 44d397c583928d98ec252899398ae6c3a83c207c..0664addd646806f1b8a5083ef5da16c4dfc015dc 100644
--- a/ipatests/test_xmlrpc/test_vault_plugin.py
+++ b/ipatests/test_xmlrpc/test_vault_plugin.py
@@ -22,12 +22,15 @@ Test the `ipalib/plugins/vault.py` module.
 """
 
 from ipalib import api, errors
-from xmlrpc_test import Declarative, fuzzy_string
+from xmlrpc_test import Declarative
 
 vault_name = u'test_vault'
 service_name = u'HTTP/server.example.com'
 user_name = u'testuser'
 
+# binary data from \x00 to \xff
+secret = ''.join(map(chr, xrange(0, 256)))
+
 
 class test_vault_plugin(Declarative):
 
@@ -442,4 +445,70 @@ class test_vault_plugin(Declarative):
             },
         },
 
+        {
+            'desc': 'Create vault for archival',
+            'command': (
+                'vault_add',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': 'Added vault "%s"' % vault_name,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive secret',
+            'command': (
+                'vault_archive',
+                [vault_name],
+                {
+                    'data': secret,
+                },
+            ),
+            'expected': {
+                'result': {},
+            },
+        },
+
+        {
+            'desc': 'Retrieve secret',
+            'command': (
+                'vault_retrieve',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'cn': [vault_name],
+                    'data': secret,
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete vault for archival',
+            'command': (
+                'vault_del',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': [vault_name],
+                'summary': u'Deleted vault "%s"' % vault_name,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
     ]
-- 
1.9.3



More information about the Freeipa-devel mailing list