def action_status(args, cfg): if not cfg: cfg = config.UAConfig() if not cfg.is_attached: print(ua_status.MESSAGE_UNATTACHED) return contractInfo = cfg.machine_token['machineTokenInfo']['contractInfo'] expiry = datetime.strptime(contractInfo['effectiveTo'], '%Y-%m-%dT%H:%M:%SZ') status_content = [] support_entitlement = cfg.entitlements.get('support') tech_support = ua_status.COMMUNITY if support_entitlement: tech_support = support_entitlement.get('affordances', {}).get('supportLevel', ua_status.COMMUNITY) tech_support_txt = ua_status.STATUS_COLOR.get( tech_support) or ua_status.STATUS_COLOR[ua_status.COMMUNITY] account = cfg.accounts[0] status_content.append( STATUS_HEADER_TMPL.format(account=account['name'], subscription=contractInfo['name'], contract_expiry=expiry.date(), tech_support_level=tech_support_txt)) for ent_cls in entitlements.ENTITLEMENT_CLASSES: ent = ent_cls(cfg) status_content.append(ua_status.format_entitlement_status(ent)) status_content.append('\nEnable entitlements with `ua enable <service>`\n') print('\n'.join(status_content))
def test_can_enable_false_on_entitlement_active(self, m_getuid): """When operational status is ACTIVE, can_enable returns False.""" tmp_dir = self.tmp_dir() cfg = config.UAConfig(cfg={'data_dir': tmp_dir}) machineToken = { 'machineToken': 'blah', 'machineTokenInfo': { 'contractInfo': { 'resourceEntitlements': [{ 'type': 'testconcreteentitlement' }] } } } cfg.write_cache('machine-token', machineToken) cfg.write_cache('machine-access-testconcreteentitlement', {'entitlement': { 'entitled': True }}) entitlement = ConcreteTestEntitlement( cfg, operational_status=(status.ACTIVE, '')) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: self.assertFalse(entitlement.can_enable()) self.assertEqual( 'Test Concrete Entitlement is already enabled.\nSee `ua status`\n', m_stdout.getvalue())
def test_enable_configures_apt_sources_and_auth_files( self, m_getuid, m_platform_info, m_subp): """When entitled, configure apt repo auth token, pinning and url.""" m_platform_info.return_value = 'xenial' tmp_dir = self.tmp_dir() cfg = config.UAConfig(cfg={'data_dir': tmp_dir}) cfg.write_cache('machine-token', CIS_MACHINE_TOKEN) cfg.write_cache('machine-access-cis-audit', CIS_RESOURCE_ENTITLED) entitlement = CISEntitlement(cfg) # Unset static affordance container check entitlement.static_affordances = () with mock.patch('uaclient.apt.add_auth_apt_repo') as m_add_apt: with mock.patch('uaclient.apt.add_ppa_pinning') as m_add_pinning: self.assertTrue(entitlement.enable()) add_apt_calls = [ mock.call('/etc/apt/sources.list.d/ubuntu-cis-audit-xenial.list', 'http://CIS', 'TOKEN', None, 'APTKEY')] subp_apt_cmds = [ mock.call(['apt-get', 'update'], capture=True), mock.call(['apt-get', 'install', 'ubuntu-cisbenchmark-16.04'])] assert add_apt_calls == m_add_apt.call_args_list # No apt pinning for cis-audit assert [] == m_add_pinning.call_args_list assert subp_apt_cmds == m_subp.call_args_list
def test_can_disable_false_on_unentitled(self, m_getuid): """When entitlement contract is not enabled, can_disable is False.""" tmp_dir = self.tmp_dir() cfg = config.UAConfig(cfg={'data_dir': tmp_dir}) machineToken = { 'machineToken': 'blah', 'machineTokenInfo': { 'contractInfo': { 'resourceEntitlements': [{ 'type': 'testconcreteentitlement' }] } } } cfg.write_cache('machine-token', machineToken) cfg.write_cache('machine-access-testconcreteentitlement', {'entitlement': { 'entitled': False }}) entitlement = ConcreteTestEntitlement( cfg, operational_status=(status.INACTIVE, '')) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: self.assertFalse(entitlement.can_disable()) self.assertEqual( 'This subscription is not entitled to Test Concrete Entitlement.\n' 'See `ua status` or https://ubuntu.com/advantage\n\n', m_stdout.getvalue())
def test_no_apt_config_removed_when_upgraded_from_trusty_to_xenial( self, m_add_apt, m_unlink, tmpdir): """No apt config when connected but no entitlements enabled.""" # Make CC resource access report not entitled cc_unentitled = copy.deepcopy(dict(CC_RESOURCE_ENTITLED)) cc_unentitled['entitlement']['entitled'] = False cfg = config.UAConfig({'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(CC_MACHINE_TOKEN)) cfg.write_cache('machine-access-cc', cc_unentitled) orig_exists = os.path.exists apt_files = ['/etc/apt/sources.list.d/ubuntu-cc-trusty.list'] def fake_apt_list_exists(path): if path in apt_files: return True return orig_exists(path) with mock.patch('uaclient.apt.os.path.exists') as m_exists: m_exists.side_effect = fake_apt_list_exists migrate_apt_sources( cfg=cfg, platform_info={'series': 'xenial', 'release': '16.04'}) assert [] == m_add_apt.call_args_list # Only exists checks for for cfg.is_attached and can_enable assert [] == m_unlink.call_args_list # remove nothing assert [] == m_exists.call_args_list
def action_status(args, cfg): if not cfg: cfg = config.UAConfig() status = cfg.status() active_value = ua_status.UserFacingConfigStatus.ACTIVE.value config_active = bool(status["configStatus"] == active_value) if args and args.wait and config_active: while status["configStatus"] == active_value: print(".", end="") time.sleep(1) status = cfg.status() print("") if args and args.format == "json": if status["expires"] != ua_status.UserFacingStatus.INAPPLICABLE.value: status["expires"] = str(status["expires"]) print(json.dumps(status)) else: show_beta = args.all if args else False output = ua_status.format_tabular(cfg.status(show_beta)) # Replace our Unicode dash with an ASCII dash if we aren't going to be # writing to a utf-8 output; see # https://github.com/CanonicalLtd/ubuntu-advantage-client/issues/859 if ( sys.stdout.encoding is None or "UTF-8" not in sys.stdout.encoding.upper() ): output = output.replace("\u2014", "-") print(output) return 0
def factory( *, entitled: bool, applicability_status: "Tuple[status.ApplicabilityStatus, str]" = None, application_status: "Tuple[status.ApplicationStatus, str]" = None ) -> ConcreteTestEntitlement: cfg = config.UAConfig(cfg={"data_dir": tmpdir.strpath}) machineToken = { "machineToken": "blah", "machineTokenInfo": { "contractInfo": { "resourceEntitlements": [{ "type": "testconcreteentitlement", "entitled": entitled, }] } }, } cfg.write_cache("machine-token", machineToken) cfg.write_cache( "machine-access-testconcreteentitlement", {"entitlement": { "entitled": entitled }}, ) return ConcreteTestEntitlement( cfg, applicability_status=applicability_status, application_status=application_status, )
def test_enable_configures_apt_sources_and_auth_files( self, m_getuid, m_platform_info): """When entitled, configure apt repo auth token, pinning and url.""" m_platform_info.return_value = 'xenial' tmp_dir = self.tmp_dir() cfg = config.UAConfig(cfg={'data_dir': tmp_dir}) cfg.write_cache('machine-token', FIPS_MACHINE_TOKEN) cfg.write_cache('machine-access-fips', FIPS_RESOURCE_ENTITLED) entitlement = FIPSEntitlement(cfg) # Unset static affordance container check entitlement.static_affordances = () with mock.patch('uaclient.apt.add_auth_apt_repo') as m_add_apt: with mock.patch('uaclient.apt.add_ppa_pinning') as m_add_pinning: self.assertTrue(entitlement.enable()) add_apt_calls = [ mock.call('/etc/apt/sources.list.d/ubuntu-fips-xenial.list', 'http://FIPS', 'TOKEN', None, 'APTKEY') ] apt_pinning_calls = [ mock.call('/etc/apt/preferences.d/ubuntu-fips-xenial', 'http://FIPS', 1001) ] assert add_apt_calls == m_add_apt.call_args_list assert apt_pinning_calls == m_add_pinning.call_args_list
def test_can_disable_requires_root(self, m_getuid): """Non-root users receive False from UAEntitlement.can_disable.""" cfg = config.UAConfig(cfg={}) entitlement = ConcreteTestEntitlement(cfg) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: self.assertFalse(entitlement.can_disable()) self.assertEqual('This command must be run as root (try using sudo)\n', m_stdout.getvalue())
def __init__(self, cfg=None): """Setup UAEntitlement instance @param config: Parsed configuration dictionary """ if not cfg: cfg = config.UAConfig() self.cfg = cfg
def test_contract_status_entitled(self, tmpdir): """The contract_status returns ENTITLED when entitled is True.""" cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(LIVEPATCH_MACHINE_TOKEN)) cfg.write_cache('machine-access-livepatch', dict(LIVEPATCH_RESOURCE_ENTITLED)) entitlement = LivepatchEntitlement(cfg) assert status.ENTITLED == entitlement.contract_status()
def action_status(args, cfg): if not cfg: cfg = config.UAConfig() if args and args.format == 'json': status = cfg.status() if status['expires'] != ua_status.INAPPLICABLE: status['expires'] = str(status['expires']) print(json.dumps(status)) else: print(ua_status.format_tabular(cfg.status()))
def entitlement(tmpdir): """ A pytest fixture to create a RepoTestEntitlement with some default config (Uses the tmpdir fixture for the underlying config cache.) """ cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(REPO_MACHINE_TOKEN)) cfg.write_cache('machine-access-repotest', dict(REPO_RESOURCE_ENTITLED)) return RepoTestEntitlement(cfg)
def action_status(args, cfg): if not cfg: cfg = config.UAConfig() if args and args.format == "json": status = cfg.status() if status["expires"] != ua_status.UserFacingStatus.INAPPLICABLE.value: status["expires"] = str(status["expires"]) print(json.dumps(status)) else: print(ua_status.format_tabular(cfg.status())) return 0
def test_can_disable_false_on_unattached_machine(self, m_getuid): """An unattached machine will return False from can_disable.""" tmp_dir = self.tmp_dir() cfg = config.UAConfig(cfg={'data_dir': tmp_dir}) entitlement = ConcreteTestEntitlement(cfg) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: self.assertFalse(entitlement.can_disable()) self.assertEqual( 'This machine is not attached to a UA subscription.\n' 'See `ua attach` or https://ubuntu.com/advantage\n\n', m_stdout.getvalue())
def test_contract_status_unentitled(self, tmpdir): """The contract_status returns NONE when entitled is False.""" livepatch_unentitled = copy.deepcopy(dict(LIVEPATCH_RESOURCE_ENTITLED)) # Make livepatch resource access report not entitled livepatch_unentitled['entitlement']['entitled'] = False cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(LIVEPATCH_MACHINE_TOKEN)) cfg.write_cache('machine-access-livepatch', livepatch_unentitled) entitlement = LivepatchEntitlement(cfg) assert status.NONE == entitlement.contract_status()
def __init__( self, cfg: "Optional[config.UAConfig]" = None, assume_yes: bool = False ) -> None: """Setup UAEntitlement instance @param config: Parsed configuration dictionary """ if not cfg: cfg = config.UAConfig() self.cfg = cfg self.assume_yes = assume_yes
def test_apt_config_migrated_when_enabled_upgraded_from_trusty_to_xenial( self, m_add_apt, m_unlink, m_platform_info, m_subp, tmpdir): """Apt config is migrated when connected and entitlement is enabled.""" cfg = config.UAConfig({'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(CC_MACHINE_TOKEN)) cfg.write_cache('machine-access-cc', dict(CC_RESOURCE_ENTITLED)) orig_exists = os.path.exists glob_files = [ '/etc/apt/sources.list.d/ubuntu-cc-trusty.list', '/etc/apt/sources.list.d/ubuntu-cc-xenial.list' ] def fake_platform_info(key=None): platform_data = { 'arch': 'x86_64', 'series': 'xenial', 'release': '16.04' } if key: return platform_data[key] return platform_data def fake_apt_list_exists(path): if path in glob_files: return True return orig_exists(path) def fake_glob(regex): if regex == '/etc/apt/sources.list.d/ubuntu-cc-*.list': return glob_files return [] repo_url = CC_RESOURCE_ENTITLED['entitlement']['directives']['aptURL'] m_platform_info.side_effect = fake_platform_info m_subp.return_value = '500 %s' % repo_url, '' with mock.patch('uaclient.apt.glob.glob') as m_glob: with mock.patch('uaclient.apt.os.path.exists') as m_exists: m_glob.side_effect = fake_glob m_exists.side_effect = fake_apt_list_exists assert None is migrate_apt_sources(cfg=cfg) assert [] == m_add_apt.call_args_list # Only exists checks for for cfg.is_attached and can_enable exists_calls = [ mock.call(tmpdir.join('machine-token.json').strpath), mock.call(tmpdir.join('machine-access-cc.json').strpath) ] unlink_calls = [ mock.call('/etc/apt/sources.list.d/ubuntu-cc-trusty.list') ] assert unlink_calls == m_unlink.call_args_list # remove nothing assert exists_calls == m_exists.call_args_list
def test_noop_apt_config_when_not_attached( self, m_add_apt, m_unlink, tmpdir): """Perform not apt config changes when not attached.""" cfg = config.UAConfig({'data_dir': tmpdir.strpath}) assert False is cfg.is_attached with mock.patch('uaclient.apt.os.path.exists') as m_exists: m_exists.return_value = False assert None is migrate_apt_sources( cfg=cfg, platform_info={'series': 'trusty', 'release': '14.04'}) assert [] == m_add_apt.call_args_list assert [] == m_unlink.call_args_list
def test_can_enable_true_on_entitlement_inactive(self, m_getuid): """When operational status is INACTIVE, can_enable returns True.""" tmp_dir = self.tmp_dir() cfg = config.UAConfig(cfg={'data_dir': tmp_dir}) cfg.write_cache('machine-token', FIPS_MACHINE_TOKEN) cfg.write_cache('machine-access-fips', FIPS_RESOURCE_ENTITLED) entitlement = FIPSEntitlement(cfg) # Unset static affordance container check entitlement.static_affordances = () with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: self.assertTrue(entitlement.can_enable()) self.assertEqual('', m_stdout.getvalue())
def entitlement(request, tmpdir): """ pytest fixture for a FIPS/FIPS Updates entitlement with some default config (Uses the tmpdir fixture for the underlying config cache.) """ cls = request.param cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', machine_token(cls.name)) cfg.write_cache('machine-access-{}'.format(cls.name), machine_access(cls.name)) return cls(cfg)
def test_enable_does_not_install_livepatch_snap_when_present( self, m_can_enable, m_which, m_subp, tmpdir): """Do not attempt to install livepatch snap when it is present.""" cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(LIVEPATCH_MACHINE_TOKEN)) cfg.write_cache('machine-access-livepatch', dict(LIVEPATCH_RESOURCE_ENTITLED)) entitlement = LivepatchEntitlement(cfg) with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: assert entitlement.enable() assert self.mocks_config == m_subp.call_args_list assert 'Canonical livepatch enabled.\n' == m_stdout.getvalue()
def test_enable_passes_silent_if_inapplicable_through( self, m_can_enable, caplog_text, tmpdir, silent_if_inapplicable): """When can_enable returns False enable returns False.""" cfg = config.UAConfig(cfg={"data_dir": tmpdir.strpath}) entitlement = RepoTestEntitlement(cfg) kwargs = {} if silent_if_inapplicable is not None: kwargs["silent_if_inapplicable"] = silent_if_inapplicable entitlement.enable(**kwargs) expected_call = mock.call(silent=bool(silent_if_inapplicable)) assert [expected_call] == m_can_enable.call_args_list
def factory_func(cls, *, affordances=None, directives=None, suites=None): cfg = config.UAConfig(cfg={"data_dir": tmpdir.strpath}) cfg.write_cache("machine-token", machine_token(cls.name)) cfg.write_cache( "machine-access-{}".format(cls.name), machine_access( cls.name, affordances=affordances, directives=directives, suites=suites, ), ) return cls(cfg)
def test_can_enable_true_on_entitlement_inactive(self, m_platform_info, _m_subp, capsys, tmpdir): """When entitlement is INACTIVE, can_enable returns True.""" m_platform_info.return_value = PLATFORM_INFO_SUPPORTED cfg = config.UAConfig(cfg={"data_dir": tmpdir.strpath}) cfg.write_cache("machine-token", CC_MACHINE_TOKEN) entitlement = CommonCriteriaEntitlement(cfg) uf_status, uf_status_details = entitlement.user_facing_status() assert status.UserFacingStatus.INACTIVE == uf_status details = "{} is not configured".format(entitlement.title) assert details == uf_status_details assert True is entitlement.can_enable() assert ("", "") == capsys.readouterr()
def test_can_enable_true_on_entitlement_inactive(self, tmpdir): """When operational status is INACTIVE, can_enable returns True.""" # Unset static affordance container check cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', dict(FIPS_MACHINE_TOKEN)) cfg.write_cache('machine-access-fips', dict(FIPS_RESOURCE_ENTITLED)) entitlement = FIPSEntitlement(cfg) entitlement.static_affordances = () with mock.patch('uaclient.entitlements.base.os.getuid') as m_getuid: with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: m_getuid.return_value = 0 assert True is entitlement.can_enable() assert '' == m_stdout.getvalue()
def test_can_enable_true_on_entitlement_inactive(self, tmpdir): """When operational status is INACTIVE, can_enable returns True.""" cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', CIS_MACHINE_TOKEN) cfg.write_cache('machine-access-cis-audit', CIS_RESOURCE_ENTITLED) entitlement = CISEntitlement(cfg) # Unset static affordance container check entitlement.static_affordances = () with mock.patch.object(entitlement, 'operational_status', return_value=(status.INACTIVE, '')): with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: assert entitlement.can_enable() assert '' == m_stdout.getvalue()
def test_enable_configures_apt_sources_and_auth_files( self, m_platform_info, m_subp, tmpdir): """When entitled, configure apt repo auth token, pinning and url.""" def fake_platform(key=None): info = {'series': 'xenial', 'kernel': '4.15.0-00-generic'} if key: return info[key] return info m_platform_info.side_effect = fake_platform m_subp.return_value = ('fakeout', '') cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', CIS_MACHINE_TOKEN) cfg.write_cache('machine-access-cis-audit', CIS_RESOURCE_ENTITLED) entitlement = CISEntitlement(cfg) # Unset static affordance container check entitlement.static_affordances = () with mock.patch('uaclient.entitlements.repo.os.path.exists', mock.Mock(return_value=True)): with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: with mock.patch('uaclient.apt.add_auth_apt_repo') as m_add_apt: with mock.patch( 'uaclient.apt.add_ppa_pinning') as m_add_pin: assert entitlement.enable() add_apt_calls = [ mock.call( '/etc/apt/sources.list.d/ubuntu-cis-audit-xenial.list', 'http://CIS', 'TOKEN', ['xenial'], '/usr/share/keyrings/ubuntu-securitybenchmarks-keyring.gpg') ] subp_apt_cmds = [ mock.call(['apt-cache', 'policy']), mock.call(['apt-get', 'update'], capture=True), mock.call(['apt-get', 'install', '--assume-yes'] + entitlement.packages, capture=True) ] assert add_apt_calls == m_add_apt.call_args_list # No apt pinning for cis-audit assert [] == m_add_pin.call_args_list assert subp_apt_cmds == m_subp.call_args_list expected_stdout = ( 'Updating package lists ...\n' 'Installing Canonical CIS Benchmark Audit Tool packages ...\n' 'Canonical CIS Benchmark Audit Tool enabled.\n') assert expected_stdout == m_stdout.getvalue()
def main(sys_argv=None): if not sys_argv: sys_argv = sys.argv parser = get_parser() cli_arguments = sys_argv[1:] if not cli_arguments: parser.print_usage() print('Try \'ubuntu-advantage --help\' for more information.') sys.exit(1) args = parser.parse_args(args=cli_arguments) cfg = config.UAConfig() log_level = cfg.log_level console_level = logging.DEBUG if args.debug else logging.INFO setup_logging(console_level, log_level, cfg.log_file) return args.action(args, cfg)
def test_can_enable_true_on_entitlement_inactive(self, m_getuid, m_platform_info, tmpdir): """When operational status is INACTIVE, can_enable returns True.""" m_platform_info.return_value = PLATFORM_INFO_SUPPORTED cfg = config.UAConfig(cfg={'data_dir': tmpdir.strpath}) cfg.write_cache('machine-token', CC_MACHINE_TOKEN) cfg.write_cache('machine-access-cc', CC_RESOURCE_ENTITLED) entitlement = CommonCriteriaEntitlement(cfg) op_status, op_status_details = entitlement.operational_status() assert status.INACTIVE == op_status details = '%s is not configured' % entitlement.title assert details == op_status_details with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: assert True is entitlement.can_enable() assert '' == m_stdout.getvalue()