Home | History | Annotate | Download | only in cros
      1 # Copyright 2017 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 """
      5 Wrapper for D-Bus calls ot the AuthPolicy daemon.
      6 """
      7 
      8 import logging
      9 import os
     10 import sys
     11 
     12 import dbus
     13 
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.client.common_lib import utils
     16 from autotest_lib.client.cros import upstart
     17 
     18 
     19 class AuthPolicy(object):
     20     """
     21     Wrapper for D-Bus calls ot the AuthPolicy daemon.
     22 
     23     The AuthPolicy daemon handles Active Directory domain join, user
     24     authentication and policy fetch. This class is a wrapper around the D-Bus
     25     interface to the daemon.
     26 
     27     """
     28 
     29     # Log file written by authpolicyd.
     30     _LOG_FILE = '/var/log/authpolicy.log'
     31 
     32     # Number of log lines to include in error logs.
     33     _LOG_LINE_LIMIT = 50
     34 
     35     # The usual system log file (minijail logs there!).
     36     _SYSLOG_FILE = '/var/log/messages'
     37 
     38     # Authpolicy daemon D-Bus parameters.
     39     _DBUS_SERVICE_NAME = 'org.chromium.AuthPolicy'
     40     _DBUS_SERVICE_PATH = '/org/chromium/AuthPolicy'
     41     _DBUS_INTERFACE_NAME = 'org.chromium.AuthPolicy'
     42     _DBUS_ERROR_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
     43 
     44     # Default timeout in seconds for D-Bus calls.
     45     _DEFAULT_TIMEOUT = 120
     46 
     47     def __init__(self, bus_loop, proto_binding_location):
     48         """
     49         Constructor
     50 
     51         Creates and returns a D-Bus connection to authpolicyd. The daemon must
     52         be running.
     53 
     54         @param bus_loop: glib main loop object.
     55         @param proto_binding_location: the location of generated python bindings
     56                                        for authpolicy protobufs.
     57         """
     58 
     59         # Pull in protobuf bindings.
     60         sys.path.append(proto_binding_location)
     61 
     62         self._bus_loop = bus_loop
     63         self.restart()
     64 
     65     def restart(self):
     66         """
     67         Restarts authpolicyd and rebinds to D-Bus interface.
     68         """
     69         logging.info('restarting authpolicyd')
     70         upstart.restart_job('authpolicyd')
     71         bus = dbus.SystemBus(self._bus_loop)
     72         proxy = bus.get_object(self._DBUS_SERVICE_NAME,
     73                                self._DBUS_SERVICE_PATH)
     74         self._authpolicyd = dbus.Interface(proxy, self._DBUS_INTERFACE_NAME)
     75 
     76     def stop(self):
     77         """
     78         Turns debug logs off.
     79 
     80         Stops authpolicyd.
     81         """
     82         logging.info('stopping authpolicyd')
     83 
     84         # Reset log level and stop. Ignore errors that occur when authpolicy is
     85         # already down.
     86         try:
     87             self.set_default_log_level(0)
     88         except dbus.exceptions.DBusException as ex:
     89             if ex.get_dbus_name() != self._DBUS_ERROR_SERVICE_UNKNOWN:
     90                 raise
     91         try:
     92             upstart.stop_job('authpolicyd')
     93         except error.CmdError as ex:
     94             if (ex.result_obj.exit_status == 0):
     95                 raise
     96 
     97         self._authpolicyd = None
     98 
     99     def join_ad_domain(self,
    100                        user_principal_name,
    101                        password,
    102                        machine_name,
    103                        machine_domain=None,
    104                        machine_ou=None):
    105         """
    106         Joins a machine (=device) to an Active Directory domain.
    107 
    108         @param user_principal_name: Logon name of the user (with @realm) who
    109             joins the machine to the domain.
    110         @param password: Password corresponding to user_principal_name.
    111         @param machine_name: Netbios computer (aka machine) name for the joining
    112             device.
    113         @param machine_domain: Domain (realm) the machine should be joined to.
    114             If not specified, the machine is joined to the user's realm.
    115         @param machine_ou: Array of organizational units (OUs) from leaf to
    116             root. The machine is put into the leaf OU. If not specified, the
    117             machine account is created in the default 'Computers' OU.
    118 
    119         @return A tuple with the ErrorType and the joined domain returned by the
    120             D-Bus call.
    121 
    122         """
    123 
    124         from active_directory_info_pb2 import JoinDomainRequest
    125 
    126         request = JoinDomainRequest()
    127         request.user_principal_name = user_principal_name
    128         request.machine_name = machine_name
    129         if machine_ou:
    130             request.machine_ou.extend(machine_ou)
    131         if machine_domain:
    132             request.machine_domain = machine_domain
    133 
    134         with self.PasswordFd(password) as password_fd:
    135             return self._authpolicyd.JoinADDomain(
    136                     dbus.ByteArray(request.SerializeToString()),
    137                     dbus.types.UnixFd(password_fd),
    138                     timeout=self._DEFAULT_TIMEOUT,
    139                     byte_arrays=True)
    140 
    141     def authenticate_user(self, user_principal_name, account_id, password):
    142         """
    143         Authenticates a user with an Active Directory domain.
    144 
    145         @param user_principal_name: User logon name (user (at] example.com) for the
    146             Active Directory domain.
    147         #param account_id: User account id (aka objectGUID). May be empty.
    148         @param password: Password corresponding to user_principal_name.
    149 
    150         @return A tuple with the ErrorType and an ActiveDirectoryAccountInfo
    151                 blob string returned by the D-Bus call.
    152 
    153         """
    154 
    155         from active_directory_info_pb2 import ActiveDirectoryAccountInfo
    156         from active_directory_info_pb2 import AuthenticateUserRequest
    157         from active_directory_info_pb2 import ERROR_NONE
    158 
    159         request = AuthenticateUserRequest()
    160         request.user_principal_name = user_principal_name
    161         if account_id:
    162             request.account_id = account_id
    163 
    164         with self.PasswordFd(password) as password_fd:
    165             error_value, account_info_blob = self._authpolicyd.AuthenticateUser(
    166                     dbus.ByteArray(request.SerializeToString()),
    167                     dbus.types.UnixFd(password_fd),
    168                     timeout=self._DEFAULT_TIMEOUT,
    169                     byte_arrays=True)
    170             account_info = ActiveDirectoryAccountInfo()
    171             if error_value == ERROR_NONE:
    172                 account_info.ParseFromString(account_info_blob)
    173             return error_value, account_info
    174 
    175     def refresh_user_policy(self, account_id):
    176         """
    177         Fetches user policy and sends it to Session Manager.
    178 
    179         @param account_id: User account ID (aka objectGUID).
    180 
    181         @return ErrorType from the D-Bus call.
    182 
    183         """
    184 
    185         return self._authpolicyd.RefreshUserPolicy(
    186                 dbus.String(account_id),
    187                 timeout=self._DEFAULT_TIMEOUT,
    188                 byte_arrays=True)
    189 
    190     def refresh_device_policy(self):
    191         """
    192         Fetches device policy and sends it to Session Manager.
    193 
    194         @return ErrorType from the D-Bus call.
    195 
    196         """
    197 
    198         return self._authpolicyd.RefreshDevicePolicy(
    199                 timeout=self._DEFAULT_TIMEOUT, byte_arrays=True)
    200 
    201     def change_machine_password(self):
    202         """
    203         Changes machine password.
    204 
    205         @return ErrorType from the D-Bus call.
    206 
    207         """
    208         return self._authpolicyd.ChangeMachinePasswordForTesting(
    209                 timeout=self._DEFAULT_TIMEOUT, byte_arrays=True)
    210 
    211     def set_default_log_level(self, level):
    212         """
    213         Fetches device policy and sends it to Session Manager.
    214 
    215         @param level: Log level, 0=quiet, 1=taciturn, 2=chatty, 3=verbose.
    216 
    217         @return error_message: Error message, empty if no error occurred.
    218 
    219         """
    220 
    221         return self._authpolicyd.SetDefaultLogLevel(level, byte_arrays=True)
    222 
    223     def print_log_tail(self):
    224         """
    225         Prints out authpolicyd log tail. Catches and prints out errors.
    226 
    227         """
    228 
    229         try:
    230             cmd = 'tail -n %s %s' % (self._LOG_LINE_LIMIT, self._LOG_FILE)
    231             log_tail = utils.run(cmd).stdout
    232             logging.info('Tail of %s:\n%s', self._LOG_FILE, log_tail)
    233         except error.CmdError as ex:
    234             logging.error('Failed to print authpolicyd log tail: %s', ex)
    235 
    236     def print_seccomp_failure_info(self):
    237         """
    238         Detects seccomp failures and prints out the failing syscall.
    239 
    240         """
    241 
    242         # Exit code 253 is minijail's marker for seccomp failures.
    243         cmd = 'grep -q "exit code 253" %s' % self._LOG_FILE
    244         if utils.run(cmd, ignore_status=True).exit_status == 0:
    245             logging.error('Seccomp failure detected!')
    246             cmd = 'grep -oE "blocked syscall: \\w+" %s | tail -1' % \
    247                     self._SYSLOG_FILE
    248             try:
    249                 logging.error(utils.run(cmd).stdout)
    250                 logging.error(
    251                         'This can happen if you changed a dependency of '
    252                         'authpolicyd. Consider whitelisting this syscall in '
    253                         'the appropriate -seccomp.policy file in authpolicyd.'
    254                         '\n')
    255             except error.CmdError as ex:
    256                 logging.error(
    257                         'Failed to determine reason for seccomp issue: %s', ex)
    258 
    259     def clear_log(self):
    260         """
    261         Clears the authpolicy daemon's log file.
    262 
    263         """
    264 
    265         try:
    266             utils.run('echo "" > %s' % self._LOG_FILE)
    267         except error.CmdError as ex:
    268             logging.error('Failed to clear authpolicyd log file: %s', ex)
    269 
    270     class PasswordFd(object):
    271         """
    272         Writes password into a file descriptor.
    273 
    274         Use in a 'with' statement to automatically close the returned file
    275         descriptor.
    276 
    277         @param password: Plaintext password string.
    278 
    279         @return A file descriptor (pipe) containing the password.
    280 
    281         """
    282 
    283         def __init__(self, password):
    284             self._password = password
    285             self._read_fd = None
    286 
    287         def __enter__(self):
    288             """Creates the password file descriptor."""
    289             self._read_fd, write_fd = os.pipe()
    290             os.write(write_fd, self._password)
    291             os.close(write_fd)
    292             return self._read_fd
    293 
    294         def __exit__(self, mytype, value, traceback):
    295             """Closes the password file descriptor again."""
    296             if self._read_fd:
    297                 os.close(self._read_fd)
    298