On 10/16/2014 4:12 PM, Endi Sukma Dewata wrote:
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.

New patch attached. It's now using the correct OID's for the schema. It also has been rebased on top of #352-1 and #354.

--
Endi S. Dewata
>From 2284f5684149e9fdfb7cde13865fe28e265ff5a3 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edew...@redhat.com>
Date: Tue, 21 Oct 2014 10:57:08 -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 also included the initial LDAP
schema.

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.

Ticket #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            | 724 +++++++++++++++++++++++++++++++++++++
 ipaserver/install/dsinstance.py    |   1 +
 12 files changed, 931 insertions(+), 2 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 
0000491d7a76fd1d2d50208d314d1600839ce295..cfa6558fcf678e5915a90407da517f9a591a41bf
 100644
--- a/API.txt
+++ b/API.txt
@@ -4475,6 +4475,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 
b0d41e5e1ec59ddefbdcccf588b97bac2ff798ee..fe23eae5f349f4a2d40c3d3e55f6168a82b961b2
 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=108
-# Last change: pvoborni - manage authorization of keytab operations
+IPA_API_VERSION_MINOR=109
+# Last change: edewata - initial vault implementation
diff --git a/install/share/60basev4.ldif b/install/share/60basev4.ldif
new file mode 100644
index 
0000000000000000000000000000000000000000..97553d53938093c1b0ecba0826fc469d0d758c62
--- /dev/null
+++ b/install/share/60basev4.ldif
@@ -0,0 +1,3 @@
+dn: cn=schema
+objectClasses: (2.16.840.1.113730.3.8.18.1.1 NAME 'ipaVault' SUP nsContainer 
STRUCTURAL MAY ( description ) X-ORIGIN 'IPA v4.1' )
+objectClasses: (2.16.840.1.113730.3.8.18.1.2 NAME 'ipaVaultContainer' SUP 
nsContainer STRUCTURAL MAY ( description ) X-ORIGIN 'IPA v4.1' )
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index 
878d8868bbbb4f774d378b1d2e886841e2b4b7e4..ec417b634aeb80be3a39f2b8e3410e68a1131ee0
 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                   \
        60ipapk11.ldif                  \
        61kerberos-ipav3.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 
2bd877a0d89525a69ea2d09647499ab2311bb358..a127f91cbe03aa13ec90bd628eaa29b7a898c3b9
 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -32,6 +32,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 
e206289248dfe9ae79bd87271ff2c7672fb98b4f..928a996e35cf31d21e6e85e4ea31380ec9b2c2ce
 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
@@ -889,6 +890,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..c0e66621d1cf326b0bfb88565951d66cda3b9500
--- /dev/null
+++ b/ipalib/plugins/vault.py
@@ -0,0 +1,724 @@
+# 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 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 post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        vault_id = entry_attrs.get('cn')[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 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 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 post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+        vault_id = entry_attrs.get('cn')[0]
+
+        wrapped_session_key = base64.b64decode(options['wrapped_session_key'])
+
+        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)
+
+        entry_attrs['encrypted_data'] = base64.b64encode(key.encrypted_data)
+        entry_attrs['nonce'] = base64.b64encode(key.nonce_data)
+
+        kra_account.logout()
+
+        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/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 
d1569697cba7d7fb9f44a3b85afb643a42624f20..9fa736dcf635b286035b4438a6c342e64f09d1d6
 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",
                     "60ipapk11.ldif",
                     "60ipadns.ldif",
                     "61kerberos-ipav3.ldif",
-- 
1.9.0

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

Reply via email to