The vault-mod command has been modified to support changing vault
encryption attributes (i.e. type, password, public/private keys)
in addition to normal attributes (i.e. description). Changing the
encryption requires retrieving the stored secret with the old
attributes and rearchieving it with the new attributes.

https://fedorahosted.org/freeipa/ticket/5176

--
Endi S. Dewata
From e80928fa8e8a099576fcdbff08fd90a634600825 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edew...@redhat.com>
Date: Fri, 31 Jul 2015 07:53:15 +0200
Subject: [PATCH] Added support for changing vault encryption.

The vault-mod command has been modified to support changing vault
encryption attributes (i.e. type, password, public/private keys)
in addition to normal attributes (i.e. description). Changing the
encryption requires retrieving the stored secret with the old
attributes and rearchieving it with the new attributes.

https://fedorahosted.org/freeipa/ticket/5176
---
 API.txt                 |  27 +++++-
 VERSION                 |   4 +-
 ipalib/plugins/vault.py | 239 ++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 248 insertions(+), 22 deletions(-)

diff --git a/API.txt b/API.txt
index 
00b47b8709c81217d7f2a69e719093f4a04f1734..bd5c48998056ab94729cb1b475bf444707a883cb
 100644
--- a/API.txt
+++ b/API.txt
@@ -5466,11 +5466,12 @@ output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an 
LDAP entry', domain='ipa', localedir=None))
 command: vault_archive
