xref: /aosp_15_r20/external/autotest/client/common_lib/cros/authpolicy.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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