You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
9.9 KiB
298 lines
9.9 KiB
# 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 allowlisting 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)
|