← Back to team overview

cloud-init-dev team mailing list archive

Re: [Merge] ~chad.smith/cloud-init:feature/cc-uaclient into cloud-init:master

 


Diff comments:

> diff --git a/cloudinit/config/cc_ubuntu_advantage.py b/cloudinit/config/cc_ubuntu_advantage.py
> index 5e082bd..1a95766 100644
> --- a/cloudinit/config/cc_ubuntu_advantage.py
> +++ b/cloudinit/config/cc_ubuntu_advantage.py
> @@ -1,145 +1,170 @@
> -# Copyright (C) 2018 Canonical Ltd.
> -#
>  # This file is part of cloud-init. See LICENSE file for license information.
>  
> -"""Ubuntu advantage: manage ubuntu-advantage offerings from Canonical."""
> +"""ubuntu_advantage: Configure Ubuntu Advantage support entitlements"""
>  
> -import sys
>  from textwrap import dedent
>  
> -from cloudinit import log as logging
>  from cloudinit.config.schema import (
>      get_schema_doc, validate_cloudconfig_schema)
> +from cloudinit import log as logging
>  from cloudinit.settings import PER_INSTANCE
> -from cloudinit.subp import prepend_base_command
>  from cloudinit import util
>  
>  
> -distros = ['ubuntu']
> -frequency = PER_INSTANCE
> +UA_URL = 'https://ubuntu.com/advantage'
>  
> -LOG = logging.getLogger(__name__)
> +distros = ['ubuntu']
>  
>  schema = {
>      'id': 'cc_ubuntu_advantage',
> -    'name': 'Ubuntu Advantage',
> -    'title': 'Install, configure and manage ubuntu-advantage offerings',
> +    'name': 'UbuntuAdvantage',
> +    'title': 'Configure Ubuntu Advantage support entitlements',
>      'description': dedent("""\
> -        This module provides configuration options to setup ubuntu-advantage
> -        subscriptions.
> -
> -        .. note::
> -            Both ``commands`` value can be either a dictionary or a list. If
> -            the configuration provided is a dictionary, the keys are only used
> -            to order the execution of the commands and the dictionary is
> -            merged with any vendor-data ubuntu-advantage configuration
> -            provided. If a ``commands`` is provided as a list, any vendor-data
> -            ubuntu-advantage ``commands`` are ignored.
> -
> -        Ubuntu-advantage ``commands`` is a dictionary or list of
> -        ubuntu-advantage commands to run on the deployed machine.
> -        These commands can be used to enable or disable subscriptions to
> -        various ubuntu-advantage products. See 'man ubuntu-advantage' for more
> -        information on supported subcommands.
> -
> -        .. note::
> -           Each command item can be a string or list. If the item is a list,
> -           'ubuntu-advantage' can be omitted and it will automatically be
> -           inserted as part of the command.
> +        Attach machine to an existing Ubuntu Advantage support contract and
> +        enable or disable support entitlements such as livepatch, ESM,
> +        FIPS, FIPS Updates and CIS Audit tools. When attaching a machine to
> +        Advantage, one can either specify explicit entitlements to enable or
> +        rely on the entitlement default behavior. When no 'entitlements' list
> +        is provided, the default behavior enables both livepatch and esm on
> +        supported Ubuntu environments.
> +        When 'entitlements' list is present, any named entitlement will be
> +        enabled and all absent entitlements will remain disabled.
> +        Note when enabling FIPS or FIPS updates a reboot will occur after
> +        installation completes to ensure the machine is running the
> +        FIPS-compliant kernel.
>          """),
>      'distros': distros,
>      'examples': [dedent("""\
> -        # Enable Extended Security Maintenance using your service auth token
> -        ubuntu-advantage:
> -            commands:
> -              00: ubuntu-advantage enable-esm <token>
> +        # Attach the machine to a Ubuntu Advantage support contract with a
> +        # UA user token obtained from %s.
> +        # Default entitlemtents such as livepatch and esm will automatically
> +        # be enabled after detachment because no entitlements were specified.
> +        ubuntu_advantage:
> +          token: <ua_user_token>
> +    """ % UA_URL), dedent("""\
> +        # Attach the machine to an Ubuntu Advantage support contract using
> +        # Ubuntu SSO with optional two-factor authentication. Default
> +        # entitlements such as livepatch and esm will be enabled if applicable.
> +        ubuntu_advantage:
> +          sso_email: <sso_email>
> +          sso_password: <sso_password_hash>
> +          sso_twofactor: <2fa_code>  # if the sso account requires 2fa
>      """), dedent("""\
> -        # Enable livepatch by providing your livepatch token
> +        # Attach the machine to an Ubuntu Advantage support contract enabling
> +        # only fips and entitlements. Entitlementswill only be enabled if
> +        # the environment supports said entitlement. Otherwise warnings will
> +        # be logged for incompatible entitlements specified.
>          ubuntu-advantage:
> -            commands:
> -                00: ubuntu-advantage enable-livepatch <livepatch-token>
> -
> -    """), dedent("""\
> -        # Convenience: the ubuntu-advantage command can be omitted when
> -        # specifying commands as a list and 'ubuntu-advantage' will
> -        # automatically be prepended.
> -        # The following commands are equivalent
> -        ubuntu-advantage:
> -            commands:
> -                00: ['enable-livepatch', 'my-token']
> -                01: ['ubuntu-advantage', 'enable-livepatch', 'my-token']
> -                02: ubuntu-advantage enable-livepatch my-token
> -                03: 'ubuntu-advantage enable-livepatch my-token'
> +          token: <ua_user_token>
> +          entitlements:
> +          - fips
> +          - esm
>      """)],
>      'frequency': PER_INSTANCE,
>      'type': 'object',
>      'properties': {
> -        'ubuntu-advantage': {
> +        'ubuntu_advantage': {
>              'type': 'object',
>              'properties': {
> -                'commands': {
> -                    'type': ['object', 'array'],  # Array of strings or dict
> +                'entitlements': {
> +                    'type': 'array',
>                      'items': {
> -                        'oneOf': [
> -                            {'type': 'array', 'items': {'type': 'string'}},
> -                            {'type': 'string'}]
> +                        'type': 'string',
> +                        'enum': ['esm', 'fips', 'fips-updates', 'livepatch',
> +                                 'cis-audit']

This seems like we're going to collide with a changing ua schema.
I wonder if instead of copying portions of the UA client features, we should leave these out
and install allow users to put in what ever here as it's going to get passed to ua client anyhow
which will report failure.  In the docs we can point to UA docs on what features/functions are 
present?

>                      },
> -                    'additionalItems': False,  # Reject non-string & non-list
> -                    'minItems': 1,
> -                    'minProperties': 1,
> +                    'minItems': 0
> +                },
> +                'sso_email': {
> +                    'type': 'string',
> +                    'description': 'SSO email for the UA account'
> +                },
> +                'sso_password': {
> +                    'type': 'string',
> +                    'description': 'Hashed SSO password for the UA account'
> +                },
> +                'sso_twofactor': {
> +                    'type': 'string',
> +                    'description':
> +                        'Optional Two-factor authentication code if'
> +                        ' required on this UA account'
> +                },
> +                'token': {
> +                    'type': 'string',
> +                    'description': "A user-token obtained from %s." % UA_URL
>                  }
>              },
> -            'additionalProperties': False,  # Reject keys not in schema
> -            'required': ['commands']
> +            'oneOf': [  # Either sso_* credentials or token, but not both
> +                {'required': ['sso_email', 'sso_password'],
> +                 'not': {'required': ['token']}},
> +                {'required': ['token'],
> +                 'not': {'required': [
> +                             'sso_email', 'sso_password', 'sso_twofactor']}}
> +            ],
> +            'minProperties': 1,  # Either token or sso_* creds must be provided
> +            'additionalProperties': False
>          }
>      }
>  }
>  
> -# TODO schema for 'assertions' and 'commands' are too permissive at the moment.
> -# Once python-jsonschema supports schema draft 6 add support for arbitrary
> -# object keys with 'patternProperties' constraint to validate string values.
> -
>  __doc__ = get_schema_doc(schema)  # Supplement python help()
>  
> -UA_CMD = "ubuntu-advantage"
> -
> -
> -def run_commands(commands):
> -    """Run the commands provided in ubuntu-advantage:commands config.
> +LOG = logging.getLogger(__name__)
>  
> -     Commands are run individually. Any errors are collected and reported
> -     after attempting all commands.
>  
> -     @param commands: A list or dict containing commands to run. Keys of a
> -         dict will be used to order the commands provided as dict values.
> -     """
> -    if not commands:
> -        return
> -    LOG.debug('Running user-provided ubuntu-advantage commands')
> -    if isinstance(commands, dict):
> -        # Sort commands based on dictionary key
> -        commands = [v for _, v in sorted(commands.items())]
> -    elif not isinstance(commands, list):
> -        raise TypeError(
> -            'commands parameter was not a list or dict: {commands}'.format(
> -                commands=commands))
> -
> -    fixed_ua_commands = prepend_base_command('ubuntu-advantage', commands)
> -
> -    cmd_failures = []
> -    for command in fixed_ua_commands:
> -        shell = isinstance(command, str)
> -        try:
> -            util.subp(command, shell=shell, status_cb=sys.stderr.write)
> -        except util.ProcessExecutionError as e:
> -            cmd_failures.append(str(e))
> -    if cmd_failures:
> -        msg = (
> -            'Failures running ubuntu-advantage commands:\n'
> -            '{cmd_failures}'.format(
> -                cmd_failures=cmd_failures))
> +def configure_ua(token=None, sso_email=None, sso_password=None,
> +                 sso_twofactor=None, entitlements=None):
> +    """Call ua commandline client to attach or enable entitlements."""
> +    sso_auth = any([sso_email, sso_password, sso_twofactor])
> +    error = None
> +    if not any([token, sso_auth]):
> +        error = ('ubuntu_advantage: either token or sso_email and sso_password'
> +                 ' must be provided')
> +    elif token and sso_auth:
> +        error = ('ubuntu_advantage: token and sso credentials cannot both be'
> +                 ' provided')
> +    elif sso_auth and not all([sso_email, sso_password]):
> +        error = ('ubuntu_advantage: both sso_email and sso_password are'
> +                 ' required')
> +    if error:
> +        LOG.error(error)
> +        raise RuntimeError(error)
> +    attach_cmd = ['ua', 'attach']
> +    if token:
> +        attach_cmd.append(token)
> +    else:
> +        attach_cmd.extend(['--email', sso_email, '--password', sso_password])
> +        if sso_twofactor:
> +            attach_cmd.extend(['--otp', sso_twofactor])
> +
> +    entitlement_cmds = []
> +    if entitlements is not None:  # Entitlements explicitly enabled
> +        attach_cmd.append('--no-auto-enable')
> +        entitlement_cmds.extend(
> +            [['ua', 'enable', name] for name in entitlements])
> +
> +    msg = 'Attaching to Ubuntu Advantage. %s' % ' '.join(attach_cmd)
> +    if sso_password:
> +        msg = msg.replace(sso_password, '<REDACTED>')
> +    LOG.debug(msg)
> +    try:
> +        util.subp(attach_cmd)
> +    except util.ProcessExecutionError as e:
> +        error = str(e)
> +        if sso_password:
> +            error = error.replace(sso_password, '<REDACTED>')
> +        msg = 'Failure attaching ubuntu advantage:\n{error}'.format(
> +            error=error)
>          util.logexc(LOG, msg)
>          raise RuntimeError(msg)
> +    for cmd in entitlement_cmds:
> +        try:
> +            util.subp(cmd, capture=True)
> +        except util.ProcessExecutionError as e:
> +            msg = 'Failure enabling ubuntu advantage:\n{error}'.format(
> +                error=str(e))
> +            util.logexc(LOG, msg)
> +            raise RuntimeError(msg)
>  
>  
>  def maybe_install_ua_tools(cloud):
> @@ -159,14 +184,29 @@ def maybe_install_ua_tools(cloud):
>  
>  
>  def handle(name, cfg, cloud, log, args):
> -    cfgin = cfg.get('ubuntu-advantage')
> -    if cfgin is None:
> +    if 'ubuntu-advantage' in cfg:
> +        msg = ('Deprecated configuration key "ubuntu-advantage" provided.'

do we really want to raise runtime error on deprecated config?
I think logging a warning and doing the ua attach is a better outcome, right?

> +               ' Expected underscore delimited "ubuntu_advantage"')
> +        LOG.error(msg)
> +        raise RuntimeError(msg)
> +    ua_section = cfg.get('ubuntu_advantage')
> +    if ua_section is None:
>          LOG.debug(("Skipping module named %s,"
> -                   " no 'ubuntu-advantage' key in configuration"), name)
> +                   " no 'ubuntu_advantage' configuration found"), name)
>          return
> -
>      validate_cloudconfig_schema(cfg, schema)
> +    if 'commands' in ua_section:
> +        msg = (
> +            'Deprecated configuration "ubuntu_advantage: commands" provided.'
> +            ' Expected "token" or "sso_email" and "sso_password"')
> +        LOG.error(msg)
> +        raise RuntimeError(msg)
> +
>      maybe_install_ua_tools(cloud)
> -    run_commands(cfgin.get('commands', []))
> +    configure_ua(token=ua_section.get('token'),
> +                 sso_email=ua_section.get('sso_email'),
> +                 sso_password=ua_section.get('sso_password'),
> +                 sso_twofactor=ua_section.get('sso_twofactor'),
> +                 entitlements=ua_section.get('entitlements'))
>  
>  # vi: ts=4 expandtab


-- 
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/362161
Your team cloud-init commiters is requested to review the proposed merge of ~chad.smith/cloud-init:feature/cc-uaclient into cloud-init:master.


References