On 10/15/2014 10:59 PM, Endi Sukma Dewata wrote:
The NSSConnection class has to be modified not to shutdown existing
database because some of the vault clients (e.g. vault-archive and
vault-retrieve) also use a database to encrypt/decrypt the secret.


The problem is described in more detail in this ticket:
https://fedorahosted.org/freeipa/ticket/4638

The changes to the NSSConnection in the first patch caused the installation to fail. Attached is a new patch that uses the solution proposed by jdennis.

--
Endi S. Dewata
>From 65929b64da8d273a8b991845e47e69c1b3182f79 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edew...@redhat.com>
Date: Tue, 16 Sep 2014 20:11:35 -0400
Subject: [PATCH] Added initial vault implementation.

This patch provides the initial vault implementation which allows
the admin to create a vault, archive a secret, and retrieve the
secret using a standard vault.

It currently has limitations including:
 - The vault only supports the standard vault type.
 - The vault can only be used by the admin user.
 - The transport certificate has to be installed manually.

These limitations, other vault features, schema and ACL changes will
be addressed in subsequent patches.

The NSSConnection class has been modified not to shutdown existing
database because some of the vault clients (e.g. vault-archive and
vault-retrieve) also use a database to encrypt/decrypt the secret.

Ticket #4638, #3872
---
 API.txt                            | 160 ++++++++
 VERSION                            |   4 +-
 install/share/60basev4.ldif        |   3 +
 install/share/Makefile.am          |   1 +
 install/share/copy-schema-to-ca.py |   1 +
 install/updates/40-vault.update    |  27 ++
 install/updates/Makefile.am        |   1 +
 ipa-client/man/default.conf.5      |   1 +
 ipalib/constants.py                |   1 +
 ipalib/plugins/user.py             |   9 +
 ipalib/plugins/vault.py            | 726 +++++++++++++++++++++++++++++++++++++
 ipalib/rpc.py                      |  34 +-
 ipapython/nsslib.py                |  31 +-
 ipaserver/install/dsinstance.py    |   1 +
 14 files changed, 973 insertions(+), 27 deletions(-)
 create mode 100644 install/share/60basev4.ldif
 create mode 100644 install/updates/40-vault.update
 create mode 100644 ipalib/plugins/vault.py

diff --git a/API.txt b/API.txt
index 
1af78509732b13eec07208114cea00e56c1059b4..1eec3527e36bc250acddbf0e2fe7a6baa30abd74
 100644
--- a/API.txt
+++ b/API.txt
@@ -4373,6 +4373,166 @@ option: Str('version?', exclude='webui')
 output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: vault_add
