mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Use GnuPG 2 for backup/restore
ipa-backup and ipa-restore now use GnuPG 2 for asymmetric encryption, too. The gpg2 command behaves a bit different and requires a gpg2 compatible config directory. Therefore the --keyring option has been deprecated. The backup and restore tools now use root's GPG keyring by default. Custom configuration and keyring can be used by setting GNUPGHOME environment variables. Fixes: https://pagure.io/freeipa/issue/7560 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
parent
dbc3788405
commit
8e165480ac
@ -757,7 +757,6 @@ Provides: python2-ipaplatform = %{version}-%{release}
|
|||||||
%{!?python_provide:Provides: python-ipaplatform = %{version}-%{release}}
|
%{!?python_provide:Provides: python-ipaplatform = %{version}-%{release}}
|
||||||
Requires: %{name}-common = %{version}-%{release}
|
Requires: %{name}-common = %{version}-%{release}
|
||||||
Requires: python2-gssapi >= 1.2.0-5
|
Requires: python2-gssapi >= 1.2.0-5
|
||||||
Requires: gnupg
|
|
||||||
Requires: gnupg2
|
Requires: gnupg2
|
||||||
Requires: keyutils
|
Requires: keyutils
|
||||||
Requires: python2 >= 2.7.9
|
Requires: python2 >= 2.7.9
|
||||||
@ -811,7 +810,6 @@ Provides: python3-ipaplatform = %{version}-%{release}
|
|||||||
%{?python_provide:%python_provide python3-ipaplatform}
|
%{?python_provide:%python_provide python3-ipaplatform}
|
||||||
Requires: %{name}-common = %{version}-%{release}
|
Requires: %{name}-common = %{version}-%{release}
|
||||||
Requires: python3-gssapi >= 1.2.0
|
Requires: python3-gssapi >= 1.2.0
|
||||||
Requires: gnupg
|
|
||||||
Requires: gnupg2
|
Requires: gnupg2
|
||||||
Requires: keyutils
|
Requires: keyutils
|
||||||
Requires: python3-cryptography >= 1.6
|
Requires: python3-cryptography >= 1.6
|
||||||
|
@ -43,10 +43,7 @@ A backup can not be restored in a different version of IPA.
|
|||||||
Back up data only. The default is to back up all IPA files plus data.
|
Back up data only. The default is to back up all IPA files plus data.
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-gpg\fR
|
\fB\-\-gpg\fR
|
||||||
Encrypt the back up file.
|
Encrypt the back up file. Set \fBGNUPGHOME\fR environment variable to use a custom keyring and gpg2 configuration.
|
||||||
.TP
|
|
||||||
\fB\-\-gpg\-keyring\fR=\fIGPG_KEYRING\fR
|
|
||||||
The full path to a GPG keyring. The keyring consists of two files, a public and a private key (.sec and .pub respectively). Specify the path without an extension.
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-logs\fR
|
\fB\-\-logs\fR
|
||||||
Include the IPA service log files in the backup.
|
Include the IPA service log files in the backup.
|
||||||
@ -71,6 +68,10 @@ Log to the given file
|
|||||||
1 if an error occurred
|
1 if an error occurred
|
||||||
|
|
||||||
2 if IPA is not configured
|
2 if IPA is not configured
|
||||||
|
.SH "ENVIRONMENT VARIABLES"
|
||||||
|
.PP
|
||||||
|
\fBGNUPGHOME\fR
|
||||||
|
Use custom GnuPG keyring and settings (default: \fB~/.gnupg\fR).
|
||||||
.SH "FILES"
|
.SH "FILES"
|
||||||
.PP
|
.PP
|
||||||
\fI/var/lib/ipa/backup\fR
|
\fI/var/lib/ipa/backup\fR
|
||||||
@ -83,4 +84,5 @@ The default directory for storing backup files.
|
|||||||
The log file for backups
|
The log file for backups
|
||||||
.PP
|
.PP
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
ipa\-restore(1).
|
.BR ipa\-restore(1)
|
||||||
|
.BR gpg2(1)
|
@ -32,7 +32,7 @@ The type of backup is automatically detected. A data restore can be done from ei
|
|||||||
.TP
|
.TP
|
||||||
\fBWARNING\fR: A full restore will restore files like /etc/passwd, /etc/group, /etc/resolv.conf as well. Any file that IPA may have touched is backed up and restored.
|
\fBWARNING\fR: A full restore will restore files like /etc/passwd, /etc/group, /etc/resolv.conf as well. Any file that IPA may have touched is backed up and restored.
|
||||||
.TP
|
.TP
|
||||||
An encrypted backup is also automatically detected and the root keyring is used by default. The \-\-keyring option can be used to define the full path to the private and public keys.
|
An encrypted backup is also automatically detected and the root keyring and gpg-agent is used by default. Set \fBGNUPGHOME\fR environment variable to use a custom keyring and gpg2 configuration.
|
||||||
.TP
|
.TP
|
||||||
Within the subdirectory is file, header, that describes the back up including the type, system, date of backup, the version of IPA, the version of the backup and the services on the master.
|
Within the subdirectory is file, header, that describes the back up including the type, system, date of backup, the version of IPA, the version of the backup and the services on the master.
|
||||||
.TP
|
.TP
|
||||||
@ -61,9 +61,6 @@ The Directory Manager password.
|
|||||||
\fB\-\-data\fR
|
\fB\-\-data\fR
|
||||||
Restore the data only. The default is to restore everything in the backup.
|
Restore the data only. The default is to restore everything in the backup.
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-gpg\-keyring\fR=\fIGPG_KEYRING\fR
|
|
||||||
The full path to a GPG keyring. The keyring consists of two files, a public and a private key (.sec and .pub respectively). Specify the path without an extension.
|
|
||||||
.TP
|
|
||||||
\fB\-\-no\-logs\fR
|
\fB\-\-no\-logs\fR
|
||||||
Exclude the IPA service log files in the backup (if they were backed up).
|
Exclude the IPA service log files in the backup (if they were backed up).
|
||||||
.TP
|
.TP
|
||||||
@ -91,6 +88,10 @@ Log to the given file
|
|||||||
0 if the command was successful
|
0 if the command was successful
|
||||||
|
|
||||||
1 if an error occurred
|
1 if an error occurred
|
||||||
|
.SH "ENVIRONMENT VARIABLES"
|
||||||
|
.PP
|
||||||
|
\fBGNUPGHOME\fR
|
||||||
|
Use custom GnuPG keyring and settings (default: \fB~/.gnupg\fR).
|
||||||
.SH "FILES"
|
.SH "FILES"
|
||||||
.PP
|
.PP
|
||||||
\fI/var/lib/ipa/backup\fR
|
\fI/var/lib/ipa/backup\fR
|
||||||
@ -103,4 +104,5 @@ The default directory for storing backup files.
|
|||||||
The log file for restoration
|
The log file for restoration
|
||||||
.PP
|
.PP
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
ipa\-backup(1).
|
.BR ipa\-backup(1)
|
||||||
|
.BR gpg2(1)
|
||||||
|
@ -167,8 +167,8 @@ class BasePathNamespace(object):
|
|||||||
CHROMIUM_BROWSER = "/usr/bin/chromium-browser"
|
CHROMIUM_BROWSER = "/usr/bin/chromium-browser"
|
||||||
FIREFOX = "/usr/bin/firefox"
|
FIREFOX = "/usr/bin/firefox"
|
||||||
GETCERT = "/usr/bin/getcert"
|
GETCERT = "/usr/bin/getcert"
|
||||||
GPG = "/usr/bin/gpg"
|
|
||||||
GPG2 = "/usr/bin/gpg2"
|
GPG2 = "/usr/bin/gpg2"
|
||||||
|
GPG_CONNECT_AGENT = "/usr/bin/gpg-connect-agent"
|
||||||
GPG_AGENT = "/usr/bin/gpg-agent"
|
GPG_AGENT = "/usr/bin/gpg-agent"
|
||||||
IPA_GETCERT = "/usr/bin/ipa-getcert"
|
IPA_GETCERT = "/usr/bin/ipa-getcert"
|
||||||
KADMIN_LOCAL = '/usr/sbin/kadmin.local'
|
KADMIN_LOCAL = '/usr/sbin/kadmin.local'
|
||||||
|
@ -17,11 +17,13 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import optparse # pylint: disable=deprecated-module
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import pwd
|
import pwd
|
||||||
@ -53,45 +55,38 @@ ISO8601_DATETIME_FMT = '%Y-%m-%dT%H:%M:%S'
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A test gpg can be generated like this:
|
A test GnuPG key can be generated like this:
|
||||||
|
|
||||||
# cat >keygen <<EOF
|
# cat >keygen <<EOF
|
||||||
%echo Generating a standard key
|
%echo Generating a standard key
|
||||||
Key-Type: RSA
|
Key-Type: RSA
|
||||||
Key-Length: 2048
|
Key-Length: 2048
|
||||||
Name-Real: IPA Backup
|
Name-Real: IPA Backup
|
||||||
Name-Comment: IPA Backup
|
Name-Comment: IPA Backup
|
||||||
Name-Email: root@example.com
|
Name-Email: root@example.com
|
||||||
Expire-Date: 0
|
Expire-Date: 0
|
||||||
%pubring /root/backup.pub
|
Passphrase: SecretPassPhrase42
|
||||||
%secring /root/backup.sec
|
%commit
|
||||||
%commit
|
%echo done
|
||||||
%echo done
|
|
||||||
EOF
|
EOF
|
||||||
# gpg --batch --gen-key keygen
|
# export GNUPGHOME=/root/backup
|
||||||
# gpg --no-default-keyring --secret-keyring /root/backup.sec \
|
# mkdir -p $GNUPGHOME
|
||||||
--keyring /root/backup.pub --list-secret-keys
|
# gpg2 --batch --gen-key keygen
|
||||||
|
# gpg2 --list-secret-keys
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def encrypt_file(filename, keyring, remove_original=True):
|
def encrypt_file(filename, remove_original=True):
|
||||||
source = filename
|
source = filename
|
||||||
dest = filename + '.gpg'
|
dest = filename + '.gpg'
|
||||||
|
|
||||||
args = [paths.GPG,
|
args = [
|
||||||
'--batch',
|
paths.GPG2,
|
||||||
'--default-recipient-self',
|
'--batch',
|
||||||
'-o', dest]
|
'--default-recipient-self',
|
||||||
|
'--output', dest,
|
||||||
if keyring is not None:
|
'--encrypt', source,
|
||||||
args.append('--no-default-keyring')
|
]
|
||||||
args.append('--keyring')
|
|
||||||
args.append(keyring + '.pub')
|
|
||||||
args.append('--secret-keyring')
|
|
||||||
args.append(keyring + '.sec')
|
|
||||||
|
|
||||||
args.append('-e')
|
|
||||||
args.append(source)
|
|
||||||
|
|
||||||
result = run(args, raiseonerr=False)
|
result = run(args, raiseonerr=False)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
@ -233,16 +228,22 @@ class Backup(admintool.AdminTool):
|
|||||||
def add_options(cls, parser):
|
def add_options(cls, parser):
|
||||||
super(Backup, cls).add_options(parser, debug_option=True)
|
super(Backup, cls).add_options(parser, debug_option=True)
|
||||||
|
|
||||||
parser.add_option("--gpg-keyring", dest="gpg_keyring",
|
parser.add_option(
|
||||||
help="The gpg key name to be used (or full path)")
|
"--gpg-keyring", dest="gpg_keyring",
|
||||||
parser.add_option("--gpg", dest="gpg", action="store_true",
|
help=optparse.SUPPRESS_HELP)
|
||||||
default=False, help="Encrypt the backup")
|
parser.add_option(
|
||||||
parser.add_option("--data", dest="data_only", action="store_true",
|
"--gpg", dest="gpg", action="store_true",
|
||||||
|
default=False, help="Encrypt the backup")
|
||||||
|
parser.add_option(
|
||||||
|
"--data", dest="data_only", action="store_true",
|
||||||
default=False, help="Backup only the data")
|
default=False, help="Backup only the data")
|
||||||
parser.add_option("--logs", dest="logs", action="store_true",
|
parser.add_option(
|
||||||
|
"--logs", dest="logs", action="store_true",
|
||||||
default=False, help="Include log files in backup")
|
default=False, help="Include log files in backup")
|
||||||
parser.add_option("--online", dest="online", action="store_true",
|
parser.add_option(
|
||||||
default=False, help="Perform the LDAP backups online, for data only.")
|
"--online", dest="online", action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Perform the LDAP backups online, for data only.")
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(self, log_file_mode='a'):
|
def setup_logging(self, log_file_mode='a'):
|
||||||
@ -255,9 +256,11 @@ class Backup(admintool.AdminTool):
|
|||||||
installutils.check_server_configuration()
|
installutils.check_server_configuration()
|
||||||
|
|
||||||
if options.gpg_keyring is not None:
|
if options.gpg_keyring is not None:
|
||||||
if not os.path.exists(options.gpg_keyring + '.pub'):
|
print(
|
||||||
raise admintool.ScriptError('No such key %s' %
|
"--gpg-keyring is no longer supported, use GNUPGHOME "
|
||||||
options.gpg_keyring)
|
"environment variable to use a custom GnuPG2 directory.",
|
||||||
|
file=sys.stderr
|
||||||
|
)
|
||||||
options.gpg = True
|
options.gpg = True
|
||||||
|
|
||||||
if options.online and not options.data_only:
|
if options.online and not options.data_only:
|
||||||
@ -266,7 +269,7 @@ class Backup(admintool.AdminTool):
|
|||||||
|
|
||||||
if options.gpg:
|
if options.gpg:
|
||||||
tmpfd = write_tmp_file('encryptme')
|
tmpfd = write_tmp_file('encryptme')
|
||||||
newfile = encrypt_file(tmpfd.name, options.gpg_keyring, False)
|
newfile = encrypt_file(tmpfd.name, False)
|
||||||
os.unlink(newfile)
|
os.unlink(newfile)
|
||||||
|
|
||||||
if options.data_only and options.logs:
|
if options.data_only and options.logs:
|
||||||
@ -627,7 +630,7 @@ class Backup(admintool.AdminTool):
|
|||||||
|
|
||||||
if encrypt:
|
if encrypt:
|
||||||
logger.info('Encrypting %s', filename)
|
logger.info('Encrypting %s', filename)
|
||||||
filename = encrypt_file(filename, keyring)
|
filename = encrypt_file(filename)
|
||||||
|
|
||||||
shutil.move(self.header, backup_dir)
|
shutil.move(self.header, backup_dir)
|
||||||
|
|
||||||
|
@ -17,11 +17,13 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import optparse # pylint: disable=deprecated-module
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import pwd
|
import pwd
|
||||||
@ -77,7 +79,7 @@ def recursive_chown(path, uid, gid):
|
|||||||
os.chmod(os.path.join(root, file), 0o640)
|
os.chmod(os.path.join(root, file), 0o640)
|
||||||
|
|
||||||
|
|
||||||
def decrypt_file(tmpdir, filename, keyring):
|
def decrypt_file(tmpdir, filename):
|
||||||
source = filename
|
source = filename
|
||||||
(dest, ext) = os.path.splitext(filename)
|
(dest, ext) = os.path.splitext(filename)
|
||||||
|
|
||||||
@ -87,19 +89,12 @@ def decrypt_file(tmpdir, filename, keyring):
|
|||||||
dest = os.path.basename(dest)
|
dest = os.path.basename(dest)
|
||||||
dest = os.path.join(tmpdir, dest)
|
dest = os.path.join(tmpdir, dest)
|
||||||
|
|
||||||
args = [paths.GPG,
|
args = [
|
||||||
'--batch',
|
paths.GPG2,
|
||||||
'-o', dest]
|
'--batch',
|
||||||
|
'--output', dest,
|
||||||
if keyring is not None:
|
'--decrypt', source,
|
||||||
args.append('--no-default-keyring')
|
]
|
||||||
args.append('--keyring')
|
|
||||||
args.append(keyring + '.pub')
|
|
||||||
args.append('--secret-keyring')
|
|
||||||
args.append(keyring + '.sec')
|
|
||||||
|
|
||||||
args.append('-d')
|
|
||||||
args.append(source)
|
|
||||||
|
|
||||||
result = run(args, raiseonerr=False)
|
result = run(args, raiseonerr=False)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
@ -161,21 +156,30 @@ class Restore(admintool.AdminTool):
|
|||||||
def add_options(cls, parser):
|
def add_options(cls, parser):
|
||||||
super(Restore, cls).add_options(parser, debug_option=True)
|
super(Restore, cls).add_options(parser, debug_option=True)
|
||||||
|
|
||||||
parser.add_option("-p", "--password", dest="password",
|
parser.add_option(
|
||||||
|
"-p", "--password", dest="password",
|
||||||
help="Directory Manager password")
|
help="Directory Manager password")
|
||||||
parser.add_option("--gpg-keyring", dest="gpg_keyring",
|
parser.add_option(
|
||||||
help="The gpg key name to be used")
|
"--gpg-keyring", dest="gpg_keyring",
|
||||||
parser.add_option("--data", dest="data_only", action="store_true",
|
help=optparse.SUPPRESS_HELP)
|
||||||
|
parser.add_option(
|
||||||
|
"--data", dest="data_only", action="store_true",
|
||||||
default=False, help="Restore only the data")
|
default=False, help="Restore only the data")
|
||||||
parser.add_option("--online", dest="online", action="store_true",
|
parser.add_option(
|
||||||
default=False, help="Perform the LDAP restores online, for data only.")
|
"--online", dest="online", action="store_true",
|
||||||
parser.add_option("--instance", dest="instance",
|
default=False,
|
||||||
|
help="Perform the LDAP restores online, for data only.")
|
||||||
|
parser.add_option(
|
||||||
|
"--instance", dest="instance",
|
||||||
help="The 389-ds instance to restore (defaults to all found)")
|
help="The 389-ds instance to restore (defaults to all found)")
|
||||||
parser.add_option("--backend", dest="backend",
|
parser.add_option(
|
||||||
|
"--backend", dest="backend",
|
||||||
help="The backend to restore within the instance or instances")
|
help="The backend to restore within the instance or instances")
|
||||||
parser.add_option('--no-logs', dest="no_logs", action="store_true",
|
parser.add_option(
|
||||||
|
'--no-logs', dest="no_logs", action="store_true",
|
||||||
default=False, help="Do not restore log files from the backup")
|
default=False, help="Do not restore log files from the backup")
|
||||||
parser.add_option('-U', '--unattended', dest="unattended",
|
parser.add_option(
|
||||||
|
'-U', '--unattended', dest="unattended",
|
||||||
action="store_true", default=False,
|
action="store_true", default=False,
|
||||||
help="Unattended restoration never prompts the user")
|
help="Unattended restoration never prompts the user")
|
||||||
|
|
||||||
@ -201,9 +205,11 @@ class Restore(admintool.AdminTool):
|
|||||||
parser.error("must provide path to backup directory")
|
parser.error("must provide path to backup directory")
|
||||||
|
|
||||||
if options.gpg_keyring:
|
if options.gpg_keyring:
|
||||||
if (not os.path.exists(options.gpg_keyring + '.pub') or
|
print(
|
||||||
not os.path.exists(options.gpg_keyring + '.sec')):
|
"--gpg-keyring is no longer supported, use GNUPGHOME "
|
||||||
parser.error("no such key %s" % options.gpg_keyring)
|
"environment variable to use a custom GnuPG2 directory.",
|
||||||
|
file=sys.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def ask_for_options(self):
|
def ask_for_options(self):
|
||||||
@ -327,7 +333,7 @@ class Restore(admintool.AdminTool):
|
|||||||
try:
|
try:
|
||||||
dirsrv = services.knownservices.dirsrv
|
dirsrv = services.knownservices.dirsrv
|
||||||
|
|
||||||
self.extract_backup(options.gpg_keyring)
|
self.extract_backup()
|
||||||
|
|
||||||
if restore_type == 'FULL':
|
if restore_type == 'FULL':
|
||||||
self.restore_default_conf()
|
self.restore_default_conf()
|
||||||
@ -754,8 +760,7 @@ class Restore(admintool.AdminTool):
|
|||||||
self.backup_services = config.get('ipa', 'services').split(',')
|
self.backup_services = config.get('ipa', 'services').split(',')
|
||||||
# pylint: enable=no-member
|
# pylint: enable=no-member
|
||||||
|
|
||||||
|
def extract_backup(self):
|
||||||
def extract_backup(self, keyring=None):
|
|
||||||
'''
|
'''
|
||||||
Extract the contents of the tarball backup into a temporary location,
|
Extract the contents of the tarball backup into a temporary location,
|
||||||
decrypting if necessary.
|
decrypting if necessary.
|
||||||
@ -776,7 +781,7 @@ class Restore(admintool.AdminTool):
|
|||||||
|
|
||||||
if encrypt:
|
if encrypt:
|
||||||
logger.info('Decrypting %s', filename)
|
logger.info('Decrypting %s', filename)
|
||||||
filename = decrypt_file(self.dir, filename, keyring)
|
filename = decrypt_file(self.dir, filename)
|
||||||
|
|
||||||
os.chdir(self.dir)
|
os.chdir(self.dir)
|
||||||
|
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2017 FreeIPA Contributors. See COPYING for license
|
# Copyright (C) 2017 FreeIPA Contributors. See COPYING for license
|
||||||
#
|
#
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import binascii
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from ipaplatform.paths import paths
|
||||||
from ipapython import ipautil
|
from ipapython import ipautil
|
||||||
from ipaserver.install import installutils
|
from ipaserver.install import installutils
|
||||||
|
from ipaserver.install import ipa_backup
|
||||||
|
from ipaserver.install import ipa_restore
|
||||||
|
|
||||||
EXAMPLE_CONFIG = [
|
EXAMPLE_CONFIG = [
|
||||||
'foo=1\n',
|
'foo=1\n',
|
||||||
@ -22,8 +30,6 @@ WHITESPACE_CONFIG = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def tempdir(request):
|
def tempdir(request):
|
||||||
tempdir = tempfile.mkdtemp()
|
tempdir = tempfile.mkdtemp()
|
||||||
@ -35,6 +41,87 @@ def tempdir(request):
|
|||||||
return tempdir
|
return tempdir
|
||||||
|
|
||||||
|
|
||||||
|
GPG_GENKEY = textwrap.dedent("""
|
||||||
|
%echo Generating a standard key
|
||||||
|
Key-Type: RSA
|
||||||
|
Key-Length: 2048
|
||||||
|
Name-Real: IPA Backup
|
||||||
|
Name-Comment: IPA Backup
|
||||||
|
Name-Email: root@example.com
|
||||||
|
Expire-Date: 0
|
||||||
|
Passphrase: {passphrase}
|
||||||
|
%commit
|
||||||
|
%echo done
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def gpgkey(request, tempdir):
|
||||||
|
passphrase = "Secret123"
|
||||||
|
gnupghome = os.path.join(tempdir, "gnupg")
|
||||||
|
os.makedirs(gnupghome, 0o700)
|
||||||
|
# provide clean env for gpg test
|
||||||
|
env = os.environ.copy()
|
||||||
|
orig_gnupghome = env.get('GNUPGHOME')
|
||||||
|
env['GNUPGHOME'] = gnupghome
|
||||||
|
env['LC_ALL'] = 'C.UTF-8'
|
||||||
|
env['LANGUAGE'] = 'C'
|
||||||
|
devnull = open(os.devnull, 'w')
|
||||||
|
|
||||||
|
# allow passing passphrases to agent
|
||||||
|
with open(os.path.join(gnupghome, "gpg-agent.conf"), 'w') as f:
|
||||||
|
f.write("verbose\n")
|
||||||
|
f.write("allow-preset-passphrase\n")
|
||||||
|
|
||||||
|
# run agent in background
|
||||||
|
agent = subprocess.Popen(
|
||||||
|
[paths.GPG_AGENT, '--batch', '--daemon'],
|
||||||
|
env=env, stdout=devnull, stderr=devnull
|
||||||
|
)
|
||||||
|
|
||||||
|
def fin():
|
||||||
|
if orig_gnupghome is not None:
|
||||||
|
os.environ['GNUPGHOME'] = orig_gnupghome
|
||||||
|
else:
|
||||||
|
os.environ.pop('GNUPGHOME', None)
|
||||||
|
agent.kill()
|
||||||
|
agent.wait()
|
||||||
|
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
|
||||||
|
# create public / private key pair
|
||||||
|
keygen = os.path.join(gnupghome, 'keygen')
|
||||||
|
with open(keygen, 'w') as f:
|
||||||
|
f.write(GPG_GENKEY.format(passphrase=passphrase))
|
||||||
|
subprocess.check_call(
|
||||||
|
[paths.GPG2, '--batch', '--gen-key', keygen],
|
||||||
|
env=env, stdout=devnull, stderr=devnull
|
||||||
|
)
|
||||||
|
|
||||||
|
# get keygrip of private key
|
||||||
|
out = subprocess.check_output(
|
||||||
|
[paths.GPG2, "--list-secret-keys", "--with-keygrip"],
|
||||||
|
env=env, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
mo = re.search("Keygrip = ([A-Z0-9]{32,})", out.decode('utf-8'))
|
||||||
|
if mo is None:
|
||||||
|
raise ValueError(out.decode('utf-8'))
|
||||||
|
keygrip = mo.group(1)
|
||||||
|
|
||||||
|
# unlock private key
|
||||||
|
cmd = "PRESET_PASSPHRASE {} -1 {}".format(
|
||||||
|
keygrip,
|
||||||
|
binascii.hexlify(passphrase.encode('utf-8')).decode('utf-8')
|
||||||
|
)
|
||||||
|
subprocess.check_call(
|
||||||
|
[paths.GPG_CONNECT_AGENT, cmd, "/bye"],
|
||||||
|
env=env, stdout=devnull, stderr=devnull
|
||||||
|
)
|
||||||
|
|
||||||
|
# set env for the rest of the progress
|
||||||
|
os.environ['GNUPGHOME'] = gnupghome
|
||||||
|
|
||||||
|
|
||||||
class test_set_directive_lines(object):
|
class test_set_directive_lines(object):
|
||||||
def test_remove_directive(self):
|
def test_remove_directive(self):
|
||||||
lines = installutils.set_directive_lines(
|
lines = installutils.set_directive_lines(
|
||||||
@ -198,3 +285,21 @@ def test_gpg_encrypt(tempdir):
|
|||||||
|
|
||||||
with pytest.raises(ipautil.CalledProcessError):
|
with pytest.raises(ipautil.CalledProcessError):
|
||||||
installutils.decrypt_file(encrypted, decrypted, password='invalid')
|
installutils.decrypt_file(encrypted, decrypted, password='invalid')
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpg_asymmetric(tempdir, gpgkey):
|
||||||
|
src = os.path.join(tempdir, "asymmetric.txt")
|
||||||
|
encrypted = src + ".gpg"
|
||||||
|
payload = 'Dummy text\n'
|
||||||
|
|
||||||
|
with open(src, 'w') as f:
|
||||||
|
f.write(payload)
|
||||||
|
|
||||||
|
ipa_backup.encrypt_file(src, remove_original=True)
|
||||||
|
assert os.path.isfile(encrypted)
|
||||||
|
assert not os.path.exists(src)
|
||||||
|
|
||||||
|
ipa_restore.decrypt_file(tempdir, encrypted)
|
||||||
|
assert os.path.isfile(src)
|
||||||
|
with open(src) as f:
|
||||||
|
assert f.read() == payload
|
||||||
|
Loading…
Reference in New Issue
Block a user