blob: 68760ad2cec3f594076df8e8955441e48d47faa4 [file] [log] [blame]
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Wrapper for D-Bus calls ot the AuthPolicy daemon.
"""
import logging
import os
import sys
import dbus
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import utils
from autotest_lib.client.cros import upstart
class AuthPolicy(object):
"""
Wrapper for D-Bus calls ot the AuthPolicy daemon.
The AuthPolicy daemon handles Active Directory domain join, user
authentication and policy fetch. This class is a wrapper around the D-Bus
interface to the daemon.
"""
# Log file written by authpolicyd.
_LOG_FILE = '/var/log/authpolicy.log'
# Number of log lines to include in error logs.
_LOG_LINE_LIMIT = 50
# The usual system log file (minijail logs there!).
_SYSLOG_FILE = '/var/log/messages'
# Authpolicy daemon D-Bus parameters.
_DBUS_SERVICE_NAME = 'org.chromium.AuthPolicy'
_DBUS_SERVICE_PATH = '/org/chromium/AuthPolicy'
_DBUS_INTERFACE_NAME = 'org.chromium.AuthPolicy'
_DBUS_ERROR_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
# Default timeout in seconds for D-Bus calls.
_DEFAULT_TIMEOUT = 120
def __init__(self, bus_loop, proto_binding_location):
"""
Constructor
Creates and returns a D-Bus connection to authpolicyd. The daemon must
be running.
@param bus_loop: glib main loop object.
@param proto_binding_location: the location of generated python bindings
for authpolicy protobufs.
"""
# Pull in protobuf bindings.
sys.path.append(proto_binding_location)
self._bus_loop = bus_loop
self.restart()
def restart(self):
"""
Restarts authpolicyd and rebinds to D-Bus interface.
"""
logging.info('restarting authpolicyd')
upstart.restart_job('authpolicyd')
bus = dbus.SystemBus(self._bus_loop)
proxy = bus.get_object(self._DBUS_SERVICE_NAME,
self._DBUS_SERVICE_PATH)
self._authpolicyd = dbus.Interface(proxy, self._DBUS_INTERFACE_NAME)
def stop(self):
"""
Turns debug logs off.
Stops authpolicyd.
"""
logging.info('stopping authpolicyd')
# Reset log level and stop. Ignore errors that occur when authpolicy is
# already down.
try:
self.set_default_log_level(0)
except dbus.exceptions.DBusException as ex:
if ex.get_dbus_name() != self._DBUS_ERROR_SERVICE_UNKNOWN:
raise
try:
upstart.stop_job('authpolicyd')
except error.CmdError as ex:
if (ex.result_obj.exit_status == 0):
raise
self._authpolicyd = None
def join_ad_domain(self,
user_principal_name,
password,
machine_name,
machine_domain=None,
machine_ou=None):
"""
Joins a machine (=device) to an Active Directory domain.
@param user_principal_name: Logon name of the user (with @realm) who
joins the machine to the domain.
@param password: Password corresponding to user_principal_name.
@param machine_name: Netbios computer (aka machine) name for the joining
device.
@param machine_domain: Domain (realm) the machine should be joined to.
If not specified, the machine is joined to the user's realm.
@param machine_ou: Array of organizational units (OUs) from leaf to
root. The machine is put into the leaf OU. If not specified, the
machine account is created in the default 'Computers' OU.
@return A tuple with the ErrorType and the joined domain returned by the
D-Bus call.
"""
from active_directory_info_pb2 import JoinDomainRequest
request = JoinDomainRequest()
request.user_principal_name = user_principal_name
request.machine_name = machine_name
if machine_ou:
request.machine_ou.extend(machine_ou)
if machine_domain:
request.machine_domain = machine_domain
with self.PasswordFd(password) as password_fd:
return self._authpolicyd.JoinADDomain(
dbus.ByteArray(request.SerializeToString()),
dbus.types.UnixFd(password_fd),
timeout=self._DEFAULT_TIMEOUT,
byte_arrays=True)
def authenticate_user(self, user_principal_name, account_id, password):
"""
Authenticates a user with an Active Directory domain.
@param user_principal_name: User logon name (user@example.com) for the
Active Directory domain.
#param account_id: User account id (aka objectGUID). May be empty.
@param password: Password corresponding to user_principal_name.
@return A tuple with the ErrorType and an ActiveDirectoryAccountInfo
blob string returned by the D-Bus call.
"""
from active_directory_info_pb2 import ActiveDirectoryAccountInfo
from active_directory_info_pb2 import AuthenticateUserRequest
from active_directory_info_pb2 import ERROR_NONE
request = AuthenticateUserRequest()
request.user_principal_name = user_principal_name
if account_id:
request.account_id = account_id
with self.PasswordFd(password) as password_fd:
error_value, account_info_blob = self._authpolicyd.AuthenticateUser(
dbus.ByteArray(request.SerializeToString()),
dbus.types.UnixFd(password_fd),
timeout=self._DEFAULT_TIMEOUT,
byte_arrays=True)
account_info = ActiveDirectoryAccountInfo()
if error_value == ERROR_NONE:
account_info.ParseFromString(account_info_blob)
return error_value, account_info
def refresh_user_policy(self, account_id):
"""
Fetches user policy and sends it to Session Manager.
@param account_id: User account ID (aka objectGUID).
@return ErrorType from the D-Bus call.
"""
return self._authpolicyd.RefreshUserPolicy(
dbus.String(account_id),
timeout=self._DEFAULT_TIMEOUT,
byte_arrays=True)
def refresh_device_policy(self):
"""
Fetches device policy and sends it to Session Manager.
@return ErrorType from the D-Bus call.
"""
return self._authpolicyd.RefreshDevicePolicy(
timeout=self._DEFAULT_TIMEOUT, byte_arrays=True)
def change_machine_password(self):
"""
Changes machine password.
@return ErrorType from the D-Bus call.
"""
return self._authpolicyd.ChangeMachinePasswordForTesting(
timeout=self._DEFAULT_TIMEOUT, byte_arrays=True)
def set_default_log_level(self, level):
"""
Fetches device policy and sends it to Session Manager.
@param level: Log level, 0=quiet, 1=taciturn, 2=chatty, 3=verbose.
@return error_message: Error message, empty if no error occurred.
"""
return self._authpolicyd.SetDefaultLogLevel(level, byte_arrays=True)
def print_log_tail(self):
"""
Prints out authpolicyd log tail. Catches and prints out errors.
"""
try:
cmd = 'tail -n %s %s' % (self._LOG_LINE_LIMIT, self._LOG_FILE)
log_tail = utils.run(cmd).stdout
logging.info('Tail of %s:\n%s', self._LOG_FILE, log_tail)
except error.CmdError as ex:
logging.error('Failed to print authpolicyd log tail: %s', ex)
def print_seccomp_failure_info(self):
"""
Detects seccomp failures and prints out the failing syscall.
"""
# Exit code 253 is minijail's marker for seccomp failures.
cmd = 'grep -q "exit code 253" %s' % self._LOG_FILE
if utils.run(cmd, ignore_status=True).exit_status == 0:
logging.error('Seccomp failure detected!')
cmd = 'grep -oE "blocked syscall: \\w+" %s | tail -1' % \
self._SYSLOG_FILE
try:
logging.error(utils.run(cmd).stdout)
logging.error(
'This can happen if you changed a dependency of '
'authpolicyd. Consider whitelisting this syscall in '
'the appropriate -seccomp.policy file in authpolicyd.'
'\n')
except error.CmdError as ex:
logging.error(
'Failed to determine reason for seccomp issue: %s', ex)
def clear_log(self):
"""
Clears the authpolicy daemon's log file.
"""
try:
utils.run('echo "" > %s' % self._LOG_FILE)
except error.CmdError as ex:
logging.error('Failed to clear authpolicyd log file: %s', ex)
class PasswordFd(object):
"""
Writes password into a file descriptor.
Use in a 'with' statement to automatically close the returned file
descriptor.
@param password: Plaintext password string.
@return A file descriptor (pipe) containing the password.
"""
def __init__(self, password):
self._password = password
self._read_fd = None
def __enter__(self):
"""Creates the password file descriptor."""
self._read_fd, write_fd = os.pipe()
os.write(write_fd, self._password)
os.close(write_fd)
return self._read_fd
def __exit__(self, mytype, value, traceback):
"""Closes the password file descriptor again."""
if self._read_fd:
os.close(self._read_fd)