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