• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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