[Freeipa-devel] [PATCH] 353 Added initial vault implementation.

Endi Sukma Dewata edewata at redhat.com
Thu Oct 16 03:59:24 UTC 2014


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 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.

Ticket #3872

-- 
Endi S. Dewata
-------------- next part --------------
From 1ad4307323c9e76ed51e5cdbd736e8834864f6fc Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at 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 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.

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            | 726 +++++++++++++++++++++++++++++++++++++
 ipapython/nsslib.py                |  22 +-
 ipaserver/install/dsinstance.py    |   1 +
 13 files changed, 943 insertions(+), 14 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..210c3e5f2d25f99053c3a3fe28d9694689186254
--- /dev/null
+++ b/ipalib/plugins/vault.py
@@ -0,0 +1,726 @@
+# Authors:
+#   Endi S. Dewata <edewata at 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
+
+__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"
+
+ at 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
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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)
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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)
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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()
+
+        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)
+
+
+ at 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']
+
+        if 'file' in options:
+            del options['file']
+
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+
+        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/ipapython/nsslib.py b/ipapython/nsslib.py
index 93b0c56fcff4fc69841a6823aae8f694c1f76ff0..6acfb66057b5238921bd2a574f7bab5e47b13c72 100644
--- a/ipapython/nsslib.py
+++ b/ipapython/nsslib.py
@@ -184,19 +184,17 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
         httplib.HTTPConnection.__init__(self, host, port, strict)
         NSSAddressFamilyFallback.__init__(self, family)
 
-        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)
+
+        # If the main program already initialized a database, use the current
+        # database. Do not shutdown the current database. If the main program
+        # did not initialize a database and initialization is allowed, then
+        # initialize the specified database.
+        if not nss.nss_is_initialized() and not no_init:
+            if not dbdir:
+                raise RuntimeError("dbdir is required")
+            nss.nss_init(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



More information about the Freeipa-devel mailing list