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 rearchiving it with the new attributes.

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

Reviewed-By: Martin Basti <mbasti@redhat.com>
This commit is contained in:
Endi S. Dewata
2015-07-31 07:53:15 +02:00
committed by Petr Vobornik
parent b4daa45baa
commit e46d9236d1
4 changed files with 497 additions and 14 deletions

27
API.txt
View File

@@ -5474,11 +5474,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')
@@ -5538,6 +5539,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('ipavaultpublickey?', cli_name='public_key')
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: Str('public_key_file?', cli_name='public_key_file')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('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')

View File

@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=152
# Last change: mbasti - add 'user-stage' command
IPA_API_VERSION_MINOR=153
# Last change: edewata - Added support for changing vault encryption.

View File

@@ -116,10 +116,36 @@ EXAMPLES:
ipa vault-show <name>
[--user <user>|--service <service>|--shared]
""") + _("""
Modify a vault:
Modify vault description:
ipa vault-mod <name>
[--user <user>|--service <service>|--shared]
--desc <description>
""") + _("""
Modify vault type:
ipa vault-mod <name>
[--user <user>|--service <service>|--shared]
--type <type>
[old password/private key]
[new password/public key]
""") + _("""
Modify symmetric vault password:
ipa vault-mod <name>
[--user <user>|--service <service>|--shared]
--change-password
ipa vault-mod <name>
[--user <user>|--service <service>|--shared]
--old-password <old password>
--new-password <new password>
ipa vault-mod <name>
[--user <user>|--service <service>|--shared]
--old-password-file <old password file>
--new-password-file <new password file>
""") + _("""
Modify asymmetric vault keys:
ipa vault-mod <name>
[--user <user>|--service <service>|--shared]
--private-key-file <old private key file>
--public-key-file <new public key file>
""") + _("""
Delete a vault:
ipa vault-del <name>
@@ -457,7 +483,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.
"""
@@ -871,9 +897,182 @@ 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(
'ipavaultpublickey?',
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('ipavaultpublickey', 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"')
@@ -994,6 +1193,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
@@ -1008,6 +1211,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']
@@ -1062,15 +1267,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]

View File

@@ -37,6 +37,7 @@ asymmetric_vault_name = u'asymmetric_test_vault'
secret = ''.join(map(chr, xrange(0, 256)))
password = u'password'
other_password = u'other_password'
public_key = """
-----BEGIN PUBLIC KEY-----
@@ -80,6 +81,48 @@ kUlCMj24a8XsShzYTWBIyW2ngvGe3pQ9PfjkUdm0LGZjYITCBvgOKw==
-----END RSA PRIVATE KEY-----
"""
other_public_key = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7E/QLVyKjrgDctZ50U7
rmtL7Ks1QLoccp9WvZJ6WI1rYd0fX5FySS4dI6QTNZc6qww8NeNuZtkoxT9m1wkk
Rl/3wK7fWNLenH/+VHOaTQc20exg7ztfsO7JIsmKmigtticdR5C4jLfjcOp+WjLH
w3zrmrO5SIZ8njxMoDcQJa2vu/t281U/I7ti8ue09FSitIECU05vgmPS+MnXR8HK
PxXqrNkjl29mXNbPiByWwlse3Prwved9I7fwgpiHJqUBFudD/0tZ4DWyLG7t9wM1
O8gRaRg1r+ENVpmMSvXo4+8+bR3rEYddD5zU7nKXafeuthXlXplae/8uZmCiSI63
TwIDAQAB
-----END PUBLIC KEY-----
"""
other_private_key = """
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAv7E/QLVyKjrgDctZ50U7rmtL7Ks1QLoccp9WvZJ6WI1rYd0f
X5FySS4dI6QTNZc6qww8NeNuZtkoxT9m1wkkRl/3wK7fWNLenH/+VHOaTQc20exg
7ztfsO7JIsmKmigtticdR5C4jLfjcOp+WjLHw3zrmrO5SIZ8njxMoDcQJa2vu/t2
81U/I7ti8ue09FSitIECU05vgmPS+MnXR8HKPxXqrNkjl29mXNbPiByWwlse3Prw
ved9I7fwgpiHJqUBFudD/0tZ4DWyLG7t9wM1O8gRaRg1r+ENVpmMSvXo4+8+bR3r
EYddD5zU7nKXafeuthXlXplae/8uZmCiSI63TwIDAQABAoIBAQCA+0GFR9F+isjx
Xy+qBpKmxLl8kKKvX8r+cSpLOkEqTlW/rqqKgnI0vVuL/L2UJKKsLvpghBxoBZyC
RCvtatBGrhIlS0UrHg/9m73Ek1hylfUUAQokTn4PrkwWJSgmm/xOATmZSs5ymNTn
yFCmXl69sdNR77YvD5bQXeBtOT+bKXy7yQ1TmYPwwSjL+WSlMV6ZfE3HNVmxPTpk
CTFS638cJblWk9MUIy8HIlhu6If2P4RnHr7ZGGivhREayvs0zXcAfqhIyFHruxSE
yYnmqH9paWjv5mP3YyLoKr+NUvvxnBr/9wCTt0TKgG8G6rpkHuPDLQni9wUGnew8
QdMgFEohAoGBAPH4vaVB5gDVfvIqwJBsBLHpPq72GvxjrM/exD0jIIpXZxz9gCql
CmC5b1RS1uy8PMoc/RO4CE7UTLaTesciP6LjTD1RhH3rLLJO8/iVC1RXgMrCLHLm
ZQnDhIQGGNQxpvBjQy5ZOWat2dFxYhHN630IFPOtrWsOmJ5HsL1JrjzxAoGBAMrO
R1zNwQ42VbJS6AFshZVjmUV2h3REGh4zG/9IqL0Hz493hyCTGoDPLLXIbtkqNqzQ
XibSZ9RMVPKKTiNQTx91DTgh4Anz8xUr84tA2iAf3ayNWKi3Y3GhmP2EWp1qYeom
kV8Uq0lt4dHZuEo3LuqvbtbzlF9qUXqKS5qy6Tg/AoGBAKCp02o2HjzxhS/QeTmr
r1ZeE7PiTzrECAuh01TwzPtuW1XhcEdgfEqK9cPcmT5pIkflBZkhOcr1pdYYiI5O
TEigeY/BX6KoE251hALLG9GtpCN82DyWhAH+oy9ySOwj5793eTT+I2HtD1LE4SQH
QVQsmJTP/fS2pVl7KnwUvy9RAoGBAKzo2qchNewsHzx+uxgbsnkABfnXaP2T4sDE
yqYJCPTB6BFl02vOf9Y6zN/gF8JH333P2bY3xhaXTgXMLXqmSg+D+NVW7HEP8Lyo
UGj1zgN9p74qdODEGqETKiFb6vYzcW/1mhP6x18/tDz658k+611kXZge7O288+MK
bhNjXrx5AoGBAMox25PcxVgOjCd9+LdUcIOG6LQ971eCH1NKL9YAekICnwMrStbK
veCYju6ok4ZWnMiH8MR1jgC39RWtjJZwynCuPXUP2/vZkoVf1tCZyz7dSm8TdS/2
5NdOHVy7+NQcEPSm7/FmXdpcR9ZSGAuxMBfnEUibdyz5LdJGnFUN/+HS
-----END RSA PRIVATE KEY-----
"""
class test_vault_plugin(Declarative):
@@ -590,6 +633,48 @@ class test_vault_plugin(Declarative):
},
},
{
'desc': 'Change standard vault to symmetric vault',
'command': (
'vault_mod',
[standard_vault_name],
{
'ipavaulttype': u'symmetric',
'new_password': password,
},
),
'expected': {
'value': standard_vault_name,
'summary': u'Modified vault "%s"' % standard_vault_name,
'result': {
'cn': [standard_vault_name],
'ipavaulttype': [u'symmetric'],
'ipavaultsalt': [fuzzy_string],
'owner_user': [u'admin'],
},
},
},
{
'desc': 'Retrieve secret from standard vault converted to '
'symmetric vault',
'command': (
'vault_retrieve',
[standard_vault_name],
{
'password': password,
},
),
'expected': {
'value': standard_vault_name,
'summary': 'Retrieved data from vault "%s"'
% standard_vault_name,
'result': {
'data': secret,
},
},
},
{
'desc': 'Create symmetric vault',
'command': (
@@ -652,6 +737,90 @@ class test_vault_plugin(Declarative):
},
},
{
'desc': 'Change symmetric vault password',
'command': (
'vault_mod',
[symmetric_vault_name],
{
'old_password': password,
'new_password': other_password,
},
),
'expected': {
'value': symmetric_vault_name,
'summary': u'Modified vault "%s"' % symmetric_vault_name,
'result': {
'cn': [symmetric_vault_name],
'ipavaulttype': [u'symmetric'],
'ipavaultsalt': [fuzzy_string],
'owner_user': [u'admin'],
},
},
},
{
'desc': 'Retrieve secret from symmetric vault with new password',
'command': (
'vault_retrieve',
[symmetric_vault_name],
{
'password': other_password,
},
),
'expected': {
'value': symmetric_vault_name,
'summary': 'Retrieved data from vault "%s"'
% symmetric_vault_name,
'result': {
'data': secret,
},
},
},
{
'desc': 'Change symmetric vault to asymmetric vault',
'command': (
'vault_mod',
[symmetric_vault_name],
{
'ipavaulttype': u'asymmetric',
'old_password': other_password,
'ipavaultpublickey': public_key,
},
),
'expected': {
'value': symmetric_vault_name,
'summary': u'Modified vault "%s"' % symmetric_vault_name,
'result': {
'cn': [symmetric_vault_name],
'ipavaulttype': [u'asymmetric'],
'ipavaultpublickey': [public_key],
'owner_user': [u'admin'],
},
},
},
{
'desc': 'Retrieve secret from symmetric vault converted to '
'asymmetric vault',
'command': (
'vault_retrieve',
[symmetric_vault_name],
{
'private_key': private_key,
},
),
'expected': {
'value': symmetric_vault_name,
'summary': 'Retrieved data from vault "%s"'
% symmetric_vault_name,
'result': {
'data': secret,
},
},
},
{
'desc': 'Create asymmetric vault',
'command': (
@@ -713,4 +882,84 @@ class test_vault_plugin(Declarative):
},
},
{
'desc': 'Change asymmetric vault keys',
'command': (
'vault_mod',
[asymmetric_vault_name],
{
'private_key': private_key,
'ipavaultpublickey': other_public_key,
},
),
'expected': {
'value': asymmetric_vault_name,
'summary': u'Modified vault "%s"' % asymmetric_vault_name,
'result': {
'cn': [asymmetric_vault_name],
'ipavaulttype': [u'asymmetric'],
'ipavaultpublickey': [other_public_key],
'owner_user': [u'admin'],
},
},
},
{
'desc': 'Retrieve secret from asymmetric vault with new keys',
'command': (
'vault_retrieve',
[asymmetric_vault_name],
{
'private_key': other_private_key,
},
),
'expected': {
'value': asymmetric_vault_name,
'summary': 'Retrieved data from vault "%s"'
% asymmetric_vault_name,
'result': {
'data': secret,
},
},
},
{
'desc': 'Change asymmetric vault to standard vault',
'command': (
'vault_mod',
[asymmetric_vault_name],
{
'ipavaulttype': u'standard',
'private_key': other_private_key,
},
),
'expected': {
'value': asymmetric_vault_name,
'summary': u'Modified vault "%s"' % asymmetric_vault_name,
'result': {
'cn': [asymmetric_vault_name],
'ipavaulttype': [u'standard'],
'owner_user': [u'admin'],
},
},
},
{
'desc': 'Retrieve secret from asymmetric vault converted to '
'standard vault',
'command': (
'vault_retrieve',
[asymmetric_vault_name],
{},
),
'expected': {
'value': asymmetric_vault_name,
'summary': 'Retrieved data from vault "%s"'
% asymmetric_vault_name,
'result': {
'data': secret,
},
},
},
]