diff -Nru ubuntu-advantage-tools-2/debian/changelog ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/changelog --- ubuntu-advantage-tools-2/debian/changelog 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/changelog 2017-10-23 15:27:00.000000000 -0200 @@ -1,3 +1,78 @@ +ubuntu-advantage-tools (10~ubuntu0.16.04.1) xenial; urgency=medium + + * Backport version 10 to xenial (LP: #1719671) + + -- Andreas Hasenack Mon, 23 Oct 2017 15:27:00 -0200 + +ubuntu-advantage-tools (10) artful; urgency=medium + + * New upstream release with FIPS support (LP: #1718291) + + -- Andreas Hasenack Tue, 19 Sep 2017 18:33:03 -0300 + +ubuntu-advantage-tools (9) artful; urgency=medium + + * New upstream release: + - call apt-get with the non-interactive frontend variable set, and tell + dpkg to keep the old config file by default should there be any prompts + about that. (LP: #1715012) + - split the one big test file into multiple smaller files, for better + maintainability. + + -- Andreas Hasenack Mon, 04 Sep 2017 15:15:58 -0300 + +ubuntu-advantage-tools (8) artful; urgency=medium + + * Release to artful (LP: #1711369) + + -- Andreas Hasenack Tue, 22 Aug 2017 12:55:13 -0300 + +ubuntu-advantage-tools (7) trusty; urgency=medium + + * d/control: update package description + + -- Andreas Hasenack Thu, 17 Aug 2017 11:31:37 -0300 + +ubuntu-advantage-tools (6) trusty; urgency=medium + + * New release version 6. Main changes: + - document return codes on the manpage (Fixes: #33) + - new status command (Fixes: #40) + - restrict esm to precise only (Fixes: #43) + - drop the livepatch motd update, only esm has motd output now + (Fixes: #44) + - skip tests during package building (Fixes #49) + + -- Andreas Hasenack Wed, 16 Aug 2017 15:21:16 -0300 + +ubuntu-advantage-tools (5) trusty; urgency=medium + + * Only display apt output in the case of errors (Fixes #34). + + -- Andreas Hasenack Tue, 08 Aug 2017 12:41:25 -0300 + +ubuntu-advantage-tools (4) trusty; urgency=medium + + * Check running kernel version before enabling the Livepatch service + (Fixes #30). + + -- Andreas Hasenack Mon, 07 Aug 2017 18:45:23 -0300 + +ubuntu-advantage-tools (3) trusty; urgency=medium + + * Add livepatch support: + - New commands: + + enable-livepatch + + disable-livepatch + + is-livepatch-enabled + - new tests + - new manpage + - new help output + - new README.md + - new MOTD + + -- Andreas Hasenack Mon, 07 Aug 2017 11:09:31 -0300 + ubuntu-advantage-tools (2) trusty; urgency=medium * ubuntu-advantage & /etc/update-motd.d/99-esm now build, run and are quiet diff -Nru ubuntu-advantage-tools-2/debian/control ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/control --- ubuntu-advantage-tools-2/debian/control 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/control 2017-09-19 18:58:30.000000000 -0300 @@ -11,9 +11,10 @@ Package: ubuntu-advantage-tools Architecture: all Depends: ${misc:Depends} -Description: Ubuntu Advantage tools for ESM - This package ships Ubuntu Advantage subscription management tools. +Description: management tools for Ubuntu Advantage + Ubuntu Advantage is the professional package of tooling, technology + and expertise from Canonical, helping organisations around the world + manage their Ubuntu deployments. . - It enables Ubuntu Advantage subscribers to enable Extended Security - Maintenance (ESM) repository on Ubuntu 12.04 LTS (Precise Pangolin) - systems. + Subscribers to Ubuntu Advantage will find helpful tools for accessing + services in this package. diff -Nru ubuntu-advantage-tools-2/debian/install ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/install --- ubuntu-advantage-tools-2/debian/install 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/install 2017-09-19 18:58:30.000000000 -0300 @@ -1,3 +1,3 @@ ubuntu-advantage usr/bin/ -ubuntu-esm-keyring.gpg usr/share/keyrings/ +keyrings/*.gpg usr/share/keyrings/ update-motd.d etc/ diff -Nru ubuntu-advantage-tools-2/debian/rules ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/rules --- ubuntu-advantage-tools-2/debian/rules 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/debian/rules 2017-09-19 18:58:30.000000000 -0300 @@ -1,3 +1,6 @@ #!/usr/bin/make -f %: dh $@ + +override_dh_auto_test: + @echo "Skipping tests during package builds." Binary files /tmp/1A4EJrfRy0/ubuntu-advantage-tools-2/keyrings/ubuntu-esm-keyring.gpg and /tmp/LdovPo2Xgg/ubuntu-advantage-tools-10~ubuntu0.16.04.1/keyrings/ubuntu-esm-keyring.gpg differ Binary files /tmp/1A4EJrfRy0/ubuntu-advantage-tools-2/keyrings/ubuntu-fips-keyring.gpg and /tmp/LdovPo2Xgg/ubuntu-advantage-tools-10~ubuntu0.16.04.1/keyrings/ubuntu-fips-keyring.gpg differ diff -Nru ubuntu-advantage-tools-2/Makefile ubuntu-advantage-tools-10~ubuntu0.16.04.1/Makefile --- ubuntu-advantage-tools-2/Makefile 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/Makefile 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,11 @@ +build: + @echo Nothing to build. + +test: + @tox + +lint: + @shellcheck -s dash ubuntu-advantage update-motd.d/* + + +.PHONY: build test lint diff -Nru ubuntu-advantage-tools-2/README.md ubuntu-advantage-tools-10~ubuntu0.16.04.1/README.md --- ubuntu-advantage-tools-2/README.md 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/README.md 2017-09-19 18:58:30.000000000 -0300 @@ -2,34 +2,35 @@ [![Build Status](https://travis-ci.org/CanonicalLtd/ubuntu-advantage-script.svg?branch=master)](https://travis-ci.org/CanonicalLtd/ubuntu-advantage-script) -Script to enable the Ubuntu ESM (Extended Security Maintenance) archive for Precise. +This tool is used to enable or disable specific Ubuntu Advantage offerings from Canonical. +Currently it supports the following: -## Enabling the ESM archive +- [Ubuntu Extended Security Maintenance](https://ubuntu.com/esm) archive. +- [Canonical Livepatch](https://www.ubuntu.com/server/livepatch) service for managed live kernel patching. +- Canonical FIPS 140-2 Certified Modules. Install Configure and Enable FIPS modules. -To enable the archive, run: +Run -```bash -$ sudo ubuntu-advantage enable-esm +``` +$ ./ubuntu-advantage ``` -where the `token` is in the form `:`. +to display usage information. -## Disabling the ESM archive +## Testing -To disable the archive, run: +System tests and tests lint: -```bash -$ sudo ubuntu-advantage disable-esm +``` +$ make test ``` -## Testing - -Unit tests & lint: +Lint: -```bash -$ tox +``` +$ make lint ``` Dep8 Tests: diff -Nru ubuntu-advantage-tools-2/requirements.txt ubuntu-advantage-tools-10~ubuntu0.16.04.1/requirements.txt --- ubuntu-advantage-tools-2/requirements.txt 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/requirements.txt 1969-12-31 21:00:00.000000000 -0300 @@ -1,2 +0,0 @@ -flake8 -fixtures diff -Nru ubuntu-advantage-tools-2/tests/fakes.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/fakes.py --- ubuntu-advantage-tools-2/tests/fakes.py 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/fakes.py 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,70 @@ +# Fake for commands invoked by the script. + +SNAP_LIVEPATCH_INSTALLED = """\n +if [ "$1" = "list" ]; then + cat <> "${log_path}/apt_get.args" +env >> "${log_path}/apt_get.env" +""" diff -Nru ubuntu-advantage-tools-2/tests/test_esm.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_esm.py --- ubuntu-advantage-tools-2/tests/test_esm.py 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_esm.py 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,153 @@ +# Tests for ESM-related commands. + +from testing import UbuntuAdvantageTest +from fakes import APT_GET_LOG_WRAPPER + + +class ESMTest(UbuntuAdvantageTest): + + SERIES = 'precise' + + def test_enable_esm(self): + """The enable-esm option enables the ESM repository.""" + process = self.script('enable-esm', 'user:pass') + self.assertEqual(0, process.returncode) + self.assertIn('Ubuntu ESM repository enabled', process.stdout) + expected = ( + 'deb https://user:pass@esm.ubuntu.com/ubuntu precise main\n' + '# deb-src https://user:pass@esm.ubuntu.com/ubuntu precise main\n') + self.assertEqual(expected, self.repo_list.read_text()) + keyring_file = self.trusted_gpg_dir / 'ubuntu-esm-keyring.gpg' + self.assertEqual('GPG key', keyring_file.read_text()) + # the apt-transport-https dependency is already installed + self.assertNotIn( + 'Installing missing dependency apt-transport-https', + process.stdout) + + def test_enable_esm_install_apt_transport_https(self): + """enable-esm installs apt-transport-https if needed.""" + self.apt_method_https.unlink() + process = self.script('enable-esm', 'user:pass') + self.assertEqual(0, process.returncode) + self.assertIn( + 'Installing missing dependency apt-transport-https', + process.stdout) + + def test_enable_esm_install_apt_transport_https_apt_get_options(self): + """apt-get accepts defaults when installing apt-transports-https.""" + self.apt_method_https.unlink() + self.make_fake_binary('apt-get', command=APT_GET_LOG_WRAPPER) + self.script('enable-esm', 'user:pass') + # apt-get is called both to install packages and update lists + self.assertIn( + '-y -o Dpkg::Options::=--force-confold install ' + 'apt-transport-https', + self.read_file('apt_get.args')) + self.assertIn( + '-y -o Dpkg::Options::=--force-confold update', + self.read_file('apt_get.args')) + self.assertIn( + 'DEBIAN_FRONTEND=noninteractive', self.read_file('apt_get.env')) + + def test_enable_esm_install_apt_transport_https_fails(self): + """Stderr is printed if apt-transport-https install fails.""" + self.apt_method_https.unlink() + self.make_fake_binary('apt-get', command='echo failed >&2; false') + process = self.script('enable-esm', 'user:pass') + self.assertEqual(1, process.returncode) + self.assertIn('failed', process.stderr) + + def test_enable_esm_install_ca_certificates(self): + """enable-esm installs ca-certificates if needed.""" + self.ca_certificates.unlink() + process = self.script('enable-esm', 'user:pass') + self.assertEqual(0, process.returncode) + self.assertIn( + 'Installing missing dependency ca-certificates', + process.stdout) + + def test_enable_esm_install_ca_certificates_apt_get_options(self): + """apt-get accepts defaults when installing ca-certificates.""" + self.ca_certificates.unlink() + self.make_fake_binary('apt-get', command=APT_GET_LOG_WRAPPER) + self.script('enable-esm', 'user:pass') + # apt-get is called both to install packages and update lists + self.assertIn( + '-y -o Dpkg::Options::=--force-confold install ca-certificates', + self.read_file('apt_get.args')) + self.assertIn( + '-y -o Dpkg::Options::=--force-confold update', + self.read_file('apt_get.args')) + self.assertIn( + 'DEBIAN_FRONTEND=noninteractive', self.read_file('apt_get.env')) + + def test_enable_esm_install_ca_certificates_fails(self): + """Stderr is printed if ca-certificates install fails.""" + self.ca_certificates.unlink() + self.make_fake_binary('apt-get', command='echo failed >&2; false') + process = self.script('enable-esm', 'user:pass') + self.assertEqual(1, process.returncode) + self.assertIn('failed', process.stderr) + + def test_enable_esm_missing_token(self): + """The token must be specified when using enable-esm.""" + process = self.script('enable-esm') + self.assertEqual(3, process.returncode) + self.assertIn( + 'Invalid token, it must be in the form "user:password"', + process.stderr) + + def test_enable_esm_invalid_token(self): + """The ESM token must be specified as "user:password".""" + process = self.script('enable-esm', 'foo-bar') + self.assertEqual(3, process.returncode) + self.assertIn( + 'Invalid token, it must be in the form "user:password"', + process.stderr) + + def test_enable_esm_only_supported_on_precise(self): + """The enable-esm option fails if not on Precise.""" + self.SERIES = 'xenial' + process = self.script('enable-esm', 'user:pass') + self.assertEqual(4, process.returncode) + self.assertIn( + 'Extended Security Maintenance is not supported on xenial', + process.stderr) + + def test_disable_esm(self): + """The disable-esm option disables the ESM repository.""" + self.script('enable-esm', 'user:pass') + process = self.script('disable-esm') + self.assertEqual(0, process.returncode) + self.assertIn('Ubuntu ESM repository disabled', process.stdout) + self.assertFalse(self.repo_list.exists()) + # the keyring file is removed + keyring_file = self.trusted_gpg_dir / 'ubuntu-esm-keyring.gpg' + self.assertFalse(keyring_file.exists()) + + def test_disable_esm_disabled(self): + """If the ESM repo is not enabled, disable-esm is a no-op.""" + process = self.script('disable-esm') + self.assertEqual(0, process.returncode) + self.assertIn('Ubuntu ESM repository was not enabled', process.stdout) + + def test_disable_esm_only_supported_on_precise(self): + """The disable-esm option fails if not on Precise.""" + self.SERIES = 'xenial' + process = self.script('disable-esm') + self.assertEqual(4, process.returncode) + self.assertIn( + 'Extended Security Maintenance is not supported on xenial', + process.stderr) + + def test_is_esm_enabled_true(self): + """is-esm-enabled returns 0 if the repository is enabled.""" + self.make_fake_binary('apt-cache', command='echo esm.ubuntu.com') + process = self.script('is-esm-enabled') + self.assertEqual(0, process.returncode) + + def test_is_esm_enabled_false(self): + """is-esm-enabled returns 1 if the repository is not enabled.""" + self.make_fake_binary('apt-cache') + process = self.script('is-esm-enabled') + self.assertEqual(1, process.returncode) diff -Nru ubuntu-advantage-tools-2/tests/test_fips.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_fips.py --- ubuntu-advantage-tools-2/tests/test_fips.py 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_fips.py 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,143 @@ +# Tests for FIPS-related commands. + +from testing import UbuntuAdvantageTest + + +class FIPSTest(UbuntuAdvantageTest): + + SERIES = 'xenial' + + def test_enable_fips(self): + """The enable-fips option enables the FIPS repository.""" + process = self.script('enable-fips', 'user:pass') + self.assertEqual(0, process.returncode) + self.assertIn('Ubuntu FIPS PPA repository enabled.', process.stdout) + expected = ( + 'deb https://user:pass@private-ppa.launchpad.net/ubuntu-advantage/' + 'fips/ubuntu xenial main\n' + '# deb-src https://user:pass@private-ppa.launchpad.net/' + 'ubuntu-advantage/fips/ubuntu xenial main\n') + self.assertEqual(expected, self.repo_list.read_text()) + keyring_file = self.trusted_gpg_dir / 'ubuntu-fips-keyring.gpg' + self.assertEqual('GPG key', keyring_file.read_text()) + self.assertIn('Successfully configured FIPS. PLEASE REBOOT ' + 'to complete FIPS enablement.', process.stdout) + # the apt-transport-https dependency is already installed + self.assertNotIn( + 'Installing missing dependency apt-transport-https', + process.stdout) + + def test_enable_fips_already_enabled(self): + """If fips is already enabled, an error is returned.""" + self.make_fake_binary('dpkg-query') + p = self.fips_enabled_file + p.write_text('1') + process = self.script('enable-fips', 'user:pass') + self.assertEqual(6, process.returncode) + self.assertEqual( + 'FIPS is already enabled.', process.stdout.strip()) + + def test_enable_fips_installed_not_enabled(self): + """If fips is installed but not enabled an error is returned.""" + self.make_fake_binary('dpkg-query') + process = self.script('enable-fips', 'user:pass') + self.assertEqual(6, process.returncode) + self.assertEqual( + 'FIPS is already installed. ' + 'Please reboot into the FIPS kernel to enable it.', + process.stdout.strip()) + + def test_enable_fips_writes_config(self): + """The enable-fips option writes fips configuration.""" + self.script('enable-fips', 'user:pass') + self.assertEqual( + 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT fips=1"', + self.boot_cfg.read_text().strip()) + + def test_enable_fips_writes_config_with_boot_partition(self): + """The fips configuration includes the /boot partition.""" + self.fstab.write_text('/dev/sda1 /boot ext2 defaults 0 1\n') + self.script('enable-fips', 'user:pass') + self.assertIn('bootdev=/dev/sda1', self.boot_cfg.read_text()) + + def test_enable_fips_writes_config_s390x_parameters(self): + """On S390x, FIPS parameters are appended to the config file.""" + self.ARCH = 's390x' + self.boot_cfg.write_text('parameters=foo\n') + self.script('enable-fips', 'user:pass') + self.assertEqual('parameters=foo fips=1\n', self.boot_cfg.read_text()) + + def test_enable_fips_install_apt_transport_https(self): + """enable-fips installs apt-transport-https if needed.""" + self.apt_method_https.unlink() + process = self.script('enable-fips', 'user:pass') + self.assertEqual(0, process.returncode) + self.assertIn( + 'Installing missing dependency apt-transport-https', + process.stdout) + + def test_enable_fips_install_apt_transport_https_fails(self): + """Stderr is printed if apt-transport-https install fails.""" + self.apt_method_https.unlink() + self.make_fake_binary('apt-get', command='echo failed >&2; false') + process = self.script('enable-fips', 'user:pass') + self.assertEqual(1, process.returncode) + self.assertIn('failed', process.stderr) + + def test_enable_fips_install_ca_certificates(self): + """enable-fips installs ca-certificates if needed.""" + self.ca_certificates.unlink() + process = self.script('enable-fips', 'user:pass') + self.assertEqual(0, process.returncode) + self.assertIn( + 'Installing missing dependency ca-certificates', + process.stdout) + + def test_enable_fips_install_ca_certificates_fails(self): + """Stderr is printed if ca-certificates install fails.""" + self.ca_certificates.unlink() + self.make_fake_binary('apt-get', command='echo failed >&2; false') + process = self.script('enable-fips', 'user:pass') + self.assertEqual(1, process.returncode) + self.assertIn('failed', process.stderr) + + def test_enable_fips_missing_token(self): + """The token must be specified when using enable-fips.""" + process = self.script('enable-fips') + self.assertEqual(3, process.returncode) + self.assertIn( + 'Invalid token, it must be in the form "user:password"', + process.stderr) + + def test_enable_fips_invalid_token(self): + """The FIPS token must be specified as "user:password".""" + process = self.script('enable-fips', 'foo-bar') + self.assertEqual(3, process.returncode) + self.assertIn( + 'Invalid token, it must be in the form "user:password"', + process.stderr) + + def test_enable_fips_only_supported_on_xenial(self): + """The enable-fips option fails if not on Xenial.""" + self.SERIES = 'zesty' + process = self.script('enable-fips', 'user:pass') + self.assertEqual(4, process.returncode) + self.assertIn( + 'Canonical FIPS 140-2 Modules is not supported on zesty', + process.stderr) + + def test_is_fips_enabled_true(self): + """is-fips-enabled returns 0 if fips is enabled.""" + self.make_fake_binary('dpkg-query') + p = self.fips_enabled_file + p.write_text('1') + process = self.script('is-fips-enabled') + self.assertEqual(0, process.returncode) + + def test_is_fips_enabled_false(self): + """is-fips-enabled returns 1 if fips is not enabled.""" + self.make_fake_binary('dpkg-query') + p = self.fips_enabled_file + p.write_text('0') + process = self.script('is-fips-enabled') + self.assertEqual(1, process.returncode) diff -Nru ubuntu-advantage-tools-2/tests/testing.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/testing.py --- ubuntu-advantage-tools-2/tests/testing.py 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/testing.py 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,112 @@ +# Test helpers. + +import os +from pathlib import Path +import subprocess +from collections import namedtuple + +from fixtures import ( + TestWithFixtures, + TempDir) + +from fakes import ( + SNAP_LIVEPATCH_INSTALLED, + SNAP_LIVEPATCH_NOT_INSTALLED, + LIVEPATCH_ENABLED, + LIVEPATCH_DISABLED) + +ProcessResult = namedtuple('ProcessResult', ['returncode', 'stdout', 'stderr']) + + +class UbuntuAdvantageTest(TestWithFixtures): + + SERIES = None + KERNEL_VERSION = None + ARCH = None + + def setUp(self): + super(UbuntuAdvantageTest, self).setUp() + self.tempdir = self.useFixture(TempDir()) + self.repo_list = Path(self.tempdir.join('repo.list')) + self.boot_cfg = Path(self.tempdir.join('boot.cfg')) + self.fstab = Path(self.tempdir.join('fstab')) + self.fips_enabled_file = Path(self.tempdir.join('fips_enabled_file')) + self.bin_dir = Path(self.tempdir.join('bin')) + self.etc_dir = Path(self.tempdir.join('etc')) + self.keyrings_dir = Path(self.tempdir.join('keyrings')) + self.trusted_gpg_dir = Path(self.tempdir.join('trusted.gpg.d')) + self.apt_method_https = self.bin_dir / 'apt-method-https' + self.ca_certificates = self.bin_dir / 'update-ca-certificates' + self.snapd = self.bin_dir / 'snapd' + # setup directories and files + self.bin_dir.mkdir() + self.keyrings_dir.mkdir() + self.etc_dir.mkdir() + self.fstab.write_text('') + self.trusted_gpg_dir.mkdir() + (self.keyrings_dir / 'ubuntu-esm-keyring.gpg').write_text('GPG key') + (self.keyrings_dir / 'ubuntu-fips-keyring.gpg').write_text('GPG key') + self.make_fake_binary('apt-get') + self.make_fake_binary('apt-method-https') + self.make_fake_binary('update-ca-certificates') + self.make_fake_binary('id', command='echo 0') + self.make_fake_binary('snapd') + self.make_fake_binary('update-grub') + self.make_fake_binary('zipl') + + def make_fake_binary(self, binary, command='true'): + """Create a script to fake a binary in path.""" + path = self.bin_dir / binary + path.write_text('#!/bin/sh\n{}\n'.format(command)) + path.chmod(0o755) + + def read_file(self, path): + """Return the content of a file with path relative to the test dir.""" + with open(self.tempdir.join(path)) as fh: + return fh.read() + + def script(self, *args): + """Run the script.""" + command = ['./ubuntu-advantage'] + command.extend(args) + path = os.pathsep.join([str(self.bin_dir), os.environ['PATH']]) + env = { + 'PATH': path, + 'FSTAB': str(self.fstab), + 'REPO_LIST': str(self.repo_list), + 'FIPS_REPO_LIST': str(self.repo_list), + 'FIPS_BOOT_CFG': str(self.boot_cfg), + 'FIPS_BOOT_CFG_DIR': str(self.etc_dir), + 'FIPS_ENABLED_FILE': str(self.fips_enabled_file), + 'KEYRINGS_DIR': str(self.keyrings_dir), + 'APT_KEYS_DIR': str(self.trusted_gpg_dir), + 'APT_METHOD_HTTPS': str(self.apt_method_https), + 'CA_CERTIFICATES': str(self.ca_certificates), + 'SNAPD': str(self.snapd)} + if self.SERIES: + env['SERIES'] = self.SERIES + if self.KERNEL_VERSION: + env['KERNEL_VERSION'] = self.KERNEL_VERSION + if self.ARCH: + env['ARCH'] = self.ARCH + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + process.wait() + result = ProcessResult( + process.returncode, + process.stdout.read().decode('utf-8'), + process.stderr.read().decode('utf-8')) + process.stdout.close() + process.stderr.close() + return result + + def setup_livepatch(self, installed=None, enabled=None): + """Setup livepatch-related fakes.""" + if installed is not None: + command = ( + SNAP_LIVEPATCH_INSTALLED if installed + else SNAP_LIVEPATCH_NOT_INSTALLED) + self.make_fake_binary('snap', command=command) + if enabled is not None: + command = LIVEPATCH_ENABLED if enabled else LIVEPATCH_DISABLED + self.make_fake_binary('canonical-livepatch', command=command) diff -Nru ubuntu-advantage-tools-2/tests/test_livepatch.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_livepatch.py --- ubuntu-advantage-tools-2/tests/test_livepatch.py 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_livepatch.py 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,175 @@ +# Tests for Livepatch-related commands. + +from testing import UbuntuAdvantageTest +from fakes import APT_GET_LOG_WRAPPER + + +class LivepatchTest(UbuntuAdvantageTest): + + SERIES = 'trusty' + KERNEL_VERSION = '4.4.0-89-generic' + + def setUp(self): + super().setUp() + self.setup_livepatch(installed=True, enabled=True) + self.livepatch_token = '0123456789abcdef1234567890abcdef' + + def test_livepatch_supported_trusty_xenial_not_precise(self): + """Livepatch is supported in trusty and xenial but not precise.""" + for series in ['trusty', 'xenial']: + self.SERIES = series + process = self.script('enable-livepatch') + # if we get a token error, that means we passed the ubuntu + # release check. + self.assertEqual(3, process.returncode) + self.assertIn('Invalid or missing Livepatch token', process.stderr) + # precise is not supported + self.SERIES = 'precise' + process = self.script('enable-livepatch') + self.assertEqual(4, process.returncode) + self.assertIn('Sorry, but Canonical Livepatch is not supported on ' + 'precise', process.stderr) + + def test_enable_livepatch_missing_token(self): + """The token must be specified when using enable-livepatch.""" + process = self.script('enable-livepatch') + self.assertEqual(3, process.returncode) + self.assertIn('Invalid or missing Livepatch token', process.stderr) + + def test_enable_livepatch_invalid_token(self): + """The Livepatch token must be specified as 32 hex chars.""" + process = self.script('enable-livepatch', 'invalid:token') + self.assertEqual(3, process.returncode) + self.assertIn('Invalid or missing Livepatch token', process.stderr) + + def test_enable_livepatch_installs_snapd(self): + """enable-livepatch installs snapd if needed.""" + self.snapd.unlink() + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(0, process.returncode) + self.assertIn('Installing missing dependency snapd', process.stdout) + + def test_enable_livepatch_apt_get_options(self): + """apt-get is called with options to accept defaults.""" + self.snapd.unlink() + self.make_fake_binary('apt-get', command=APT_GET_LOG_WRAPPER) + self.script('enable-livepatch', self.livepatch_token) + self.assertIn( + '-y -o Dpkg::Options::=--force-confold install snapd', + self.read_file('apt_get.args')) + self.assertIn( + 'DEBIAN_FRONTEND=noninteractive', self.read_file('apt_get.env')) + + def test_enable_livepatch_installs_snap(self): + """enable-livepatch installs the livepatch snap if needed.""" + self.setup_livepatch(installed=False) + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(0, process.returncode) + self.assertIn( + 'Installing the canonical-livepatch snap', process.stdout) + + def test_is_livepatch_enabled_true(self): + """is-livepatch-enabled returns 0 if the service is enabled.""" + process = self.script('is-livepatch-enabled') + self.assertEqual(0, process.returncode) + + def test_is_livepatch_enabled_false(self): + """is-livepatch-enabled returns 1 if the service is not enabled.""" + self.setup_livepatch(enabled=False) + process = self.script('is-livepatch-enabled') + self.assertEqual(1, process.returncode) + + def test_enable_livepatch_enabled(self): + """enable-livepatch when it's already enabled is detected.""" + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(0, process.returncode) + self.assertIn('Livepatch already enabled.', process.stdout) + + def test_enable_livepatch(self): + """enable-livepatch enables the livepatch service.""" + self.setup_livepatch(enabled=False) + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(0, process.returncode) + self.assertIn('Successfully enabled device. Using machine-token:', + process.stdout) + + def test_disable_livepatch_invalid_remove_snap_option(self): + """disable-livepatch complains if given an invalid argument.""" + process = self.script('disable-livepatch', '-invalidargument') + self.assertEqual(1, process.returncode) + self.assertIn('Unknown option "-invalidargument"', process.stderr) + + def test_disable_livepatch_already_disabled(self): + """disable-livepatch when it's already disabled is detected.""" + self.setup_livepatch(enabled=False) + process = self.script('disable-livepatch') + self.assertEqual(0, process.returncode) + self.assertIn('Livepatch is already disabled.', process.stdout) + + def test_disable_livepatch_supported_trusty_xenial_not_precise(self): + """Livepatch can't be disabled on unsupported distros.""" + for series in ['trusty', 'xenial']: + self.SERIES = series + process = self.script('disable-livepatch') + self.assertEqual(0, process.returncode) + # precise is not supported + self.SERIES = 'precise' + process = self.script('disable-livepatch') + # self.assertEqual(4, process.returncode) + self.assertIn('Sorry, but Canonical Livepatch is not supported on ' + 'precise', process.stderr) + + def test_disable_livepatch(self): + """disable-livepatch disables the service.""" + process = self.script('disable-livepatch') + self.assertEqual(0, process.returncode) + self.assertIn('Successfully disabled device. Removed machine-token: ' + 'deadbeefdeadbeefdeadbeefdeadbeef', process.stdout) + self.assertIn('Note: the canonical-livepatch snap is still installed', + process.stdout) + + def test_disable_livepatch_removing_snap(self): + """disable-livepatch with '-r' will also remove the snap.""" + process = self.script('disable-livepatch', '-r') + self.assertEqual(0, process.returncode) + self.assertIn('Successfully disabled device. Removed machine-token: ' + 'deadbeefdeadbeefdeadbeefdeadbeef', process.stdout) + self.assertIn('canonical-livepatch removed', process.stdout) + + def test_enable_livepatch_old_kernel(self): + """enable-livepatch with an old kernel will not enable livepatch.""" + self.setup_livepatch(enabled=False) + self.KERNEL_VERSION = '3.10.0-30-generic' + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(5, process.returncode) + self.assertIn('Your currently running kernel ({}) is too ' + 'old'.format(self.KERNEL_VERSION), process.stdout) + + def test_enable_livepatch_apt_output_is_hidden(self): + """Hide all apt output when enabling livepatch if exit status is 0.""" + self.make_fake_binary('apt-get', + command='echo this goes to stderr >&2;' + 'echo this goes to stdout;exit 0') + self.setup_livepatch(enabled=False) + self.snapd.unlink() + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(0, process.returncode) + # the UA script is redirecting stderr to stdout and capturing that, + # but then writing everything back to stderr if there was an error + self.assertNotIn('this goes to stderr', process.stderr) + self.assertNotIn('this goes to stdout', process.stderr) + + def test_enable_livepatch_apt_output_shown_if_errors(self): + """enable-livepatch displays apt errors if there were any.""" + apt_error_code = 99 + self.make_fake_binary( + 'apt-get', command='echo this goes to stderr >&2;' + 'echo this goes to stdout;exit {}'.format(apt_error_code)) + self.setup_livepatch(enabled=False) + self.snapd.unlink() + process = self.script('enable-livepatch', self.livepatch_token) + self.assertEqual(apt_error_code, process.returncode) + # the UA script is redirecting stderr to stdout and capturing that, + # but then writing everything back to stderr if there was an error + self.assertIn('this goes to stderr', process.stderr) + self.assertIn('this goes to stdout', process.stderr) diff -Nru ubuntu-advantage-tools-2/tests/test_script.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_script.py --- ubuntu-advantage-tools-2/tests/test_script.py 1969-12-31 21:00:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests/test_script.py 2017-09-19 18:58:30.000000000 -0300 @@ -0,0 +1,59 @@ +# Tests for the ubuntu-advantage script. + +from testing import UbuntuAdvantageTest + + +class UbuntuAdvantageScriptTest(UbuntuAdvantageTest): + + def test_enable_disable_needs_root(self): + """The script must be run as root for enable and disable actions.""" + self.make_fake_binary('id', command='echo 100') + actions = ['enable-esm', 'disable-esm', 'enable-livepatch', + 'disable-livepatch'] + for action in actions: + # we don't need to pass a token for the enable actions since the + # root check is before the parameter check + process = self.script(action) + self.assertEqual(2, process.returncode) + self.assertIn('This command must be run as root', process.stderr) + + def test_usage(self): + """Calling the script with no args prints out the usage.""" + process = self.script() + self.assertEqual(1, process.returncode) + self.assertIn('usage: ubuntu-advantage', process.stderr) + + def test_status_precise(self): + """The status command shows livepatch not available on precise.""" + self.SERIES = 'precise' + self.setup_livepatch(installed=False, enabled=False) + process = self.script('status') + self.assertIn("livepatch: disabled (not available)", process.stdout) + self.assertIn("esm: disabled", process.stdout) + + def test_status_precise_esm_enabled(self): + """The status command shows esm enabled.""" + self.SERIES = 'precise' + self.make_fake_binary('apt-cache', command='echo esm.ubuntu.com') + self.setup_livepatch(installed=False, enabled=False) + process = self.script('status') + self.assertIn("livepatch: disabled (not available)", process.stdout) + self.assertIn("esm: enabled", process.stdout) + + def test_status_xenial(self): + """The status command shows only livepatch available on xenial.""" + self.SERIES = 'xenial' + self.setup_livepatch(installed=True, enabled=False) + process = self.script('status') + self.assertIn("livepatch: disabled", process.stdout) + self.assertIn("esm: disabled (not available)", process.stdout) + + def test_status_xenial_livepatch_enabled(self): + """The status command shows livepatch enabled on xenial.""" + self.SERIES = 'xenial' + self.setup_livepatch(installed=True, enabled=True) + process = self.script('status') + self.assertIn("livepatch: enabled", process.stdout) + # the livepatch status output is also included + self.assertIn("fully-patched: true", process.stdout) + self.assertIn("esm: disabled (not available)", process.stdout) diff -Nru ubuntu-advantage-tools-2/tests.py ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests.py --- ubuntu-advantage-tools-2/tests.py 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tests.py 1969-12-31 21:00:00.000000000 -0300 @@ -1,168 +0,0 @@ -import os -from pathlib import Path -import subprocess -from collections import namedtuple - -from fixtures import TestWithFixtures, TempDir - - -ProcessResult = namedtuple('ProcessResult', ['returncode', 'stdout', 'stderr']) - - -class UbuntuAdvantageTest(TestWithFixtures): - - def setUp(self): - super(UbuntuAdvantageTest, self).setUp() - tempdir = self.useFixture(TempDir()) - self.repo_list = Path(tempdir.join('repo.list')) - self.bin_dir = Path(tempdir.join('bin')) - self.keyrings_dir = Path(tempdir.join('keyrings')) - self.trusted_gpg_dir = Path(tempdir.join('trusted.gpg.d')) - self.apt_method_https = self.bin_dir / 'apt-method-https' - self.ca_certificates = self.bin_dir / 'update-ca-certificates' - # setup directories and files - self.bin_dir.mkdir() - self.keyrings_dir.mkdir() - self.trusted_gpg_dir.mkdir() - (self.keyrings_dir / 'ubuntu-esm-keyring.gpg').write_text('GPG key') - self.make_fake_binary('apt-get') - self.make_fake_binary('apt-method-https') - self.make_fake_binary('update-ca-certificates') - self.make_fake_binary('id', command='echo 0') - self.make_fake_binary('lsb_release', command='echo precise') - - def make_fake_binary(self, binary, command='true'): - path = self.bin_dir / binary - path.write_text('#!/bin/sh\n{}\n'.format(command)) - path.chmod(0o755) - - def script(self, *args): - """Run the script.""" - command = ['./ubuntu-advantage'] - command.extend(args) - path = os.pathsep.join([str(self.bin_dir), os.environ['PATH']]) - env = { - 'PATH': path, - 'REPO_LIST': str(self.repo_list), - 'KEYRINGS_DIR': str(self.keyrings_dir), - 'APT_KEYS_DIR': str(self.trusted_gpg_dir), - 'APT_METHOD_HTTPS': str(self.apt_method_https), - 'CA_CERTIFICATES': str(self.ca_certificates)} - process = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) - process.wait() - result = ProcessResult( - process.returncode, - process.stdout.read().decode('utf-8'), - process.stderr.read().decode('utf-8')) - process.stdout.close() - process.stderr.close() - return result - - def test_run_not_as_root(self): - """The script must be run as root.""" - self.make_fake_binary('id', command='echo 100') - process = self.script('enable-esm', 'user:pass') - self.assertEqual(2, process.returncode) - self.assertIn('This command must be run as root', process.stderr) - - def test_usage(self): - """Calling the script with no args prints out the usage.""" - process = self.script() - self.assertEqual(1, process.returncode) - self.assertIn('usage: ubuntu-advantage', process.stderr) - - def test_enable(self): - """The script enables the ESM repository.""" - process = self.script('enable-esm', 'user:pass') - self.assertEqual(0, process.returncode) - self.assertIn('Ubuntu ESM repository enabled', process.stdout) - expected = ( - 'deb https://user:pass@esm.ubuntu.com/ubuntu precise main\n' - '# deb-src https://user:pass@esm.ubuntu.com/ubuntu precise main\n') - self.assertEqual(expected, self.repo_list.read_text()) - keyring_file = self.trusted_gpg_dir / 'ubuntu-esm-keyring.gpg' - self.assertEqual('GPG key', keyring_file.read_text()) - # the apt-transport-https dependency is already installed - self.assertNotIn( - 'Installing missing dependency apt-transport-https', - process.stdout) - - def test_enable_install_apt_transport_https(self): - """The apt-transport-https package is installed if it's not.""" - self.apt_method_https.unlink() - process = self.script('enable-esm', 'user:pass') - self.assertEqual(0, process.returncode) - self.assertIn( - 'Installing missing dependency apt-transport-https', - process.stdout) - - def test_enable_install_apt_transport_https_fails(self): - """Stderr is printed if apt-transport-https install fails.""" - self.apt_method_https.unlink() - self.make_fake_binary('apt-get', command='echo failed >&2; false') - process = self.script('enable-esm', 'user:pass') - self.assertEqual(1, process.returncode) - self.assertIn('failed', process.stderr) - - def test_enable_install_ca_certificates(self): - """The ca-certificates package is installed if it's not.""" - self.ca_certificates.unlink() - process = self.script('enable-esm', 'user:pass') - self.assertEqual(0, process.returncode) - self.assertIn( - 'Installing missing dependency ca-certificates', - process.stdout) - - def test_enable_install_ca_certificates__fails(self): - """Stderr is printed if ca-certificates install fails.""" - self.ca_certificates.unlink() - self.make_fake_binary('apt-get', command='echo failed >&2; false') - process = self.script('enable-esm', 'user:pass') - self.assertEqual(1, process.returncode) - self.assertIn('failed', process.stderr) - - def test_enable_missing_token(self): - """The token must be specified when enabling the repository.""" - process = self.script('enable-esm') - self.assertEqual(3, process.returncode) - self.assertIn( - 'Invalid token, it must be in the form "user:password"', - process.stderr) - - def test_enable_invalid_token(self): - """The token must be specified as "user:password".""" - process = self.script('enable-esm', 'foo-bar') - self.assertEqual(3, process.returncode) - self.assertIn( - 'Invalid token, it must be in the form "user:password"', - process.stderr) - - def test_disable(self): - """The script disables the ESM repository.""" - self.script('enable-esm', 'user:pass') - process = self.script('disable-esm') - self.assertEqual(0, process.returncode) - self.assertIn('Ubuntu ESM repository disabled', process.stdout) - self.assertFalse(self.repo_list.exists()) - # the keyring file is removed - keyring_file = self.trusted_gpg_dir / 'ubuntu-esm-keyring.gpg' - self.assertFalse(keyring_file.exists()) - - def test_disable_disabled(self): - """If the repo is not enabled, disabling is a no-op.""" - process = self.script('disable-esm') - self.assertEqual(0, process.returncode) - self.assertIn('Ubuntu ESM repository was not enabled', process.stdout) - - def test_is_esm_enabled_true(self): - """is-esm-enabled returns 0 if the repository is enabled.""" - self.make_fake_binary('apt-cache', command='echo esm.ubuntu.com') - process = self.script('is-esm-enabled') - self.assertEqual(0, process.returncode) - - def test_is_esm_enabled_false(self): - """is-esm-enabled returns 1 if the repository is not enabled.""" - self.make_fake_binary('apt-cache') - process = self.script('is-esm-enabled') - self.assertEqual(1, process.returncode) diff -Nru ubuntu-advantage-tools-2/tox.ini ubuntu-advantage-tools-10~ubuntu0.16.04.1/tox.ini --- ubuntu-advantage-tools-2/tox.ini 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/tox.ini 2017-09-19 18:58:30.000000000 -0300 @@ -4,7 +4,8 @@ [testenv] commands = - {envpython} -m unittest discover . - {envpython} -m flake8 --exclude .tox . + {envpython} -m unittest discover tests + {envpython} -m flake8 tests deps = - -r{toxinidir}/requirements.txt + flake8 + fixtures diff -Nru ubuntu-advantage-tools-2/.travis.yml ubuntu-advantage-tools-10~ubuntu0.16.04.1/.travis.yml --- ubuntu-advantage-tools-2/.travis.yml 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/.travis.yml 2017-09-19 18:58:30.000000000 -0300 @@ -1,5 +1,12 @@ language: python python: - "3.5" +dist: precise install: pip install tox -script: tox +script: make lint test +addons: + apt: + sources: + - debian-sid + packages: + - shellcheck diff -Nru ubuntu-advantage-tools-2/ubuntu-advantage ubuntu-advantage-tools-10~ubuntu0.16.04.1/ubuntu-advantage --- ubuntu-advantage-tools-2/ubuntu-advantage 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/ubuntu-advantage 2017-09-19 18:58:30.000000000 -0300 @@ -1,62 +1,318 @@ #!/bin/sh -e +# shellcheck disable=SC2039 SCRIPTNAME=$(basename "$0") -SERIES=$(lsb_release -cs) +ESM_SUPPORTED_SERIES="precise" +LIVEPATCH_SUPPORTED_SERIES="trusty xenial" +FIPS_SUPPORTED_SERIES="xenial" + +SERIES=${SERIES:-$(lsb_release -cs)} +KERNEL_VERSION=${KERNEL_VERSION:-$(uname -r)} +ARCH=${ARCH:-$(uname -m)} + +FIPS_REPO_URL="private-ppa.launchpad.net/ubuntu-advantage/fips" +FIPS_REPO_KEY_FILE="ubuntu-fips-keyring.gpg" +FIPS_REPO_LIST=${FIPS_REPO_LIST:-"/etc/apt/sources.list.d/ubuntu-fips-${SERIES}.list"} REPO_URL="esm.ubuntu.com" REPO_KEY_FILE="ubuntu-esm-keyring.gpg" REPO_LIST=${REPO_LIST:-"/etc/apt/sources.list.d/ubuntu-esm-${SERIES}.list"} KEYRINGS_DIR=${KEYRINGS_DIR:-"/usr/share/keyrings"} APT_KEYS_DIR=${APT_KEYS_DIR:-"/etc/apt/trusted.gpg.d"} -APT_METHOD_HTTPS=${APT_METHOD_HTTPS:="/usr/lib/apt/methods/https"} -CA_CERTIFICATES=${CA_CERTIFICATES:="/usr/sbin/update-ca-certificates"} +APT_METHOD_HTTPS=${APT_METHOD_HTTPS:-"/usr/lib/apt/methods/https"} +CA_CERTIFICATES=${CA_CERTIFICATES:-"/usr/sbin/update-ca-certificates"} +SNAPD=${SNAPD:-"/usr/lib/snapd/snapd"} -write_list_file() { +FIPS_ENABLED_FILE=${FIPS_ENABLED_FILE:-"/proc/sys/crypto/fips_enabled"} +FSTAB=${FSTAB:-"/etc/fstab"} +if [ "$ARCH" = "s390x" ]; then + FIPS_BOOT_CFG=${FIPS_BOOT_CFG:-"/etc/zipl.conf"} +else + FIPS_BOOT_CFG_DIR=${FIPS_BOOT_CFG_DIR:-"/etc/default/grub.d"} + FIPS_BOOT_CFG=${FIPS_BOOT_CFG:-"${FIPS_BOOT_CFG_DIR}/99-fips.cfg"} +fi + +check_result() { + local result output + result=0 + output=$("$@" 2>&1) || result=$? + if [ $result -ne 0 ]; then + echo "ERROR" + if [ -n "$output" ]; then + echo "$output" >&2 + fi + exit $result + else + echo "OK" + fi +} + +apt_get() { + DEBIAN_FRONTEND=noninteractive \ + apt-get -y -o Dpkg::Options::='--force-confold' "$@" +} + + +is_package_installed() { + dpkg-query -s "$1" > /dev/null 2>&1 +} + + +# Install a package if the specified file doesn't exist +install_package_if_missing_file() { + local file="$1" + local package="$2" + + if [ ! -f "$file" ]; then + echo -n "Installing missing dependency $package... " + check_result apt_get install "$package" + fi +} + + +# Whether the current series is among supported ones. +is_supported_series() { + local s + for s in $1; do + if [ "$s" = "$SERIES" ]; then + return 0 + fi + done + return 1 +} + + +install_livepatch_prereqs() { + install_package_if_missing_file "$SNAPD" snapd + if ! snap list canonical-livepatch >/dev/null 2>&1; then + echo 'Installing the canonical-livepatch snap.' + echo 'This may take a few minutes depending on your bandwidth.' + # show output as it has a nice progress bar and isn't too verbose + snap install canonical-livepatch + fi +} + +# $1: livepatch token +enable_livepatch() { + install_livepatch_prereqs + if ! is_livepatch_enabled; then + if check_snapd_kernel_support; then + echo 'Enabling Livepatch with the given token, stand by...' + canonical-livepatch enable "$1" + else + echo + echo "Your currently running kernel ($KERNEL_VERSION) is too old to" + echo "support snaps. Version 4.4.0 or higher is needed." + echo + echo "Please reboot your system into a supported kernel version" + echo "and run the following command one more time to complete the" + echo "installation:" + echo + echo "sudo ubuntu-advantage enable-livepatch $1" + exit 5 + fi + else + echo 'Livepatch already enabled.' + fi + echo 'Use "canonical-livepatch status" to verify current patch status.' +} + +disable_livepatch() { + if is_livepatch_enabled; then + echo 'Disabling Livepatch...' + canonical-livepatch disable + if [ "$1" = "yes" ]; then + echo 'Removing the canonical-livepatch snap...' + snap remove canonical-livepatch + else + echo 'Note: the canonical-livepatch snap is still installed.' + echo 'To remove it, run sudo snap remove canonical-livepatch' + fi + else + echo 'Livepatch is already disabled.' + fi +} + +write_esm_list_file() { cat > "$REPO_LIST" </dev/null - fi - if [ ! -f "$CA_CERTIFICATES" ]; then - echo 'Installing missing dependency ca-certificates' - apt-get install -y ca-certificates >/dev/null - fi - echo 'Running apt-get update...' - apt-get update >/dev/null + write_esm_list_file "$1" + install_package_if_missing_file "$APT_METHOD_HTTPS" apt-transport-https + install_package_if_missing_file "$CA_CERTIFICATES" ca-certificates + echo -n 'Running apt-get update... ' + check_result apt_get update echo 'Ubuntu ESM repository enabled.' } - disable_esm() { if [ -f "$REPO_LIST" ]; then mv "$REPO_LIST" "${REPO_LIST}.save" rm -f "$APT_KEYS_DIR/$REPO_KEY_FILE" - echo 'Running apt-get update...' - apt-get update >/dev/null + echo -n 'Running apt-get update... ' + check_result apt_get update echo 'Ubuntu ESM repository disabled.' else echo 'Ubuntu ESM repository was not enabled.' fi } +fips_prep_check() { + local fips_hmacs pkg + + # sanity check + case "$ARCH" in + s390x|x86_64|ppc64le) ;; + *) + echo "ERROR: $ARCH is not fips supported." + return 1 + ;; + esac + + # check to see if fips pkgs already installed. + fips_hmacs="openssh-client-hmac openssh-server-hmac libssl1.0.0-hmac \ + linux-fips strongswan-hmac" + for pkg in $fips_hmacs; do + if is_package_installed "$pkg"; then + if is_fips_enabled; then + echo 'FIPS is already enabled.' + else + echo 'FIPS is already installed. Please reboot into the FIPS kernel to enable it.' + fi + return 1 + fi + done + + return 0 +} + +configure_fips() { + local bootdev fips_params result + + # if /boot has its own partition, then get the bootdevice + # Note: /boot/efi does not count + bootdev=$(awk '!/^\s*#/ && $2 ~ /^\/boot\/?$/ { print $1 }' "$FSTAB") + fips_params="fips=1" + if [ -n "$bootdev" ]; then + fips_params="$fips_params bootdev=$bootdev" + fi + + if [ "$ARCH" = "s390x" ]; then + sed -i -e 's,^parameters\s*=.*,& '"$fips_params"',' "$FIPS_BOOT_CFG" + echo -n 'Updating zipl to enable fips... ' + check_result zipl + else + result=0 + if [ ! -d "$FIPS_BOOT_CFG_DIR" ]; then + mkdir "$FIPS_BOOT_CFG_DIR" > /dev/null 2>&1 || result=$? + if [ $result -ne 0 ]; then + echo "Failed to make directory, $FIPS_BOOT_CFG_DIR." + return 1 + fi + fi + echo "GRUB_CMDLINE_LINUX_DEFAULT=\"\$GRUB_CMDLINE_LINUX_DEFAULT $fips_params\"" > "$FIPS_BOOT_CFG" + echo -n 'Updating grub to enable fips... ' + check_result update-grub + fi +} + +write_fips_list_file() { + cat > "$FIPS_REPO_LIST" </dev/null 2>&1 +} + +validate_user_pass_token(){ echo "$1" | grep -q '^[^:]\+:[^:]\+$' } +validate_livepatch_token() { + # the livepatch token is an hex string 32 characters long + echo "$1" | grep -q -E '^[0-9a-fA-F]{32}$' +} + +# snapd needs a 4.4.x *running* kernel +check_snapd_kernel_support() { + local v1 v2 + v1=$(echo "$KERNEL_VERSION" | cut -d . -f 1) + v2=$(echo "$KERNEL_VERSION" | cut -d . -f 2) + test "$v1" -ge "4" -a "$v2" -ge "4" +} + +check_esm_support() { + check_service_support "Extended Security Maintenance" "$ESM_SUPPORTED_SERIES" +} + +check_livepatch_support() { + check_service_support "Canonical Livepatch" "$LIVEPATCH_SUPPORTED_SERIES" +} + +check_fips_support() { + check_service_support "Canonical FIPS 140-2 Modules" "$FIPS_SUPPORTED_SERIES" +} + +check_service_support() { + local title="$1" + local supported_series="$2" + + if ! is_supported_series "$supported_series"; then + echo "Sorry, but $title is not supported on $SERIES" >&2 + exit 4 + fi +} check_user() { if [ "$(id -u)" -ne 0 ]; then @@ -66,28 +322,108 @@ } -usage() { - cat >&2 < enable the ESM repository - disable-esm disable the ESM repository + echo + + local fips_status + if is_fips_enabled; then + fips_status="enabled" + else + fips_status="disabled" + is_supported_series "$FIPS_SUPPORTED_SERIES" || fips_status="$fips_status (not available)" + fi + echo "fips: $fips_status" +} -the argument must be in the form "user:password" +usage() { + cat >&2 < [parameters] + +This is a tool that facilitates access to some of Canonical's +Ubuntu Advantage offerings. + +Currently available are: +- Ubuntu Extended Security Maintenance archive (https://ubuntu.com/esm) +- Canonical Livepatch Service (https://www.ubuntu.com/server/livepatch) +- Canonical FIPS 140-2 Certified Modules + +Commands: + status show current status of Ubuntu Advantage offerings + enable-esm enable the ESM repository + disable-esm disable the ESM repository + enable-livepatch enable the Livepatch service + disable-livepatch [-r] disable the Livepatch service. With "-r", the + canonical-livepatch snap will also be removed + enable-fips enable the FIPS PPA repository and install, + configure and enable FIPS certified modules EOF } case "$1" in - enable-esm) + enable-livepatch) + check_user + check_livepatch_support + token="$2" + if ! validate_livepatch_token "$token"; then + echo 'Invalid or missing Livepatch token' >&2 + echo 'Please visit https://ubuntu.com/livepatch to obtain a Livepatch token.' >&2 + exit 3 + fi + enable_livepatch "$token" + ;; + + disable-livepatch) check_user + check_livepatch_support + remove_snap="no" + if [ -n "$2" ]; then + if [ "$2" = "-r" ]; then + remove_snap="yes" + else + echo "Unknown option \"$2\"" >&2 + usage + exit 1 + fi + fi + disable_livepatch "$remove_snap" + ;; + + is-livepatch-enabled) + # no root needed + is_livepatch_enabled + ;; + enable-esm) + check_user + check_esm_support token="$2" - if ! validate_token "$token"; then + if ! validate_user_pass_token "$token"; then echo 'Invalid token, it must be in the form "user:password"' >&2 exit 3 fi @@ -96,13 +432,36 @@ disable-esm) check_user + check_esm_support disable_esm ;; is-esm-enabled) + # no root needed is_esm_enabled ;; + enable-fips) + check_user + check_fips_support + token="$2" + if ! validate_user_pass_token "$token"; then + echo 'Invalid token, it must be in the form "user:password"' >&2 + exit 3 + fi + + enable_fips "$token" + ;; + + is-fips-enabled) + # no root needed + is_fips_enabled + ;; + + status) + print_status + ;; + *) usage exit 1 diff -Nru ubuntu-advantage-tools-2/ubuntu-advantage.1 ubuntu-advantage-tools-10~ubuntu0.16.04.1/ubuntu-advantage.1 --- ubuntu-advantage-tools-2/ubuntu-advantage.1 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/ubuntu-advantage.1 2017-09-19 18:58:30.000000000 -0300 @@ -1,25 +1,88 @@ .TH UBUNTU-ADVANTAGE 1 "28 April 2017" "" "" .SH NAME -ubuntu-advantage \- Enable or disable the Ubuntu Extended Security -Maintenance archive. +ubuntu-advantage \- Enable or disable Ubuntu Advantage offerings from +Canonical. .SH SYNOPSIS .B ubuntu-advantage -[enable-esm |disable-esm] + [parameters] .SH DESCRIPTION -This tool is used to enable or disable the Ubuntu Extended Security -Maintenance archive. +This tool is used to enable or disable specific Ubuntu Advantage offerings +from Canonical. The available modules and their commands are described below. It must be run with root privileges. -.PP -Parameters: -.RS .TP .B -enable-esm -enable the ESM repository. The argument must be in the form -"user:password" +status +Show the status of Ubuntu Advantage offerings. +.SH ESM (Extended Security Maintenance) +Ubuntu Extended Security Maintenance archive. See https://ubuntu.com/esm for +more information. +.TP +.B +enable-esm \fItoken\fR +Enable the ESM repository. The \fItoken\fR argument must be in the form +"user:password". .TP .B disable-esm -disable the ESM repository -.RE -.PP +Disable the ESM repository. + +.SH Livepatch (Canonical Livepatch Service) +Managed live kernel patching. For more information, visit +https://www.ubuntu.com/server/livepatch +.TP +.B +enable-livepatch \fI\fR +Enable the Livepatch service. The \fItoken\fR can be obtained by visiting +https://ubuntu.com/livepatch +.TP +.B +disable-livepatch \fR[\fB\-r\fR] +Disable the Livepatch service. If the \fB\-r\fR option is given, the +canonical-livepatch snap will be removed after the sevice is disabled. + +.SH FIPS (Canonical FIPS 140-2 certified modules) +Install, Configure, and Enable FIPS 140-2 certified modules. +.TP +.B +enable-fips \fItoken\fR +Enable the FIPS PPA repository, install the FIPS modules, configure +the bootloader and enable fips on the system. After successfully executing the +ubuntu-advantage script to enable fips, the system MUST be rebooted to +complete the enablement process. Failing to reboot will result in the system +not being fips enabled. +The \fItoken\fR argument must be in the form "user:password". + +The following FIPS certified modules will be installed and put in fips mode; +openssh-server, openssh-client, strongswan, openssl, and the kernel +cryptoapi. +.SH EXIT STATUS +.TP +.B +0 +Command succeded +.TP +.B +1 +Invalid command or option +.TP +.B +2 +Current user is not root +.TP +.B +3 +Invalid or missing token when enabling a service +.TP +.B +4 +The requested service not supported on the current Ubuntu release +.TP +.B +5 +Current kernel is too old to support Snaps (required for the Livepatch service) +.TP +.B +6 +It was determined that FIPS has already been installed. +.TP +If apt commands run by the tool fail, the exit status from apt is returned. Binary files /tmp/1A4EJrfRy0/ubuntu-advantage-tools-2/ubuntu-esm-keyring.gpg and /tmp/LdovPo2Xgg/ubuntu-advantage-tools-10~ubuntu0.16.04.1/ubuntu-esm-keyring.gpg differ diff -Nru ubuntu-advantage-tools-2/update-motd.d/99-esm ubuntu-advantage-tools-10~ubuntu0.16.04.1/update-motd.d/99-esm --- ubuntu-advantage-tools-2/update-motd.d/99-esm 2017-06-30 18:20:00.000000000 -0300 +++ ubuntu-advantage-tools-10~ubuntu0.16.04.1/update-motd.d/99-esm 2017-09-19 18:58:30.000000000 -0300 @@ -3,9 +3,9 @@ SERIES=$(lsb_release -cs) DESCRIPTION=$(lsb_release -ds) -if [ "$SERIES" != "precise" ]; then - exit 0 -fi +[ "$SERIES" = "precise" ] || exit 0 + +[ -x /usr/bin/ubuntu-advantage ] || exit 0 if ubuntu-advantage is-esm-enabled; then cat <