-args: 1,10,3
+args: 1,11,3
 arg: Str('cn', attribute=True, cli_name='name', maxlength=255, 
multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, 
required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
 option: Bytes('data?')
 option: Str('in?')
+option: Flag('override_password?', autofill=True, default=False)
 option: Str('password?', cli_name='password')
 option: Str('password_file?', cli_name='password_file')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
@@ -5528,6 +5529,30 @@ output: ListOfEntries('result', (<type 'list'>, <type 
'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: vault_mod
+args: 1,18,3
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, 
multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, 
required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
+option: Flag('change_password?', autofill=True, default=False)
+option: Str('description?', cli_name='desc')
+option: Bytes('ipavaultsalt?', cli_name='salt')
+option: Str('ipavaulttype?', cli_name='type')
+option: Str('new_password?', cli_name='new_password')
+option: Str('new_password_file?', cli_name='new_password_file')
+option: Str('old_password?', cli_name='old_password')
+option: Str('old_password_file?', cli_name='old_password_file')
+option: Bytes('private_key?', cli_name='private_key')
+option: Str('private_key_file?', cli_name='private_key_file')
+option: Bytes('public_key?', cli_name='public_key')
+option: Str('public_key_file?', cli_name='public_key_file')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
+option: Str('servicename?', cli_name='service')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('username?', cli_name='user')
+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_mod_internal
 args: 1,15,3
 arg: Str('cn', attribute=True, cli_name='name', maxlength=255, 
multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, 
required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
diff --git a/VERSION b/VERSION
index 
c42bea06522dae55e1a89ff94ae394594086b467..feb9f4db92c7c7b95e9e5d5907b1f97e96b26886
 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=149
-# Last change: edewata - Added CLI param and ACL for vault service operations
+IPA_API_VERSION_MINOR=150
+# Last change: edewata - Added support for changing vault encryption.
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index 
3b62822366a62c90f843a6293589c28383e782ef..5bee6aaae8ddd306d4ee0c273143c9d0ffc913d5
 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -133,19 +133,36 @@ EXAMPLES:
    ipa vault-show <name> --shared
 """) + _("""
  Show a user vault:
-   ipa vault-show <name> --user <username>
+   ipa vault-show <name>
 """) + _("""
- Modify a private vault:
-   ipa vault-mod <name> --desc <description>
+ Modify vault description:
+   ipa vault-mod <name>
+       [--user <username>|--service <service name>|--shared]
+       --desc <description>
 """) + _("""
- Modify a service vault:
-   ipa vault-mod <name> --service <service name> --desc <description>
+ Modify vault type:
+   ipa vault-mod <name>
+       [--user <username>|--service <service name>|--shared]
+       --type <type>
 """) + _("""
- Modify a shared vault:
-   ipa vault-mod <name> --shared --desc <description>
+ Modify vault password:
+   ipa vault-mod <name>
+       [--user <username>|--service <service name>|--shared]
+       --change-password
+   ipa vault-mod <name>
+       [--user <username>|--service <service name>|--shared]
+       --old-password <old password>
+       --new-password <new password>
+   ipa vault-mod <name>
+       [--user <username>|--service <service name>|--shared]
+       --old-password-file <old password file>
+       --new-password-file <new password file>
 """) + _("""
- Modify a user vault:
-   ipa vault-mod <name> --user <username> --desc <description>
+ Modify vault keys:
+   ipa vault-mod <name>
+       [--user <username>|--service <service name>|--shared]
+       --private-key-file <old private key file>
+       --public-key-file <new public key file>
 """) + _("""
  Delete a private vault:
    ipa vault-del <name>
@@ -478,7 +495,7 @@ class vault(LDAPObject):
 
             print '  ** Passwords do not match! **'
 
-    def get_existing_password(self, new=False):
+    def get_existing_password(self):
         """
         Gets existing password from user.
         """
@@ -818,9 +835,183 @@ class vault_find(LDAPSearch):
 
 
 @register()
-class vault_mod(LDAPUpdate):
+class vault_mod(PKQuery, Local):
     __doc__ = _('Modify a vault.')
 
+    takes_options = vault_options + (
+        Str(
+            'description?',
+            cli_name='desc',
+            doc=_('Vault description'),
+        ),
+        Str(
+            'ipavaulttype?',
+            cli_name='type',
+            doc=_('Vault type'),
+        ),
+        Bytes(
+            'ipavaultsalt?',
+            cli_name='salt',
+            doc=_('Vault salt'),
+        ),
+        Flag(
+            'change_password?',
+            doc=_('Change password'),
+        ),
+        Str(
+            'old_password?',
+            cli_name='old_password',
+            doc=_('Old vault password'),
+        ),
+        Str(  # TODO: use File parameter
+            'old_password_file?',
+            cli_name='old_password_file',
+            doc=_('File containing the old vault password'),
+        ),
+        Str(
+            'new_password?',
+            cli_name='new_password',
+            doc=_('New vault password'),
+        ),
+        Str(  # TODO: use File parameter
+            'new_password_file?',
+            cli_name='new_password_file',
+            doc=_('File containing the new vault password'),
+        ),
+        Bytes(
+            'private_key?',
+            cli_name='private_key',
+            doc=_('Old vault private key'),
+        ),
+        Str(  # TODO: use File parameter
+            'private_key_file?',
+            cli_name='private_key_file',
+            doc=_('File containing the old vault private key'),
+        ),
+        Bytes(
+            'public_key?',
+            cli_name='public_key',
+            doc=_('New vault public key'),
+        ),
+        Str(  # TODO: use File parameter
+            'public_key_file?',
+            cli_name='public_key_file',
+            doc=_('File containing the new vault public key'),
+        ),
+    )
+
+    has_output = output.standard_entry
+
+    def forward(self, *args, **options):
+
+        vault_type = options.pop('ipavaulttype', False)
+        salt = options.pop('ipavaultsalt', False)
+        change_password = options.pop('change_password', False)
+
+        old_password = options.pop('old_password', None)
+        old_password_file = options.pop('old_password_file', None)
+        new_password = options.pop('new_password', None)
+        new_password_file = options.pop('new_password_file', None)
+
+        old_private_key = options.pop('private_key', None)
+        old_private_key_file = options.pop('private_key_file', None)
+        new_public_key = options.pop('public_key', None)
+        new_public_key_file = options.pop('public_key_file', None)
+
+        if self.api.env.in_server:
+            backend = self.api.Backend.ldap2
+        else:
+            backend = self.api.Backend.rpcclient
+        if not backend.isconnected():
+            backend.connect(ccache=krbV.default_context().default_ccache())
+
+        # determine the vault type based on parameters specified
+        if vault_type:
+            pass
+
+        elif change_password or \
+            new_password or new_password_file or salt:
+            vault_type = u'symmetric'
+
+        elif new_public_key or new_public_key_file:
+            vault_type = u'asymmetric'
+
+        # if vault type is specified, retrieve existing secret
+        if vault_type:
+            opts = options.copy()
+            opts.pop('description', None)
+
+            opts['password'] = old_password
+            opts['password_file'] = old_password_file
+            opts['private_key'] = old_private_key
+            opts['private_key_file'] = old_private_key_file
+
+            response = self.api.Command.vault_retrieve(*args, **opts)
+            data = response['result']['data']
+
+        opts = options.copy()
+
+        # if vault type is specified, update crypto attributes
+        if vault_type:
+            opts['ipavaulttype'] = vault_type
+
+            if vault_type == u'standard':
+                opts['ipavaultsalt'] = None
+                opts['ipavaultpublickey'] = None
+
+            elif vault_type == u'symmetric':
+                if salt:
+                    opts['ipavaultsalt'] = salt
+                else:
+                    opts['ipavaultsalt'] = os.urandom(16)
+
+                opts['ipavaultpublickey'] = None
+
+            elif vault_type == u'asymmetric':
+
+                # get new vault public key
+                if new_public_key and new_public_key_file:
+                    raise errors.MutuallyExclusiveError(
+                        reason=_('New public key specified multiple times'))
+
+                elif new_public_key:
+                    pass
+
+                elif new_public_key_file:
+                    new_public_key = validated_read('public_key_file',
+                                                    new_public_key_file,
+                                                    mode='rb')
+
+                else:
+                    raise errors.ValidationError(
+                        name='ipavaultpublickey',
+                        error=_('Missing new vault public key'))
+
+                opts['ipavaultsalt'] = None
+                opts['ipavaultpublickey'] = new_public_key
+
+        response = self.api.Command.vault_mod_internal(*args, **opts)
+
+        # if vault type is specified, rearchive existing secret
+        if vault_type:
+            opts = options.copy()
+            opts.pop('description', None)
+
+            opts['data'] = data
+            opts['password'] = new_password
+            opts['password_file'] = new_password_file
+            opts['override_password'] = True
+
+            self.api.Command.vault_archive(*args, **opts)
+
+        return response
+
+
+@register()
+class vault_mod_internal(LDAPUpdate):
+
+    NO_CLI = True
+
     takes_options = LDAPUpdate.takes_options + vault_options
 
     msg_summary = _('Modified vault "%(value)s"')
@@ -933,6 +1124,10 @@ class vault_archive(PKQuery, Local):
             cli_name='password_file',
             doc=_('File containing the vault password'),
         ),
+        Flag(
+            'override_password?',
+            doc=_('Override existing password'),
+        ),
     )
 
     has_output = output.standard_entry
@@ -947,6 +1142,8 @@ class vault_archive(PKQuery, Local):
         password = options.get('password')
         password_file = options.get('password_file')
 
+        override_password = options.pop('override_password', False)
+
         # don't send these parameters to server
         if 'data' in options:
             del options['data']
@@ -1001,15 +1198,19 @@ class vault_archive(PKQuery, Local):
                 password = password.rstrip('\n')
 
             else:
-                password = self.obj.get_existing_password()
+                if override_password:
+                    password = self.obj.get_new_password()
+                else:
+                    password = self.obj.get_existing_password()
 
-            # verify password by retrieving existing data
-            opts = options.copy()
-            opts['password'] = password
-            try:
-                self.api.Command.vault_retrieve(*args, **opts)
-            except errors.NotFound:
-                pass
+            if not override_password:
+                # verify password by retrieving existing data
+                opts = options.copy()
+                opts['password'] = password
+                try:
+                    self.api.Command.vault_retrieve(*args, **opts)
+                except errors.NotFound:
+                    pass
 
             salt = vault['ipavaultsalt'][0]
 
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to