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""" 5Wrapper for D-Bus calls ot the AuthPolicy daemon. 6""" 7 8import logging 9import os 10import sys 11 12import dbus 13 14from autotest_lib.client.common_lib import error 15from autotest_lib.client.common_lib import utils 16 17 18class 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@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