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