+args: 1,8,3
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('description', attribute=True, cli_name='desc', multivalue=False, 
required=False)
+option: Str('in?', cli_name='in')
+option: Str('parent', attribute=False, cli_name='parent', multivalue=False, 
required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Bytes('secret', attribute=True, cli_name='secret', multivalue=False, 
required=False)
+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,10,3
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Bytes('encrypted_data?', cli_name='encrypted_data')
+option: Str('in?', cli_name='in')
+option: Bytes('nonce?', cli_name='nonce')
+option: Str('parent?', cli_name='parent')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Bytes('secret?', cli_name='secret')
+option: Str('version?', exclude='webui')
+option: Bytes('wrapped_session_key?', cli_name='wrapped_session_key')
+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_del
+args: 1,3,3
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, 
multivalue=True, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('parent?', cli_name='parent')
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: vault_find
+args: 1,10,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='vault_name', 
maxlength=255, multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', 
multivalue=False, query=True, required=False)
+option: Str('parent', attribute=False, autofill=False, cli_name='parent', 
multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Bytes('secret', attribute=True, autofill=False, cli_name='secret', 
multivalue=False, query=True, required=False)
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A 
list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: vault_mod
+args: 1,10,3
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='desc', 
multivalue=False, required=False)
+option: Str('parent', attribute=False, autofill=False, cli_name='parent', 
multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Bytes('secret', attribute=True, autofill=False, cli_name='secret', 
multivalue=False, required=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+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,7,3
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('out?', cli_name='out')
+option: Str('parent?', cli_name='parent')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+option: Bytes('wrapped_session_key?', cli_name='wrapped_session_key')
+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_show
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('parent?', cli_name='parent')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+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: vaultcontainer_add
+args: 1,7,3
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('description', attribute=True, cli_name='desc', multivalue=False, 
required=False)
+option: Str('parent', attribute=False, cli_name='parent', multivalue=False, 
required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+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: vaultcontainer_del
+args: 1,3,3
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, 
multivalue=True, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('parent?', cli_name='parent')
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: vaultcontainer_find
+args: 1,9,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='container_name', 
maxlength=255, multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', 
multivalue=False, query=True, required=False)
+option: Str('parent', attribute=False, autofill=False, cli_name='parent', 
multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A 
list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: vaultcontainer_mod
+args: 1,9,3
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='desc', 
multivalue=False, required=False)
+option: Str('parent', attribute=False, autofill=False, cli_name='parent', 
multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+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: vaultcontainer_show
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 
primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('parent?', cli_name='parent')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+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)
 capability: messages 2.52
 capability: optional_uid_params 2.54
 capability: permissions2 2.69
diff --git a/VERSION b/VERSION
index 
24a6a5ebc70e7ddf6626666e5f9252c44a29d368..4dff96f7242e4a0ff8f914f3e15c5833d4753113
 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=105
-# Last change: abbra - ID views attributes
+IPA_API_VERSION_MINOR=106
+# Last change: edewata - initial vault implementation
diff --git a/install/share/60basev4.ldif b/install/share/60basev4.ldif
new file mode 100644
index 
0000000000000000000000000000000000000000..5a077b5a393067169015c71458632a1b3ee77189
--- /dev/null
+++ b/install/share/60basev4.ldif
@@ -0,0 +1,3 @@
+dn: cn=schema
+objectClasses: (2.16.840.1.113730.3.8.12.34 NAME 'ipaVaultContainer' SUP 
nsContainer STRUCTURAL MAY ( description ) X-ORIGIN 'IPA v4.1' )
+objectClasses: (2.16.840.1.113730.3.8.12.35 NAME 'ipaVault' SUP nsContainer 
STRUCTURAL MAY ( description ) X-ORIGIN 'IPA v4.1' )
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index 
7d8ceb60e6374e133cfb6e3684bc307dbf313ce7..95bd6a1d246679822fc57156c58efd1182ee5a13
 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -14,6 +14,7 @@ app_DATA =                            \
        60ipaconfig.ldif                \
        60basev2.ldif                   \
        60basev3.ldif                   \
+       60basev4.ldif                   \
        60ipadns.ldif                   \
        61kerberos-ipav3.ldif           \
        65ipacertstore.ldif             \
diff --git a/install/share/copy-schema-to-ca.py 
b/install/share/copy-schema-to-ca.py
index 
fc53fe4cb52486cc618bec77aabe8283ad5eadbc..fb938d212f0f4ddd9a8250a362b89c29d3078efd
 100755
--- a/install/share/copy-schema-to-ca.py
+++ b/install/share/copy-schema-to-ca.py
@@ -29,6 +29,7 @@ SCHEMA_FILENAMES = (
     "60ipaconfig.ldif",
     "60basev2.ldif",
     "60basev3.ldif",
+    "60basev4.ldif",
     "60ipadns.ldif",
     "61kerberos-ipav3.ldif",
     "65ipacertstore.ldif",
diff --git a/install/updates/40-vault.update b/install/updates/40-vault.update
new file mode 100644
index 
0000000000000000000000000000000000000000..59e5b629ce4e6c5acac06df78f02106afa6c859e
--- /dev/null
+++ b/install/updates/40-vault.update
@@ -0,0 +1,27 @@
+dn: cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: objectClass: ipaVaultContainer
+default: cn: vaults
+default: description: Root vault container
+
+dn: cn=services,cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: objectClass: ipaVaultContainer
+default: cn: services
+default: description: Services vault container
+
+dn: cn=shared,cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: objectClass: ipaVaultContainer
+default: cn: shared
+default: description: Shared vault container
+
+dn: cn=users,cn=vaults,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: objectClass: ipaVaultContainer
+default: cn: users
+default: description: Users vault container
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 
2b3157d2e656e214a0706d3ab1c780c651b0df91..5c7214343443504f9527039460ef90b80a52603d
 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -31,6 +31,7 @@ app_DATA =                            \
        40-dns.update                   \
        40-automember.update            \
        40-otp.update                   \
+       40-vault.update                 \
        45-roles.update                 \
        50-7_bit_check.update           \
        50-dogtag10-migration.update    \
diff --git a/ipa-client/man/default.conf.5 b/ipa-client/man/default.conf.5
index 
dbc8a5b4647439de4de7c01152d098eb0561e236..0973f1a07179ad64daa326a02803cdc9ba1870aa
 100644
--- a/ipa-client/man/default.conf.5
+++ b/ipa-client/man/default.conf.5
@@ -221,6 +221,7 @@ The following define the containers for the IPA server. 
Containers define where
   container_sudocmdgroup: cn=sudocmdgroups,cn=sudo
   container_sudorule: cn=sudorules,cn=sudo
   container_user: cn=users,cn=accounts
+  container_vault: cn=vaults
   container_virtual: cn=virtual operations,cn=etc
 
 .SH "FILES"
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 
325414b64fdacd4d8df261588cfc9b7481923be1..f64e02b5cf2a949a3c0ea7c1702132a3a09c1c19
 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -97,6 +97,7 @@ DEFAULT_CONFIG = (
     ('container_hbacservice', DN(('cn', 'hbacservices'), ('cn', 'hbac'))),
     ('container_hbacservicegroup', DN(('cn', 'hbacservicegroups'), ('cn', 
'hbac'))),
     ('container_dns', DN(('cn', 'dns'))),
+    ('container_vault', DN(('cn', 'vaults'))),
     ('container_virtual', DN(('cn', 'virtual operations'), ('cn', 'etc'))),
     ('container_sudorule', DN(('cn', 'sudorules'), ('cn', 'sudo'))),
     ('container_sudocmd', DN(('cn', 'sudocmds'), ('cn', 'sudo'))),
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 
f95b4fd4a9bc3f478f6fd523bf242002a5b6649f..97cd5916f5c63509587879bbebfce7c8644a0c25
 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -22,6 +22,7 @@ from time import gmtime, strftime
 import string
 import posixpath
 import os
+import traceback
 
 from ipalib import api, errors
 from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime
@@ -878,6 +879,14 @@ class user_del(LDAPDelete):
             else:
                 self.api.Command.otptoken_del(token)
 
+        # Delete user's private vault container.
+        try:
+            vaultcontainer_id = 
self.api.Object.vaultcontainer.get_private_id(owner)
+            (vaultcontainer_parent_id, vaultcontainer_name) = 
self.api.Object.vaultcontainer.split_id(vaultcontainer_id)
+            self.api.Command.vaultcontainer_del(vaultcontainer_name, 
parent=vaultcontainer_parent_id)
+        except errors.NotFound:
+            pass
+
         return dn
 
 
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
new file mode 100644
index 
0000000000000000000000000000000000000000..eb5847cef8de3d5c21723e307afcceda4aaf0f82
--- /dev/null
+++ b/ipalib/plugins/vault.py
@@ -0,0 +1,726 @@
+# Authors:
+#   Endi S. Dewata <edew...@redhat.com>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# 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 json
+import os
+import random
+import shutil
+import string
+import tempfile
+
+import pki
+import pki.account
+import pki.crypto
+import pki.key
+
+from ipalib import api, errors
+from ipalib import Str, Bytes
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import *
+from ipalib.plugins import baseldap
+from ipalib.request import context
+from ipalib.plugins.user import split_principal
+from ipalib import _, ngettext
+from ipaplatform.paths import paths
+import ipapython.nsslib
+
+__doc__ = _("""
+Vaults
+
+Manage vaults and vault containers.
+
+EXAMPLES:
+
+ List private vaults:
+   ipa vault-find
+
+ List shared vaults:
+   ipa vault-find --parent /shared
+
+ Add a vault:
+   ipa vault-add MyVault
+
+ Show a vault:
+   ipa vault-show MyVault
+
+ Archive a secret:
+   ipa vault-archive MyVault --in secret.in
+
+ Retrieve a secret:
+   ipa vault-retrieve MyVault --out secret.out
+
+ Delete a vault:
+   ipa vault-del MyVault
+
+ List private vault containers:
+   ipa vaultcontainer-find
+
+ List top-level vault containers:
+   ipa vaultcontainer-find --parent /
+
+ Add a vault container:
+   ipa vaultcontainer-add MyContainer
+
+ Show a vault container:
+   ipa vaultcontainer-show MyContainer
+
+ Delete a vault container:
+   ipa vaultcontainer-del MyContainer
+""")
+
+register = Registry()
+transport_cert_nickname = "KRA Transport Certificate"
+
+@register()
+class vaultcontainer(LDAPObject):
+    """
+    Vault container object.
+    """
+    container_dn = api.env.container_vault
+    object_name = _('vault container')
+    object_name_plural = _('vault containers')
+
+    object_class = ['ipaVaultContainer']
+    default_attributes = [
+        'cn', 'description',
+    ]
+    search_display_attributes = [
+        'cn', 'description',
+    ]
+
+    label = _('Vault Containers')
+    label_singular = _('Vault Container')
+
+    takes_params = (
+        Str('cn',
+            pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
+            pattern_errmsg='may only include letters, numbers, _, -, . and $',
+            maxlength=255,
+            cli_name='container_name',
+            label=_('Container name'),
+            primary_key=True,
+        ),
+        Str('description?',
+            cli_name='desc',
+            label=_('Description'),
+            doc=_('Container description'),
+        ),
+        Str('parent?',
+            cli_name='parent',
+            label=_('Parent'),
+            doc=_('Parent container'),
+            flags=('virtual_attribute'),
+        ),
+    )
+
+    def get_private_id(self, username=None):
+        """
+        Returns user's private container ID (i.e. /users/<username>/).
+        """
+
+        if not username:
+            principal = getattr(context, 'principal')
+            (username, realm) = split_principal(principal)
+
+        return u'/users/' + username + u'/'
+
+    def normalize_id(self, id):
+        """
+        Normalizes container ID.
+        """
+
+        # if ID is empty, return user's private container ID
+        if not id:
+            return self.get_private_id()
+
+        # make sure ID ends with slash
+        if not id.endswith(u'/'):
+            id += u'/'
+
+        # if it's an absolute ID, do nothing
+        if id.startswith(u'/'):
+            return id
+
+        # otherwise, prepend with user's private container ID
+        return self.get_private_id() + id
+
+    def split_id(self, id):
+        """
+        Split a normalized container ID into (parent ID, container name) tuple.
+        """
+
+        # handle root ID
+        if id == u'/':
+            return (None, None)
+
+        # split ID into parent ID, container name, and empty string
+        parts = id.rsplit(u'/', 2)
+
+        # return parent ID and container name
+        return (parts[-3] + u'/', parts[-2])
+
+    def get_dn(self, *keys, **kwargs):
+        """
+        Generate vault container DN.
+        """
+
+        id = keys[0]
+        dn = DN(self.container_dn, api.env.basedn)
+
+        # if ID is not specified, return base DN
+        if not id:
+            return dn
+
+        # for each name in the ID, prepend the base DN
+        for name in id.split(u'/'):
+            if name:
+                dn = DN(('cn', name), dn)
+
+        return dn
+
+
+@register()
+class vaultcontainer_add(LDAPCreate):
+    __doc__ = _('Create a new vault container.')
+
+    msg_summary = _('Added vault container "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, 
**options):
+
+        name = entry_attrs.get('cn')
+        parent_id = self.obj.normalize_id(options.get('parent'))
+
+        # add parent container if it doesn't exist
+        try:
+            (grandparent_id, parent_name) = self.obj.split_id(parent_id)
+            if parent_name:
+                api.Command.vaultcontainer_add(parent_name, 
parent=grandparent_id)
+        except errors.DuplicateEntry:
+            pass
+
+        id = parent_id + name
+        dn = self.obj.get_dn(id)
+
+        return dn
+
+
+@register()
+class vaultcontainer_del(LDAPDelete):
+    __doc__ = _('Delete a vault container.')
+
+    msg_summary = _('Deleted vault container "%(value)s"')
+
+    takes_options = LDAPDelete.takes_options + (
+        Str('parent?',
+            cli_name='parent',
+            doc=_('Parent container'),
+        ),
+    )
+
+    def pre_callback(self, ldap, dn, *keys, **options):
+
+        name = keys[0]
+        parent_id = self.obj.normalize_id(options.get('parent'))
+        id = parent_id + name
+        dn = self.obj.get_dn(id)
+        return dn
+
+
+@register()
+class vaultcontainer_find(LDAPSearch):
+    __doc__ = _('Search for vault containers.')
+
+    msg_summary = ngettext(
+        '%(count)d vault container matched', '%(count)d vault containers 
matched', 0
+    )
+
+    def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, 
**options):
+
+        parent_id = self.obj.normalize_id(options.get('parent'))
+        base_dn = self.obj.get_dn(parent_id)
+        return (filter, base_dn, scope)
+
+
+@register()
+class vaultcontainer_mod(LDAPUpdate):
+    __doc__ = _('Modify a vault container.')
+
+    msg_summary = _('Modified vault container "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        name = keys[1]
+        parent_id = self.obj.normalize_id(options.get('parent'))
+        id = parent_id + name
+        dn = self.obj.get_dn(id)
+        return dn
+
+
+@register()
+class vaultcontainer_show(LDAPRetrieve):
+    __doc__ = _('Display information about a vault container.')
+
+    takes_options = LDAPDelete.takes_options + (
+        Str('parent?',
+            cli_name='parent',
+            doc=_('Parent container'),
+        ),
+    )
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        name = keys[0]
+        parent_id = self.obj.normalize_id(options.get('parent'))
+        id = parent_id + name
+        dn = self.obj.get_dn(id)
+        return dn
+
+
+@register()
+class vault(LDAPObject):
+    """
+    Vault object.
+    """
+    container_dn = api.env.container_vault
+    object_name = _('vault')
+    object_name_plural = _('vaults')
+
+    object_class = ['ipaVault']
+    default_attributes = [
+        'cn', 'description',
+    ]
+    search_display_attributes = [
+        'cn', 'description',
+    ]
+
+    label = _('Vaults')
+    label_singular = _('Vault')
+
+    takes_params = (
+        Str('cn',
+            pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
+            pattern_errmsg='may only include letters, numbers, _, -, . and $',
+            maxlength=255,
+            cli_name='vault_name',
+            label=_('Vault name'),
+            primary_key=True,
+        ),
+        Str('description?',
+            cli_name='desc',
+            label=_('Description'),
+            doc=_('Vault description'),
+        ),
+        Bytes('secret?',
+            cli_name='secret',
+            label=_('Secret'),
+            doc=_('Secret'),
+        ),
+        Str('parent?',
+            cli_name='parent',
+            label=_('Parent'),
+            doc=_('Parent container'),
+            flags=('virtual_attribute'),
+        ),
+    )
+
+    def get_dn(self, *keys, **kwargs):
+        """
+        Generate vault DN.
+        """
+
+        name = keys[0]
+        parent_id = 
api.Object.vaultcontainer.normalize_id(kwargs.get('parent'))
+        parent_dn = api.Object.vaultcontainer.get_dn(parent_id)
+        dn = DN(('cn', name), parent_dn)
+
+        return dn
+
+
+@register()
+class vault_add(LDAPCreate):
+    __doc__ = _('Create a new vault.')
+
+    takes_options = LDAPRetrieve.takes_options + (
+        Str('in?',
+            cli_name='in',
+            doc=_('Input file containing the secret'),
+        ),
+    )
+
+    msg_summary = _('Added vault "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, 
**options):
+
+        name = entry_attrs.get('cn')
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        dn = self.obj.get_dn(name, parent=parent_id)
+
+        # add parent container if it doesn't exist
+        try:
+            (grandparent_id, parent_name) = 
api.Object.vaultcontainer.split_id(parent_id)
+            if parent_name:
+                api.Command.vaultcontainer_add(parent_name, 
parent=grandparent_id)
+        except errors.DuplicateEntry:
+            pass
+
+        return dn
+
+    def forward(self, *args, **options):
+
+        vault_id = args[0]
+
+        secret = options.get('secret')
+        file = options.get('in')
+
+        # don't send these parameters to server
+        if 'secret' in options:
+            del options['secret']
+        if 'in' in options:
+            del options['in']
+
+        if secret:
+            pass
+
+        elif file:
+            with open(file) as f:
+                secret = f.read()
+
+        # create the vault
+        response = super(vault_add, self).forward(*args, **options)
+
+        # archive an empty secret
+        api.Command.vault_archive(vault_id, secret=secret)
+
+        return response
+
+
+@register()
+class vault_del(LDAPDelete):
+    __doc__ = _('Delete a vault.')
+
+    msg_summary = _('Deleted vault "%(value)s"')
+
+    takes_options = LDAPDelete.takes_options + (
+        Str('parent?',
+            cli_name='parent',
+            doc=_('Parent container'),
+        ),
+    )
+
+    def pre_callback(self, ldap, dn, *keys, **options):
+
+        vault_id = keys[0]
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        dn = self.obj.get_dn(vault_id, parent=parent_id)
+
+
+        kra_client = api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = 'ipa-' + vault_id
+
+        try:
+            key_info = kra_client.keys.get_active_key_info(client_key_id)
+
+            kra_client.keys.modify_key_status(
+                key_info.get_key_id(),
+                pki.key.KeyClient.KEY_STATUS_INACTIVE)
+
+        except pki.ResourceNotFoundException, e:
+            pass
+
+        return dn
+
+
+@register()
+class vault_find(LDAPSearch):
+    __doc__ = _('Search for vaults.')
+
+    msg_summary = ngettext(
+        '%(count)d vault matched', '%(count)d vaults matched', 0
+    )
+
+    def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, 
**options):
+
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        base_dn = api.Object.vaultcontainer.get_dn(parent_id)
+        return (filter, base_dn, scope)
+
+
+@register()
+class vault_mod(LDAPUpdate):
+    __doc__ = _('Modify a vault.')
+
+    msg_summary = _('Modified vault "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        name = keys[1]
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        dn = self.obj.get_dn(name, parent=parent_id)
+        return dn
+
+
+@register()
+class vault_show(LDAPRetrieve):
+    __doc__ = _('Display information about a vault.')
+
+    takes_options = LDAPRetrieve.takes_options + (
+        Str('parent?',
+            cli_name='parent',
+            doc=_('Parent container'),
+        ),
+    )
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        name = keys[0]
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        dn = self.obj.get_dn(name, parent=parent_id)
+        return dn
+
+
+@register()
+class vault_archive(LDAPRetrieve):
+    __doc__ = _('Archive a secret into a vault.')
+
+    takes_options = LDAPRetrieve.takes_options + (
+        Str('parent?',
+            cli_name='parent',
+            doc=_('Parent container'),
+        ),
+        Bytes('secret?',
+            cli_name='secret',
+            doc=_('Secret to archive'),
+        ),
+        Str('in?',
+            cli_name='in',
+            doc=_('Input file containing the secret'),
+        ),
+        Bytes('wrapped_session_key?',
+            cli_name='wrapped_session_key',
+            doc=_('Session key wrapped with transport certificate and encoded 
in base-64'),
+        ),
+        Bytes('encrypted_data?',
+            cli_name='encrypted_data',
+            doc=_('Data encrypted with session key and encoded in base-64'),
+        ),
+        Bytes('nonce?',
+            cli_name='nonce',
+            doc=_('Nonce encrypted encoded in base-64'),
+        ),
+    )
+
+    msg_summary = _('Archived secret into vault "%(value)s"')
+
+    def execute(self, *args, **options):
+
+        vault_id = args[0]
+
+        kra_client = api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = 'ipa-' + vault_id
+
+        try:
+            key_info = kra_client.keys.get_active_key_info(client_key_id)
+
+            kra_client.keys.modify_key_status(
+                key_info.get_key_id(),
+                pki.key.KeyClient.KEY_STATUS_INACTIVE)
+
+        except pki.ResourceNotFoundException, e:
+            pass
+
+        wrapped_session_key = base64.b64decode(options['wrapped_session_key'])
+        encrypted_data = base64.b64decode(options['encrypted_data'])
+        nonce = base64.b64decode(options['nonce'])
+
+        kra_client.keys.archive_encrypted_data(
+            client_key_id,
+            pki.key.KeyClient.PASS_PHRASE_TYPE,
+            encrypted_data,
+            wrapped_session_key,
+            "{1 2 840 113549 3 7}",
+            base64.encodestring(nonce),
+        )
+
+        kra_account.logout()
+
+        return super(vault_archive, self).execute(*args, **options)
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        name = keys[0]
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        dn = self.obj.get_dn(name, parent=parent_id)
+        return dn
+
+    def forward(self, *args, **options):
+
+        vault_id = args[0]
+
+        secret = options.get('secret')
+        file = options.get('in')
+
+        # don't send these parameters to server
+        if 'secret' in options:
+            del options['secret']
+        if 'in' in options:
+            del options['in']
+
+        if secret:
+            pass
+
+        elif file:
+            with open(file) as f:
+                secret = f.read()
+
+        else:
+            secret = ''
+
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+        ipapython.nsslib.current_dbdir = paths.IPA_NSSDB_DIR
+
+        nonce = crypto.generate_nonce_iv()
+        session_key = crypto.generate_session_key()
+        nss_transport_cert = crypto.get_cert(transport_cert_nickname)
+
+        wrapped_session_key = crypto.asymmetric_wrap(
+            session_key,
+            nss_transport_cert
+        )
+
+        encrypted_data = crypto.symmetric_wrap(
+            secret,
+            session_key,
+            nonce_iv=nonce
+        )
+
+        options['wrapped_session_key'] = base64.b64encode(wrapped_session_key)
+        options['encrypted_data'] = base64.b64encode(encrypted_data)
+        options['nonce'] = base64.b64encode(nonce)
+
+        return super(vault_archive, self).forward(*args, **options)
+
+
+@register()
+class vault_retrieve(LDAPRetrieve):
+    __doc__ = _('Retrieve a secret from a vault.')
+
+    takes_options = LDAPRetrieve.takes_options + (
+        Str('parent?',
+            cli_name='parent',
+            doc=_('Parent container'),
+        ),
+        Str('out?',
+            cli_name='out',
+            doc=_('Output file to store the secret'),
+        ),
+        Bytes('wrapped_session_key?',
+            cli_name='wrapped_session_key',
+            doc=_('Session key wrapped with transport certificate and encoded 
in base-64'),
+        ),
+    )
+
+    msg_summary = _('Retrieved secret from vault "%(value)s"')
+
+    def execute(self, *args, **options):
+
+        vault_id = args[0]
+
+        wrapped_session_key = base64.b64decode(options['wrapped_session_key'])
+
+        response = super(vault_retrieve, self).execute(*args, **options)
+
+        client_key_id = 'ipa-' + vault_id
+
+        kra_client = api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        key_client = kra_client.keys
+        key_info = key_client.get_active_key_info(client_key_id)
+
+        key = key_client.retrieve_key(
+            key_info.get_key_id(),
+            wrapped_session_key)
+
+        response['result']['encrypted_data'] = 
base64.b64encode(key.encrypted_data)
+        response['result']['nonce'] = base64.b64encode(key.nonce_data)
+
+        kra_account.logout()
+
+        return response
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        name = keys[0]
+        parent_id = 
api.Object.vaultcontainer.normalize_id(options.get('parent'))
+        dn = self.obj.get_dn(name, parent=parent_id)
+        return dn
+
+    def forward(self, *args, **options):
+
+        vault_id = args[0]
+
+        file = options.get('out')
+
+        # don't send these parameters to server
+        if 'out' in options:
+            del options['out']
+
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+        ipapython.nsslib.current_dbdir = paths.IPA_NSSDB_DIR
+
+        session_key = crypto.generate_session_key()
+        nss_transport_cert = crypto.get_cert(transport_cert_nickname)
+
+        wrapped_session_key = crypto.asymmetric_wrap(
+            session_key,
+            nss_transport_cert
+        )
+
+        options['wrapped_session_key'] = base64.b64encode(wrapped_session_key)
+
+        response = super(vault_retrieve, self).forward(*args, **options)
+
+        encrypted_data = base64.b64decode(response['result']['encrypted_data'])
+        nonce = base64.b64decode(response['result']['nonce'])
+
+        secret = crypto.symmetric_unwrap(
+            encrypted_data,
+            session_key,
+            nonce_iv=nonce)
+
+        if file:
+            with open(file, 'w') as f:
+                f.write(secret)
+
+        else:
+            response['result']['secret'] = unicode(secret)
+
+        return response
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 
5934f0c26e4b7c0a44adbab978c1f9b319d72e9f..4e44a0cf9597bdb46619665798d38a30265947f6
 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -63,6 +63,7 @@ from ipaplatform.paths import paths
 from ipapython.cookie import Cookie
 from ipapython.dnsutil import DNSName
 from ipalib.text import _
+import ipapython.nsslib
 from ipapython.nsslib import NSSHTTPS, NSSConnection
 from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, 
KRB5KRB_AP_ERR_TKT_EXPIRED, \
                              KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, 
KRB5_REALM_CANT_RESOLVE
@@ -450,14 +451,10 @@ class LanguageAwareTransport(MultiProtocolTransport):
 class SSLTransport(LanguageAwareTransport):
     """Handles an HTTPS transaction to an XML-RPC server."""
 
-    def __nss_initialized(self, dbdir):
+    def get_connection_dbdir(self):
         """
-        If there is another connections open it may have already
-        initialized NSS. This is likely to lead to an NSS shutdown
-        failure.  One way to mitigate this is to tell NSS to not
-        initialize if it has already been done in another open connection.
-
-        Returns True if another connection is using the same db.
+        If there is a connections open it may have already initialized
+        NSS database. Return the database location used by the connection.
         """
         for value in context.__dict__.values():
             if not isinstance(value, Connection):
@@ -466,25 +463,32 @@ class SSLTransport(LanguageAwareTransport):
                     getattr(value.conn, '_ServerProxy__transport', None),
                     SSLTransport):
                 continue
-            if hasattr(value.conn._ServerProxy__transport, 'dbdir') and \
-              value.conn._ServerProxy__transport.dbdir == dbdir:
-                return True
-        return False
+            if hasattr(value.conn._ServerProxy__transport, 'dbdir'):
+                return value.conn._ServerProxy__transport.dbdir
+        return None
 
     def make_connection(self, host):
         host, self._extra_headers, x509 = self.get_host_info(host)
         # Python 2.7 changed the internal class used in xmlrpclib from
         # HTTP to HTTPConnection. We need to use the proper subclass
 
-        # If we an existing connection exists using the same NSS database
-        # there is no need to re-initialize. Pass thsi into the NSS
-        # connection creator.
         if sys.version_info >= (2, 7):
             if self._connection and host == self._connection[0]:
                 return self._connection[1]
 
         dbdir = getattr(context, 'nss_dir', paths.IPA_NSSDB_DIR)
-        no_init = self.__nss_initialized(dbdir)
+        connection_dbdir = self.get_connection_dbdir()
+
+        if connection_dbdir:
+            # If an existing connection exists using the same NSS database
+            # there is no need to re-initialize.
+            # connection creator.
+            no_init = dbdir == connection_dbdir
+        else:
+            # If the NSS database is already used, there is no need to
+            # re-initialize.
+            no_init = dbdir == ipapython.nsslib.current_dbdir
+
         if sys.version_info < (2, 7):
             conn = NSSHTTPS(host, 443, dbdir=dbdir, no_init=no_init)
         else:
diff --git a/ipapython/nsslib.py b/ipapython/nsslib.py
index 
93b0c56fcff4fc69841a6823aae8f694c1f76ff0..06a192bc1449bfc2208da417667e5c26d1323407
 100644
--- a/ipapython/nsslib.py
+++ b/ipapython/nsslib.py
@@ -23,6 +23,7 @@ import httplib
 import getpass
 import socket
 from ipapython.ipa_log_manager import *
+from ipalib.request import context, Connection
 
 from nss.error import NSPRError
 import nss.io as io
@@ -31,6 +32,8 @@ import nss.ssl as ssl
 import nss.error as error
 from ipaplatform.paths import paths
 
+current_dbdir = None
+
 def auth_certificate_callback(sock, check_sig, is_server, certdb):
     cert_is_valid = False
 
@@ -184,19 +187,27 @@ class NSSConnection(httplib.HTTPConnection, 
NSSAddressFamilyFallback):
         httplib.HTTPConnection.__init__(self, host, port, strict)
         NSSAddressFamilyFallback.__init__(self, family)
 
+        root_logger.debug('%s init %s', self.__class__.__name__, host)
+
         if not dbdir:
             raise RuntimeError("dbdir is required")
 
-        root_logger.debug('%s init %s', self.__class__.__name__, host)
-        if not no_init and nss.nss_is_initialized():
-            # close any open NSS database and use the new one
-            ssl.clear_session_cache()
-            try:
-                nss.nss_shutdown()
-            except NSPRError, e:
-                if e.errno != error.SEC_ERROR_NOT_INITIALIZED:
-                    raise e
-        nss.nss_init(dbdir)
+        global current_dbdir
+
+        # If initialization is requested, initialize the new database.
+        if not no_init:
+
+            if nss.nss_is_initialized():
+                ssl.clear_session_cache()
+                try:
+                    nss.nss_shutdown()
+                except NSPRError, e:
+                    if e.errno != error.SEC_ERROR_NOT_INITIALIZED:
+                        raise e
+
+            nss.nss_init(dbdir)
+            current_dbdir = dbdir
+
         ssl.set_domestic_policy()
         nss.set_password_callback(self.password_callback)
 
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 
c9a1c15e9495108382f6e2e8a58f6cc4e8f79c98..f307f96e44ef78c85a5691aa7fdf780ea04dd6b7
 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -56,6 +56,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif",
                     "60ipaconfig.ldif",
                     "60basev2.ldif",
                     "60basev3.ldif",
+                    "60basev4.ldif",
                     "60ipadns.ldif",
                     "61kerberos-ipav3.ldif",
                     "65ipacertstore.ldif",
-- 
1.9.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to