pax_global_header00006660000000000000000000000064142633266330014522gustar00rootroot0000000000000052 comment=40c6aa89a5b57ddb72ac2a1c128483d00ca99d8b guest-oslogin-20220714.00/000077500000000000000000000000001426332663300150025ustar00rootroot00000000000000guest-oslogin-20220714.00/90-google-compute-engine-oslogin.preset000066400000000000000000000000421426332663300243110ustar00rootroot00000000000000enable google-oslogin-cache.timer guest-oslogin-20220714.00/LICENSE000066400000000000000000000261151426332663300160140ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. guest-oslogin-20220714.00/Makefile000066400000000000000000000007261426332663300164470ustar00rootroot00000000000000all install : $(MAKE) -C src $@ alltests non_network_tests network_tests : $(MAKE) -C test $@ clean : $(MAKE) -C src clean $(MAKE) -C test clean prowbuild : debian_deps all prowtest : debian_deps non_network_tests mv test/test_detail.xml ${ARTIFACTS}/junit.xml debian_deps : apt-get -y install g++ libcurl4-openssl-dev libjson-c-dev libpam-dev \ googletest && touch $@ .PHONY : all clean install prowbuild prowtest alltests non_network_tests network_tests guest-oslogin-20220714.00/OWNERS000066400000000000000000000003021426332663300157350ustar00rootroot00000000000000# This file enables automatic assignment of PR reviewers. # See the OWNERS docs at https://go.k8s.io/owners approvers: - hopkiw - illfelder reviewers: - hopkiw - illfelder - jaiminsh guest-oslogin-20220714.00/README.md000066400000000000000000000201711426332663300162620ustar00rootroot00000000000000## OS Login Guest Environment for Google Compute Engine This repository contains the system components responsible for providing Google Cloud OS Login features on Google Compute Engine instances. **Table of Contents** * [Overview](#overview) * [Components](#components) * [Authorized Keys Command](#authorized-keys-command) * [NSS Modules](#nss-modules) * [PAM Modules](#pam-modules) * [Utilities](#Utilities) * [Control Script](#control-script) * [SELinux Policy](#selinux-policy) * [Source Packages](#source-packages) * [DEB](#deb) * [RPM](#rpm) ## Overview The OS Login Guest Environment consists of the following main components: * **Authorized Keys Command** which provides SSH keys from the user's OS Login profile to sshd for authenticating users at login. * **NSS Modules** which provide support for making OS Login user and group information available to the system, using NSS (Name Service Switch) functionality. * **PAM Modules** which provide authorization (and authentication if two-factor support is enabled) support allowing the system to use Google Cloud IAM permissions to control the ability to log into an instance or to perform operations as root (via `sudo`). In addition to the main components, there are also the following utilities: * **google_oslogin_nss_cache** is a utility for updating the local user and group cache. * **selinux** contains SELinux policy definition files and a compiled policy package for configuring SELinux to support OS Login. The **packaging** directory also contains files used to generate `.deb` and `.rpm` packages for the OS Login components. ## Components #### Authorized Keys Command The `google_authorized_keys` binary is designed to be used with the sshd `AuthorizedKeysCommand` option in [sshd_config(5)](https://linux.die.net/man/5/sshd_config). It does the following: * Reads the user's profile information from the metadata server: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/users?username= ``` * Checks to make sure that the user is authorized to log in: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/authorize?email=&policy=login ``` * If the check is successful, returns the SSH keys associated with the user for use by sshd. Otherwise, exits with an error code. #### NSS Modules `libnss_oslogin.so` and `libnss_cache_oslogin.so` are NSS service modules which make OS Login users and groups available for use on the local system. The module is activated by adding `oslogin` and `cache_oslogin` entries for services in [nsswitch.conf(5)](https://linux.die.net/man/5/nsswitch.conf). * To return a list of all users, the NSS module queries: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/users?pagesize= ``` * To look up a user by username, the NSS module queries: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/users?username= ``` * To look up a user by UID, the NSS module queries: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/users?uid= ``` #### PAM Modules `pam_oslogin_login.so` is a PAM module which determines whether a given user is allowed to SSH into an instance. It is activated by adding an entry for the account group to the PAM service config for sshd as: ``` account requisite pam_oslogin_login.so ``` This module: * Retrieves the user's profile information from the metadata server: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/users?username= ``` * If the user does not have OS Login profile information it is passed on to the system authentication modules to be processed as a local user. * Otherwise, the module confirms whether the user has permissions to SSH into the instance: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/authorize?email=&policy=login ``` * If the user is authorized, PAM returns a success message and SSH can proceed. Otherwise, PAM returns a denied message and the SSH check will fail. `pam_oslogin_admin.so` is a PAM module which determines whether a given user should have admin (sudo) permissions on the instance. It is activated by adding an entry for the `account` group to the PAM service config for sshd config as: ``` account optional pam_oslogin_admin.so ``` This module: * Retrieves the user's profile information from the metadata server. ``` http://metadata.google.internal/computeMetadata/v1/oslogin/users?username= ``` * If the user is not an OS Login user (a local user account), the module returns success. * Otherwise, the module determines if the user has admin permissions: ``` http://metadata.google.internal/computeMetadata/v1/oslogin/authorize?email=&policy=adminLogin ``` * If the user is authorized as an admin, a file with the username is added to `/var/google-sudoers.d/`. The file gives the user sudo privileges. * If the authorization check fails for admin permissions, the file is removed from `/var/google-sudoers.d/` if it exists. ## Utilities #### Control Script The `google_oslogin_control` shell script activates or deactivates the OS Login features. It is invoked by the google accounts daemon. The control file performs the following tasks: * Adds (or removes) AuthorizedKeysCommand and AuthorizedKeysCommandUser lines to (from) `sshd_config` and restarts sshd. * Adds (or removes) `oslogin` and `cache_oslogin` to (from) `nsswitch.conf`. * Adds (or removes) the `account` entries to (from) the PAM sshd config. Also adds (or removes) the `pam_mkhomedir.so` module to automatically create the home directory for an OS Login user. * Creates (or deletes) the `/var/google-sudoers.d/` directory, and a file called `google-oslogin` in `/etc/sudoers.d/` that includes the directory. #### SELinux Policy The `selinux` directory contains `.te` (type enforcement) and `.fc` (file context) files used by SELinux to give the OS Login features the appropriate SELinux permissions. These are compiled using `checkmodule` and `semodule_package` to create an policy package `oslogin.pp`. ## Source Packages There is currently support for creating packages for the following distros: * Debian 9 * CentOS/RHEL 6 * CentOS/RHEL 7 Files for these packages are in the `packaging/` directory. #### DEB _Note: the `packaging/setup_deb.sh` script performs these steps, but is not production quality._ 1. Install build dependencies: ``` sudo apt-get -y install make g++ libcurl4-openssl-dev libjson-c-dev libpam-dev ``` 1. Install deb creation tools: ``` sudo apt-get -y install debhelper devscripts build-essential ``` 1. Create a compressed tar file named `google-compute-engine-oslogin_M.M.R.orig.tar.gz` using the files in this directory, excluding the `packaging` directory (where M.M.R is the version number). 1. In a separate directory, extract the `.orig.tar.gz` file and copy the `debian` directory into the top level. 1. To build the package, run the command ``` debuild -us -uc ``` #### RPM _Note: the `packaging/setup_rpm.sh` script performs these steps, but is not production quality._ 1. Install build dependencies: ``` sudo yum -y install make gcc-c++ libcurl-devel json-c json-c-devel pam-devel policycoreutils-python ``` 1. Install rpm creation tools: ``` sudo yum -y install rpmdevtools ``` 1. Create a compressed tar file named `google-compute-engine-oslogin_M.M.R.orig.tar.gz` using the files in this directory, excluding the `packaging` directory (where M.M.R is the version number). 1. In a separate location, create a directory called `rpmbuild` and a subdirectory called `SOURCES`. Copy the `.orig.tar.gz` file into the `SOURCES` directory. 1. Copy the `SPECS` directory from the `rpmbuild` directory here into the `rpmbuild` directory you created. 1. To build the package, run the command: ``` rpmbuild --define "_topdir /path/to/rpmbuild" -ba /path/to/rpmbuild/SPECS/google-compute-engine-oslogin.spec ``` guest-oslogin-20220714.00/cron.d000066400000000000000000000002351426332663300161100ustar00rootroot00000000000000# Run the NSS cache refresh script every six hours. The guest agent also invokes # this script on start. 0 */6 * * * root /usr/bin/google_oslogin_nss_cache guest-oslogin-20220714.00/find-requires000066400000000000000000000005121426332663300175000ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use IPC::Open2; # This quick script will run the native find-requires (first parameter) # and then strip out packages we don't want listed. open2(\*IN, \*OUT, @ARGV); print OUT while (); close(OUT); my $list = join('', ); # Apply my filter(s): $list =~ s/^.*JSONC.*//mg; print $list; guest-oslogin-20220714.00/google-oslogin-cache.service000066400000000000000000000002641426332663300223530ustar00rootroot00000000000000[Unit] Description=NSS cache refresh [Service] Type=oneshot ExecStart=/usr/bin/google_oslogin_nss_cache # No [Install] section - this is controlled by google-oslogin-cache.timer guest-oslogin-20220714.00/google-oslogin-cache.timer000066400000000000000000000001651426332663300220330ustar00rootroot00000000000000[Unit] Description=NSS cache refresh timer [Timer] OnBootSec=5 OnUnitActiveSec=6h [Install] WantedBy=timers.target guest-oslogin-20220714.00/man/000077500000000000000000000000001426332663300155555ustar00rootroot00000000000000guest-oslogin-20220714.00/man/nss-cache-oslogin.8000066400000000000000000000026111426332663300211620ustar00rootroot00000000000000'\" t .TH "NSS\-CACHE\-OSLOGIN" "8" "2019-06-06" "Google Cloud" .\" IF GNU troff set Aq to sequence aq (Apostrophe quote) .ie \n(.g .ds Aq \(aq .\" ELSE set Aq to ' .el .ds Aq ' .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .SH "NAME" nss-cache-oslogin, libnss_cache_oslogin.so.2 \- UNIX implementation for OS Login Users and Groups with local caching\&. .SH "SYNOPSIS" libnss_cache_oslogin\&.so\&.2 .SH "DESCRIPTION" \fBnss\-cache\-oslogin\fR is a plug\-in module for the GNU Name Service Switch (NSS) functionality of the GNU C Library (\fBglibc\fR)\&. This module provides UNIX name resolution from a local cache of users and groups configured in the \fIGoogle Cloud OS Login\fR system\&. The "passwd" and "group" services are supported by this module\&. .PP To use the NSS module, add "cache_oslogin" to the appropriate service lines in nsswitch\&.conf\&. It is recommended to place "cache_oslogin" after any system provided modules and before the "oslogin" module, as it is a pass-through cache. .SH "FILES" .IP /etc/oslogin_passwd.cache user cache file .IP /etc/oslogin_group.cache group cache file .SH "NOTES" Documentation for the Google Cloud OS Login service is available online at .UR "https://cloud.google.com/compute/docs/oslogin" .UE .SH "SEE ALSO" .BR nss-oslogin (5), .BR nsswitch.conf (5), .BR nss (5), .BR getent (1), .BR initgroups (3) guest-oslogin-20220714.00/man/nss-oslogin.8000066400000000000000000000022471426332663300201260ustar00rootroot00000000000000'\" t .TH "NSS\-OSLOGIN" "8" "2019-06-06" "Google Cloud" .\" IF GNU troff set Aq to sequence aq (Apostrophe quote) .ie \n(.g .ds Aq \(aq .\" ELSE set Aq to ' .el .ds Aq ' .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .SH "NAME" nss-oslogin, libnss_oslogin.so.2 \- UNIX implementation for OS Login Users and Groups\&. .SH "SYNOPSIS" libnss_oslogin\&.so\&.2 .SH "DESCRIPTION" \fBnss\-oslogin\fR is a plug\-in module for the GNU Name Service Switch (NSS) functionality of the GNU C Library (\fBglibc\fR). This module provides UNIX name resolution for users and groups configured through the \fIGoogle Cloud OS Login\fR system\&. The "passwd", "group", and "initgroups" services are supported by this module. .PP To use the NSS module, add "oslogin" to the appropriate service lines in nsswitch\&.conf\&. It is recommended to place "oslogin" as the last entry for each service. .SH "NOTES" Documentation for the Google Cloud OS Login service is available online at .UR "https://cloud.google.com/compute/docs/oslogin" .UE .SH "SEE ALSO" .BR nss-cache-oslogin (8), .BR nsswitch.conf (5), .BR nss (5), .BR getent (1), .BR initgroups (3) guest-oslogin-20220714.00/packaging/000077500000000000000000000000001426332663300167265ustar00rootroot00000000000000guest-oslogin-20220714.00/packaging/debian/000077500000000000000000000000001426332663300201505ustar00rootroot00000000000000guest-oslogin-20220714.00/packaging/debian/changelog000066400000000000000000000127731426332663300220340ustar00rootroot00000000000000google-compute-engine-oslogin (1:20191018.00-g1) stable; urgency=medium * Correct JSON refcount decrementing (regression). -- Google Cloud Team Fri, 18 Oct 2019 12:54:15 -0700 google-compute-engine-oslogin (1:20191014.00-g1) stable; urgency=medium * Groups support. -- Google Cloud Team Mon, 14 Oct 2019 13:18:36 -0700 google-compute-engine-oslogin (1:20190801.00-g1) stable; urgency=medium * Correct JSON refcount decrementing. -- Google Cloud Team Thu, 01 Aug 2019 13:57:16 -0700 google-compute-engine-oslogin (1:20190729.00-g1) stable; urgency=medium * Remove unnecessary binary search logic. -- Google Cloud Team Mon, 29 Jul 2019 10:11:00 -0700 google-compute-engine-oslogin (1:20190708.00-g1) stable; urgency=medium * Restructure Makefile and Debian control files. * Add man pages. -- Google Cloud Team Mon, 08 Jul 2019 10:20:01 -0700 google-compute-engine-oslogin (1.5.3-1) unstable; urgency=low * Update OS Login control file for FreeBSD support. -- Google Cloud Team Wed, 22 May 2019 12:00:00 -0700 google-compute-engine-oslogin (1.5.2-1) unstable; urgency=low * Fix pam_group ordering detection. * Restart cron on OS Login control. * Add PAM entry to su:account stack. -- Google Cloud Team Tue, 16 Apr 2019 12:00:00 -0700 google-compute-engine-oslogin (1.5.1-1) unstable; urgency=low * Fix two factor auth action name. -- Google Cloud Team Tue, 13 Mar 2019 12:00:00 -0700 google-compute-engine-oslogin (1.5.0-1) unstable; urgency=low * Support Google prompt for two factor authentication. -- Google Cloud Team Tue, 19 Feb 2019 12:00:00 -0700 google-compute-engine-oslogin (1.4.3-1) unstable; urgency=low * Improve OS Login control file for BSD support. * Improve SELinux support. -- Google Cloud Team Wed, 05 Dec 2018 12:00:00 -0700 google-compute-engine-oslogin (1.4.2-1+deb9) unstable; urgency=low * Improve OS Login control file. * Restart systemd-logind on OS Login enable. -- Google Cloud Team Tue, 04 Dec 2018 12:00:00 -0700 google-compute-engine-oslogin (1.4.1-1+deb9) unstable; urgency=low * Improve SELinux support. * Improve OS Login control file. -- Google Cloud Team Fri, 30 Nov 2018 12:00:00 -0700 google-compute-engine-oslogin (1.4.0-1+deb9) unstable; urgency=low * Support OS Login two factor authentication. -- Google Cloud Team Wed, 28 Nov 2018 12:00:00 -0700 google-compute-engine-oslogin (1.3.1-1+deb9) unstable; urgency=low * Add user name validation to pam modules. * Return false on failed final load. -- Google Cloud Team Wed, 05 Sep 2018 12:00:00 -0700 google-compute-engine-oslogin (1.3.0-1+deb9) unstable; urgency=low * Include libnss cache as part of the OS Login package. -- Google Cloud Team Tue, 01 May 2018 12:00:00 -0700 google-compute-engine-oslogin (1.2.0-1+deb9) unstable; urgency=low * Add support for NSS cache. -- Google Cloud Team Thu, 08 Mar 2018 12:00:00 -0700 google-compute-engine-oslogin (1.1.5-1+deb9) unstable; urgency=low * Clear the CURL_GLOBAL_SSL bit on curl initialization. -- Google Cloud Team Mon, 26 Feb 2018 12:00:00 -0700 google-compute-engine-oslogin (1.1.4-1+deb9) unstable; urgency=low * Close socket connections when requesting metadata. -- Google Cloud Team Mon, 29 Jan 2018 12:00:00 -0700 google-compute-engine-oslogin (1.1.3-1+deb9) unstable; urgency=low * Change the OS Login uid restriction to allow uid 1000. -- Google Cloud Team Thu, 25 Jan 2018 12:00:00 -0700 google-compute-engine-oslogin (1.1.2-1+deb9) unstable; urgency=low * Fix parsing logic for expiration time on SSH public keys. * Fix home directory creation PAM config. -- MAINTAINER Wed, 29 Nov 2017 12:00:00 -0700 google-compute-engine-oslogin (1.1.1-1+deb9) unstable; urgency=low * Remove logging when checking OS Login status. -- MAINTAINER Wed, 25 Oct 2017 12:00:00 -0700 google-compute-engine-oslogin (1.1.0-1+deb9) unstable; urgency=low * OS Login is enabled via the google-compute-engine package. -- MAINTAINER Tue, 17 Oct 2017 12:00:00 -0700 google-compute-engine-oslogin (1.0.5-1+deb9) unstable; urgency=low * JSON parser accepts string types for int64 values. -- MAINTAINER Fri, 06 Oct 2017 12:00:00 -0700 google-compute-engine-oslogin (1.0.4-1+deb9) unstable; urgency=low * JSON parser casts uid and gid to unsigned integers. -- MAINTAINER Tue, 20 Sep 2017 12:00:00 -0700 google-compute-engine-oslogin (1.0.3-1+deb9) unstable; urgency=low * Strictly check for HTTP code 200. -- MAINTAINER Tue, 25 Aug 2017 12:00:00 -0700 google-compute-engine-oslogin (1.0.2-1+deb9) unstable; urgency=low * Improve security in case of transient errors. -- MAINTAINER Tue, 15 Aug 2017 12:00:00 -0700 google-compute-engine-oslogin (1.0.1-1+deb9) unstable; urgency=low * Fix for restarting sshd and nscd. -- MAINTAINER Mon, 17 Jul 2017 12:00:00 -0700 google-compute-engine-oslogin (1.0.0-1+deb9) unstable; urgency=low * Team Upload. * Initial release. -- MAINTAINER Thu, 22 Jun 2017 12:00:00 -0700 guest-oslogin-20220714.00/packaging/debian/compat000066400000000000000000000000021426332663300213460ustar00rootroot000000000000009 guest-oslogin-20220714.00/packaging/debian/control000066400000000000000000000010021426332663300215440ustar00rootroot00000000000000Source: google-compute-engine-oslogin Maintainer: Google Cloud Team Section: misc Priority: optional Standards-Version: 3.9.8 Build-Depends: debhelper (>= 9), libcurl4-openssl-dev, libjson-c-dev | libjson0-dev, libpam-dev Package: google-compute-engine-oslogin Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Google Compute Engine OS Login Contains libraries, applications and configurations for using OS Login on Google Compute Engine Virtual Machine Instances. guest-oslogin-20220714.00/packaging/debian/copyright000066400000000000000000000017361426332663300221120ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: google-compute-engine-oslogin Upstream-Contact: gc-team@google.com Files: * Copyright: Copyright 2017 Google Inc. License: Apache-2.0 Files: debian/* Copyright: Copyright 2017 Google Inc. License: Apache-2.0 License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the complete text of the Apache version 2.0 license can be found in "/usr/share/common-licenses/Apache-2.0". guest-oslogin-20220714.00/packaging/debian/rules000077500000000000000000000004441426332663300212320ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ --with=systemd override_dh_auto_install: dh_auto_install -- LIBDIR=/lib/$(DEB_HOST_MULTIARCH) VERSION=$(VERSION) override_dh_systemd_enable: dh_systemd_enable google-oslogin-cache.timer override_dh_systemd_start: dh_systemd_start google-oslogin-cache.timer guest-oslogin-20220714.00/packaging/debian/source/000077500000000000000000000000001426332663300214505ustar00rootroot00000000000000guest-oslogin-20220714.00/packaging/debian/source/format000066400000000000000000000000141426332663300226560ustar00rootroot000000000000003.0 (quilt) guest-oslogin-20220714.00/packaging/google-compute-engine-oslogin.spec000066400000000000000000000063501426332663300254470ustar00rootroot00000000000000# Copyright 2017 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # For EL7, if building on CentOS, override dist to be el7. %if 0%{?rhel} == 7 %define dist .el7 %endif Name: google-compute-engine-oslogin Epoch: 1 Version: %{_version} Release: g1%{?dist} Summary: OS Login Functionality for Google Compute Engine License: ASL 2.0 Source0: %{name}_%{version}.orig.tar.gz BuildRequires: boost-devel BuildRequires: gcc-c++ BuildRequires: make BuildRequires: libcurl-devel BuildRequires: json-c-devel BuildRequires: pam-devel BuildRequires: policycoreutils BuildRequires: systemd Requires: boost-regex Requires: json-c Requires: policycoreutils %description This package contains several libraries and changes to enable OS Login functionality for Google Compute Engine. %global debug_package %{nil} %global _use_internal_dependency_generator 0 %global __find_requires_orig %{__find_requires} %define __find_requires %{_builddir}/%{?buildsubdir}/find-requires %{__find_requires_orig} %prep %setup %build chmod +x find-requires make %{?_smp_mflags} LDLIBS="-lcurl -ljson-c -lboost_regex" %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} LIBDIR=/%{_lib} VERSION=%{version} INSTALL_SELINUX=y %files %doc /%{_lib}/libnss_oslogin-%{version}.so /%{_lib}/libnss_cache_oslogin-%{version}.so /%{_lib}/libnss_oslogin.so.2 /%{_lib}/libnss_cache_oslogin.so.2 /%{_lib}/security/pam_oslogin_admin.so /%{_lib}/security/pam_oslogin_login.so /usr/bin/google_authorized_keys /usr/bin/google_authorized_keys_sk /usr/bin/google_oslogin_nss_cache /usr/share/selinux/packages/oslogin.pp %{_mandir}/man8/nss-oslogin.8.gz %{_mandir}/man8/libnss_oslogin.so.2.8.gz %{_mandir}/man8/nss-cache-oslogin.8.gz %{_mandir}/man8/libnss_cache_oslogin.so.2.8.gz /lib/systemd/system/google-oslogin-cache.service /lib/systemd/system/google-oslogin-cache.timer /lib/systemd/system-preset/90-google-compute-engine-oslogin.preset %post if [ $1 -eq 1 ]; then # Initial installation systemctl enable google-oslogin-cache.timer >/dev/null 2>&1 || : if [ -d /run/systemd/system ]; then systemctl daemon-reload >/dev/null 2>&1 || : systemctl start google-oslogin-cache.timer >/dev/null 2>&1 || : fi fi /sbin/ldconfig echo "Installing SELinux module for OS Login." semodule -i /usr/share/selinux/packages/oslogin.pp if [ -e /var/google-sudoers.d ]; then restorecon -r /var/google-sudoers.d fi %preun %systemd_preun google-oslogin-cache.timer # This is only relevant on EL7. %if 0%{?rhel} == 7 %postun %systemd_postun %endif /sbin/ldconfig if [ $1 = 0 ]; then # This is an uninstall. if semodule -l|grep -qi oslogin; then echo "Removing SELinux module for OS Login." semodule -r oslogin fi fi %changelog guest-oslogin-20220714.00/selinux/000077500000000000000000000000001426332663300164715ustar00rootroot00000000000000guest-oslogin-20220714.00/selinux/Makefile000066400000000000000000000007421426332663300201340ustar00rootroot00000000000000# SELINUX POLICY MOD_BASE = oslogin SELINUX_MODULE_SRC = $(MOD_BASE).te SELINUX_MOD_FILE = $(MOD_BASE).mod SELINUX_FC_FILE = $(MOD_BASE).fc SELINUX_MODULE = $(MOD_BASE).pp all: $(SELINUX_MODULE) $(SELINUX_MOD_FILE): $(SELINUX_MODULE_SRC) checkmodule -M -m -o $(SELINUX_MOD_FILE) $(SELINUX_MODULE_SRC) $(SELINUX_MODULE): $(SELINUX_MOD_FILE) semodule_package -o $(SELINUX_MODULE) -m $(SELINUX_MOD_FILE) -f $(SELINUX_FC_FILE) clean: rm -f $(SELINUX_MODULE) $(SELINUX_MOD_FILE) guest-oslogin-20220714.00/selinux/README.md000066400000000000000000000014171426332663300177530ustar00rootroot00000000000000## SELinux policy module for OS Login This module adds specific policy updates which enable OS Login features to function on SELinux-enabled systems (currently default on GCE RHEL6/7 images). It primarily enables `SSHD(8)` to make network calls to the metadata server to verify OS Login users, and to create per-user `SUDOERS(5)` files in `/var/google-sudoers.d` ### Building the module The provided Makefile compiles type enforcement and file context files into a binary SELinux policy module. It must be compiled on the oldest version of the destination OS you intend to support, as binary module versions are not backwards compatible. Therefore, this Makefile is not run as part of the normal packaging process but is done 'by hand', only when changes are made to the policy. guest-oslogin-20220714.00/selinux/oslogin.fc000066400000000000000000000001621426332663300204540ustar00rootroot00000000000000/var/google-sudoers.d(/.*)? system_u:object_r:google_t:s0 /var/google-users.d(/.*)? system_u:object_r:google_t:s0 guest-oslogin-20220714.00/selinux/oslogin.pp000066400000000000000000000034061426332663300205070ustar00rootroot00000000000000||SE Linux Module oslogin1.0@ tcp_socket name_connectdirwrite remove_nameadd_namesearchfilecreatewriteunlinkgetattrsetattropenobject_r@@@ @@file_type@google_t@@non_security_file_type @http_port_t@sshd_t@@@@@@?@@@@@@@@@@@@@@@@@@@@@@@@@@?@@@@@@@@@@@ tcp_socketdirfileobject_r file_typegoogle_tnon_security_file_type http_port_tsshd_t|/var/google-sudoers.d(/.*)? system_u:object_r:google_t:s0 /var/google-users.d(/.*)? system_u:object_r:google_t:s0 guest-oslogin-20220714.00/selinux/oslogin.te000066400000000000000000000011641426332663300204770ustar00rootroot00000000000000 module oslogin 1.0; require { attribute file_type; attribute non_security_file_type; type http_port_t; type sshd_t; class tcp_socket name_connect; class file { create getattr setattr write open unlink }; class dir { search write remove_name add_name }; } #============= types ============== type google_t; # defined in oslogin.fc typeattribute google_t file_type, non_security_file_type; #============= sshd_t ============== allow sshd_t google_t:file { create getattr setattr write open unlink }; allow sshd_t google_t:dir { search write remove_name add_name }; allow sshd_t http_port_t:tcp_socket name_connect; guest-oslogin-20220714.00/src/000077500000000000000000000000001426332663300155715ustar00rootroot00000000000000guest-oslogin-20220714.00/src/Makefile000066400000000000000000000072511426332663300172360ustar00rootroot00000000000000SHELL = /bin/sh TOPDIR = $(realpath ..) CPPFLAGS = -Iinclude -I/usr/include/json-c FLAGS = -fPIC -Wall -g CFLAGS = $(FLAGS) -Wstrict-prototypes CXXFLAGS = $(FLAGS) LDFLAGS = -shared -Wl,-soname,$(SONAME) LDLIBS = -lcurl -ljson-c PAMLIBS = -lpam $(LDLIBS) # Paths which should be overrideable. PREFIX = /usr LIBDIR = $(PREFIX)/lib BINDIR = $(PREFIX)/bin PAMDIR = $(LIBDIR)/security MANDIR = /usr/share/man CRONDIR = /etc/cron.d SYSTEMDDIR = /lib/systemd/system PRESETDIR = /lib/systemd/system-preset NSS_OSLOGIN_SONAME = libnss_oslogin.so.2 NSS_CACHE_OSLOGIN_SONAME = libnss_cache_oslogin.so.2 NSS_OSLOGIN = libnss_oslogin-$(VERSION).so NSS_CACHE_OSLOGIN = libnss_cache_oslogin-$(VERSION).so PAM_LOGIN = pam_oslogin_login.so PAM_ADMIN = pam_oslogin_admin.so BINARIES = google_oslogin_nss_cache google_authorized_keys google_authorized_keys_sk all : $(NSS_OSLOGIN) $(NSS_CACHE_OSLOGIN) $(PAM_LOGIN) $(PAM_ADMIN) $(BINARIES) clean : rm -f $(BINARIES) find . -type f \( -iname '*.o' -o -iname '*.so' \) -delete .PHONY : all clean install # NSS modules. $(NSS_OSLOGIN) : SONAME = $(NSS_OSLOGIN_SONAME) $(NSS_OSLOGIN) : nss/nss_oslogin.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ $(LDLIBS) $(NSS_CACHE_OSLOGIN) : SONAME = $(NSS_CACHE_OSLOGIN_SONAME) $(NSS_CACHE_OSLOGIN) : nss/nss_cache_oslogin.o nss/compat/getpwent_r.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ $(LDLIBS) # PAM modules $(PAM_LOGIN) : pam/pam_oslogin_login.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) -shared $^ -o $@ $(PAMLIBS) $(PAM_ADMIN) : pam/pam_oslogin_admin.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) -shared $^ -o $@ $(PAMLIBS) # Utilities. google_authorized_keys : authorized_keys/authorized_keys.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ -o $@ $(LDLIBS) google_authorized_keys_sk : authorized_keys/authorized_keys_sk.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ -o $@ $(LDLIBS) google_oslogin_nss_cache: cache_refresh/cache_refresh.o oslogin_utils.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ -o $@ $(LDLIBS) install: all # Make dirs install -d $(DESTDIR)$(LIBDIR) install -d $(DESTDIR)$(PAMDIR) install -d $(DESTDIR)$(BINDIR) install -d $(DESTDIR)$(MANDIR)/man8 # NSS modules install -m 0644 -t $(DESTDIR)$(LIBDIR) $(NSS_OSLOGIN) $(NSS_CACHE_OSLOGIN) ln -sf $(NSS_OSLOGIN) $(DESTDIR)$(LIBDIR)/$(NSS_OSLOGIN_SONAME) ln -sf $(NSS_CACHE_OSLOGIN) $(DESTDIR)$(LIBDIR)/$(NSS_CACHE_OSLOGIN_SONAME) # PAM modules install -m 0644 -t $(DESTDIR)$(PAMDIR) $(PAM_ADMIN) $(PAM_LOGIN) # Binaries install -m 0755 -t $(DESTDIR)$(BINDIR) $(BINARIES) # Manpages install -m 0644 -t $(DESTDIR)$(MANDIR)/man8 $(TOPDIR)/man/nss-oslogin.8 $(TOPDIR)/man/nss-cache-oslogin.8 gzip -9 $(DESTDIR)$(MANDIR)/man8/nss-oslogin.8 gzip -9 $(DESTDIR)$(MANDIR)/man8/nss-cache-oslogin.8 ln -sf nss-oslogin.8.gz $(DESTDIR)$(MANDIR)/man8/$(NSS_OSLOGIN_SONAME).8.gz ln -sf nss-cache-oslogin.8.gz $(DESTDIR)$(MANDIR)/man8/$(NSS_CACHE_OSLOGIN_SONAME).8.gz ifdef INSTALL_SELINUX # SELinux policy package install -d $(DESTDIR)/usr/share/selinux/packages install -m 0644 -t $(DESTDIR)/usr/share/selinux/packages $(TOPDIR)/selinux/oslogin.pp endif ifdef INSTALL_CRON # Cache refresh cron install -d $(DESTDIR)$(CRONDIR) install -m 0644 $(TOPDIR)/cron.d $(DESTDIR)$(CRONDIR)/google-compute-engine-oslogin else # Cache refresh systemd timer install -d $(DESTDIR)$(SYSTEMDDIR) install -m 0644 -t $(DESTDIR)$(SYSTEMDDIR) $(TOPDIR)/google-oslogin-cache.timer $(TOPDIR)/google-oslogin-cache.service install -d $(DESTDIR)$(PRESETDIR) install -m 0644 -t $(DESTDIR)$(PRESETDIR) $(TOPDIR)/90-google-compute-engine-oslogin.preset endif guest-oslogin-20220714.00/src/authorized_keys/000077500000000000000000000000001426332663300210025ustar00rootroot00000000000000guest-oslogin-20220714.00/src/authorized_keys/authorized_keys.cc000066400000000000000000000060311426332663300245220ustar00rootroot00000000000000// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include using std::cout; using std::endl; using std::string; using oslogin_utils::HttpGet; using oslogin_utils::ParseJsonToSuccess; using oslogin_utils::ParseJsonToEmail; using oslogin_utils::ParseJsonToSshKeys; using oslogin_utils::UrlEncode; using oslogin_utils::kMetadataServerUrl; void sigpipe_handler(int signo) { // exit 0 so SSHD can use what we've already written out. _Exit(0); } int main(int argc, char* argv[]) { if (argc != 2) { cout << "usage: authorized_keys [username]" << endl; return 1; } struct sigaction newact; newact.sa_handler = sigpipe_handler; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; if (sigaction(SIGPIPE, &newact, NULL) == -1) { cout << "Unable to add SIGPIPE handler, exiting" << endl; return 1; } std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(argv[1]); string user_response; long http_code = 0; if (!HttpGet(url.str(), &user_response, &http_code) || user_response.empty() || http_code != 200) { if (http_code == 404) { // Return 0 if the user is not an oslogin user. If we returned a failure // code, we would populate auth.log with useless error messages. // This exits successfully but prints no keys. return 0; } return 1; } string email; if (!ParseJsonToEmail(user_response, &email) || email.empty()) { return 1; } // Redundantly verify that this user has permission to log in to this VM. // Normally the PAM module determines this, but in the off chance a transient // error causes the PAM module to permit a user without login permissions, // perform the same check here. If this fails, we can guarantee that we won't // accidentally allow a user to log in without permissions. url.str(""); url << kMetadataServerUrl << "authorize?email=" << UrlEncode(email) << "&policy=login"; string auth_response; if (!HttpGet(url.str(), &auth_response, &http_code) || http_code != 200 || auth_response.empty()) { return 1; } if (!ParseJsonToSuccess(auth_response)) { return 1; } // At this point, we've verified the user can log in. Grab the ssh keys from // the user response. std::vector ssh_keys = ParseJsonToSshKeys(user_response); for (size_t i = 0; i < ssh_keys.size(); i++) { cout << ssh_keys[i] << endl; } return 0; } guest-oslogin-20220714.00/src/authorized_keys/authorized_keys_sk.cc000066400000000000000000000064071426332663300252260ustar00rootroot00000000000000// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include using std::cout; using std::endl; using std::string; using oslogin_utils::HttpGet; using oslogin_utils::ParseJsonToSuccess; using oslogin_utils::ParseJsonToEmail; using oslogin_utils::ParseJsonToSshKeys; using oslogin_utils::ParseJsonToSshKeysSk; using oslogin_utils::UrlEncode; using oslogin_utils::kMetadataServerUrl; void sigpipe_handler(int signo) { // exit 0 so SSHD can use what we've already written out. _Exit(0); } int main(int argc, char* argv[]) { if (argc != 2) { cout << "usage: authorized_keys_sk [username]" << endl; return 1; } struct sigaction newact; newact.sa_handler = sigpipe_handler; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; if (sigaction(SIGPIPE, &newact, NULL) == -1) { cout << "Unable to add SIGPIPE handler, exiting" << endl; return 1; } bool is_sa = (strncmp(argv[1], "sa_", 3) == 0); std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(argv[1]) << "&view=securityKey"; string user_response; long http_code = 0; if (!HttpGet(url.str(), &user_response, &http_code) || user_response.empty() || http_code != 200) { if (http_code == 404) { // Return 0 if the user is not an oslogin user. If we returned a failure // code, we would populate auth.log with useless error messages. return 0; } return 1; } string email; if (!ParseJsonToEmail(user_response, &email) || email.empty()) { return 1; } // Redundantly verify that this user has permission to log in to this VM. // Normally the PAM module determines this, but in the off chance a transient // error causes the PAM module to permit a user without login permissions, // perform the same check here. If this fails, we can guarantee that we won't // accidentally allow a user to log in without permissions. url.str(""); url << kMetadataServerUrl << "authorize?email=" << UrlEncode(email) << "&policy=login"; string auth_response; if (!HttpGet(url.str(), &auth_response, &http_code) || http_code != 200 || auth_response.empty()) { return 1; } if (!ParseJsonToSuccess(auth_response)) { return 1; } // At this point, we've verified the user can log in. Grab the ssh keys from // the user response. std::vector ssh_keys; if (is_sa) { // Service accounts should continue to function when SK is enabled. ssh_keys = ParseJsonToSshKeys(user_response); } else { ssh_keys = ParseJsonToSshKeysSk(user_response); } for (size_t i = 0; i < ssh_keys.size(); i++) { cout << ssh_keys[i] << endl; } return 0; } guest-oslogin-20220714.00/src/cache_refresh/000077500000000000000000000000001426332663300203525ustar00rootroot00000000000000guest-oslogin-20220714.00/src/cache_refresh/cache_refresh.cc000066400000000000000000000160151426332663300234450ustar00rootroot00000000000000// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include using oslogin_utils::BufferManager; using oslogin_utils::MutexLock; using oslogin_utils::NssCache; using oslogin_utils::GetUsersForGroup; // File paths for the nss cache file. static const char kDefaultFilePath[] = K_DEFAULT_PFILE_PATH; static const char kDefaultBackupFilePath[] = K_DEFAULT_BACKUP_PFILE_PATH; static const char kDefaultGroupPath[] = K_DEFAULT_GFILE_PATH; static const char kDefaultBackupGroupPath[] = K_DEFAULT_BACKUP_GFILE_PATH; // Local NSS Cache size. This affects the maximum number of passwd or group // entries per http request. static const uint64_t kNssGroupCacheSize = 499; static const uint64_t kNssPasswdCacheSize = 2048; // Passwd buffer size. We are guaranteed that a single OS Login user will not // exceed 32k. static const uint64_t kPasswdBufferSize = 32768; int refreshpasswdcache() { syslog(LOG_INFO, "Refreshing passwd entry cache"); int error_code = 0; // Temporary buffer to hold passwd entries before writing. char buffer[kPasswdBufferSize]; struct passwd pwd; NssCache nss_cache(kNssPasswdCacheSize); std::ofstream cache_file(kDefaultBackupFilePath); if (cache_file.fail()) { syslog(LOG_ERR, "Failed to open file %s.", kDefaultBackupFilePath); return -1; } cache_file << std::unitbuf; // enable automatic flushing chown(kDefaultBackupFilePath, 0, 0); chmod(kDefaultBackupFilePath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); nss_cache.Reset(); while (!nss_cache.OnLastPage() || nss_cache.HasNextEntry()) { BufferManager buffer_manager(buffer, kPasswdBufferSize); if (!nss_cache.NssGetpwentHelper(&buffer_manager, &pwd, &error_code)) { if (error_code == ERANGE) { syslog(LOG_ERR, "passwd entry size out of range, skipping"); } else if (error_code == EINVAL) { syslog(LOG_ERR, "Malformed passwd entry, skipping"); } else if (error_code == ENOENT) { syslog(LOG_ERR, "Failure getting users, quitting"); break; } else if (error_code == ENOMSG) { // ENOMSG means OS Login is not enabled. break; } continue; } if (strlen(pwd.pw_passwd) == 0) { pwd.pw_passwd = (char *)"*"; } cache_file << pwd.pw_name << ":" << pwd.pw_passwd << ":" << pwd.pw_uid << ":" << pwd.pw_gid << ":" << pwd.pw_gecos << ":" << pwd.pw_dir << ":" << pwd.pw_shell << "\n"; } cache_file.close(); if (error_code == ENOMSG) { remove(kDefaultBackupFilePath); return 0; } else if (error_code == ENOENT) { syslog(LOG_ERR, "Failed to get users, not updating passwd cache file, removing %s.", kDefaultBackupFilePath); // If the cache file already exists, we don't want to overwrite it on a // server error. So remove the backup file and return here. struct stat buffer; if (stat(kDefaultFilePath, &buffer) == 0) { remove(kDefaultBackupFilePath); return 0; } } if (rename(kDefaultBackupFilePath, kDefaultFilePath) != 0) { syslog(LOG_ERR, "Error moving %s to %s.", kDefaultBackupFilePath, kDefaultFilePath); remove(kDefaultBackupFilePath); } return 0; } int refreshgroupcache() { syslog(LOG_INFO, "Refreshing group entry cache"); int error_code = 0; // Temporary buffer to hold passwd entries before writing. char buffer[kPasswdBufferSize]; NssCache nss_cache(kNssGroupCacheSize); std::ofstream cache_file(kDefaultBackupGroupPath); if (cache_file.fail()) { syslog(LOG_ERR, "Failed to open file %s.", kDefaultBackupGroupPath); return -1; } cache_file << std::unitbuf; // enable automatic flushing cache_file.exceptions( cache_file.exceptions() | std::ofstream::failbit | std::ofstream::badbit ); chown(kDefaultBackupGroupPath, 0, 0); chmod(kDefaultBackupGroupPath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); struct group grp; nss_cache.Reset(); std::vector users; while (!nss_cache.OnLastPage() || nss_cache.HasNextEntry()) { BufferManager buffer_manager(buffer, kPasswdBufferSize); if (!nss_cache.NssGetgrentHelper(&buffer_manager, &grp, &error_code)) { if (error_code == ERANGE) { syslog(LOG_ERR, "Group entry size out of range, skipping"); } else if (error_code == EINVAL) { syslog(LOG_ERR, "Malformed group entry, skipping"); } else if (error_code == ENOENT) { syslog(LOG_ERR, "Failure getting groups, quitting"); break; } else if (error_code == ENOMSG) { // ENOMSG means OS Login is not enabled. break; } continue; } std::string name(grp.gr_name); users.clear(); if (!GetUsersForGroup(name, &users, &error_code)) { syslog(LOG_ERR, "Error getting users for group %s (error_code %d), skipping.", grp.gr_name, error_code); continue; } try { cache_file << grp.gr_name << ":" << grp.gr_passwd << ":" << grp.gr_gid << ":"; for (int i = 0; i < (int)users.size(); i++) { if (i > 0) { cache_file << ","; } cache_file << users[i]; } cache_file << "\n"; } catch (std::ofstream::failure e) { syslog(LOG_ERR, "Exception writing file"); error_code = ENOENT; break; } } try { cache_file.close(); } catch (std::ofstream::failure e) { syslog(LOG_ERR, "Exception closing file"); error_code = ENOENT; } if (error_code == ENOMSG) { remove(kDefaultBackupGroupPath); return 0; } else if (error_code == ENOENT) { syslog(LOG_ERR, "Failed to get groups, not updating group cache file, removing %s.", kDefaultBackupGroupPath); // If the cache file already exists, we don't want to overwrite it on a // server error. So remove the backup file and return here. struct stat buffer; if (stat(kDefaultGroupPath, &buffer) == 0) { remove(kDefaultBackupGroupPath); return 0; } } if (rename(kDefaultBackupGroupPath, kDefaultGroupPath) != 0) { syslog(LOG_ERR, "Error moving %s to %s.", kDefaultBackupGroupPath, kDefaultGroupPath); remove(kDefaultBackupGroupPath); } return 0; } int main() { openlog("oslogin_cache_refresh", LOG_PID|LOG_PERROR, LOG_USER); int u_res, g_res; u_res = refreshpasswdcache(); g_res = refreshgroupcache(); closelog(); if (u_res != 0) return u_res; return g_res; } guest-oslogin-20220714.00/src/include/000077500000000000000000000000001426332663300172145ustar00rootroot00000000000000guest-oslogin-20220714.00/src/include/compat.h000066400000000000000000000055531426332663300206600ustar00rootroot00000000000000// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef OSLOGIN_COMPAT_H #define OSLOGIN_COMPAT_H #ifdef __FreeBSD__ #include #define DECLARE_NSS_METHOD_TABLE(name, ...) \ static ns_mtab name[] = {__VA_ARGS__}; #define NSS_METHOD(method, func) { \ .database = NSDB_PASSWD, \ .name = #method, \ .method = __nss_compat_ ## method, \ .mdata = (void*)func \ } #define NSS_REGISTER_METHODS(methods) ns_mtab * \ nss_module_register (const char *name, unsigned int *size, \ nss_module_unregister_fn *unregister) \ { \ *size = sizeof (methods) / sizeof (methods[0]); \ *unregister = NULL; \ return (methods); \ } #define OSLOGIN_PASSWD_CACHE_PATH "/usr/local/etc/oslogin_passwd.cache" #define OSLOGIN_GROUP_CACHE_PATH "/usr/local/etc/oslogin_group.cache" #define PASSWD_PATH "/usr/local/etc/passwd" #define K_DEFAULT_PFILE_PATH "/usr/local/etc/oslogin_passwd.cache" #define K_DEFAULT_BACKUP_PFILE_PATH "/usr/local/etc/oslogin_passwd.cache.bak" #define K_DEFAULT_GFILE_PATH "/usr/local/etc/oslogin_group.cache" #define K_DEFAULT_BACKUP_GFILE_PATH "/usr/local/etc/oslogin_group.cache.bak" #define PAM_SYSLOG(pamh, ...) syslog(__VA_ARGS__) #define DEFAULT_SHELL "/bin/sh" #define DEFAULT_PASSWD "*" #else /* __FreeBSD__ */ #include #define OSLOGIN_PASSWD_CACHE_PATH "/etc/oslogin_passwd.cache" #define OSLOGIN_GROUP_CACHE_PATH "/etc/oslogin_group.cache" #define PASSWD_PATH "/etc/passwd" #define K_DEFAULT_PFILE_PATH "/etc/oslogin_passwd.cache" #define K_DEFAULT_BACKUP_PFILE_PATH "/etc/oslogin_passwd.cache.bak" #define K_DEFAULT_GFILE_PATH "/etc/oslogin_group.cache" #define K_DEFAULT_BACKUP_GFILE_PATH "/etc/oslogin_group.cache.bak" #define PAM_SYSLOG pam_syslog #define DEFAULT_SHELL "/bin/bash" #define DEFAULT_PASSWD "*" #define DECLARE_NSS_METHOD_TABLE(name, ...) #define NSS_METHOD_PROTOTYPE(m) #define NSS_REGISTER_METHODS(methods) #endif /* __FreeBSD__ */ #endif /* OSLOGIN_COMPAT_H */ guest-oslogin-20220714.00/src/include/nss_cache_oslogin.h000066400000000000000000000032161426332663300230470ustar00rootroot00000000000000// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NSS_CACHE_OSLOGIN_H #define NSS_CACHE_OSLOGIN_H #ifdef DEBUG #undef DEBUG #define DEBUG(fmt, ...) \ do { \ openlog("nss_cache_oslogin", LOG_PID|LOG_PERROR, LOG_DAEMON); \ syslog(LOG_ERR, fmt, ##__VA_ARGS__); \ closelog(); \ } while (0) #else #define DEBUG(fmt, ...) \ do { \ } while (0) #endif /* DEBUG */ // why isn't this in compat.h ? #define NSS_CACHE_OSLOGIN_PATH_LENGTH 255 #endif /* NSS_CACHE_OSLOGIN_H */ guest-oslogin-20220714.00/src/include/oslogin_utils.h000066400000000000000000000252751426332663300222720ustar00rootroot00000000000000// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #define TOTP "TOTP" #define AUTHZEN "AUTHZEN" #define INTERNAL_TWO_FACTOR "INTERNAL_TWO_FACTOR" #define IDV_PREREGISTERED_PHONE "IDV_PREREGISTERED_PHONE" #define SECURITY_KEY_OTP "SECURITY_KEY_OTP" #define INITGROUP_CACHE_EXPIRE_SECONDS 500 #ifdef DEBUG #undef DEBUG #define DEBUG(fmt, ...) \ do { \ openlog("nss_oslogin", LOG_PID|LOG_PERROR, LOG_DAEMON); \ syslog(LOG_ERR, fmt, ##__VA_ARGS__); \ closelog(); \ } while (0) #else #define DEBUG(fmt, ...) \ do { \ } while (0) #endif /* DEBUG */ using std::string; using std::vector; namespace oslogin_utils { // Metadata server URL. static const char kMetadataServerUrl[] = "http://169.254.169.254/computeMetadata/v1/oslogin/"; // BufferManager encapsulates and manages a buffer and length. This class is not // thread safe. class BufferManager { public: // Create a BufferManager that will dole out chunks of buf as requested. BufferManager(char* buf, size_t buflen); // Copies a string to the buffer and sets the buffer to point to that // string. Copied string is guaranteed to be null-terminated. // Returns false and sets errnop if there is not enough space left in the // buffer for the string. bool AppendString(const string& value, char** buffer, int* errnop); // Return a pointer to a buffer of size bytes. Returns NULL and sets errnop to // ERANGE if there is not enough space left in the buffer for the request. void* Reserve(size_t bytes, int* errnop); private: // Whether there is space available in the buffer. bool CheckSpaceAvailable(size_t bytes_to_write) const; char* buf_; size_t buflen_; // Not copyable or assignable. BufferManager& operator=(const BufferManager&); BufferManager(const BufferManager&); }; // Challenge represents a security challenge available to the user. class Challenge { public: int id; string type; string status; }; class Group { public: int64_t gid; string name; }; // NssCache caches passwd entries for getpwent_r. This is used to prevent making // an HTTP call on every getpwent_r invocation. Stores up to cache_size entries // at a time. This class is not thread safe. class NssCache { public: explicit NssCache(int cache_size); // Clears and resets the NssCache. void Reset(); // Whether the cache has a next entry. bool HasNextEntry(); // Whether the cache has reached the last page of the database. bool OnLastPage() { return on_last_page_; } // Grabs the next passwd or group entry. Returns true on success. Sets errnop on // failure. bool GetNextPasswd(BufferManager* buf, struct passwd* result, int* errnop); bool GetNextGroup(BufferManager* buf, struct group* result, int* errnop); // Loads a json array of passwd or group entries in the cache, starting at the // beginning of the cache. This will remove all previous entries in the cache. // response is expected to be a JSON array of passwd or group entries. Returns // true on success. bool LoadJsonUsersToCache(string response); bool LoadJsonGroupsToCache(string response, int* errnop); // Helper method for get(pw|gr)ent nss methods. Each call will iterate through the // OsLogin database and return the next entry. Internally, the cache will // keep track of pages of user or group entries, and will make an http call to // the server if necessary to retrieve additional entries. Returns whether // retrieval was successful. If true, the result will contain // valid data. bool NssGetpwentHelper(BufferManager* buf, struct passwd* result, int* errnop); bool NssGetgrentHelper(BufferManager* buf, struct group* result, int* errnop); // Returns the page token for requesting the next page of entries. string GetPageToken() { return page_token_; } private: // The maximum size of the cache. int cache_size_; // Vector of entries. These are represented as stringified json object. std::vector entry_cache_; // The page token for requesting the next page of entries. std::string page_token_; // Index for requesting the next entry from the cache. uint32_t index_; // Whether the NssCache has reached the last page of the database. bool on_last_page_; // Not copyable or assignable. NssCache& operator=(const NssCache&); NssCache(const NssCache&); }; // Auto locks and unlocks a given mutex on construction/destruction. Does NOT // take ownership of the mutex. class MutexLock { public: explicit MutexLock(pthread_mutex_t* mutex) : mutex_(mutex) { pthread_mutex_lock(mutex_); } ~MutexLock() { pthread_mutex_unlock(mutex_); } private: // The mutex to lock/unlock pthread_mutex_t* const mutex_; // Not copyable or assignable. MutexLock& operator=(const MutexLock); MutexLock(const MutexLock&); }; // Callback invoked when Curl completes a request. size_t OnCurlWrite(void* buf, size_t size, size_t nmemb, void* userp); // Uses Curl to issue a GET request to the given url. Returns whether the // request was successful. If successful, the result from the server will be // stored in response, and the HTTP response code will be stored in http_code. bool HttpGet(const string& url, string* response, long* http_code); bool HttpPost(const string& url, const string& data, string* response, long* http_code); // Returns whether user_name is a valid OsLogin user name. bool ValidateUserName(const string& user_name); // URL encodes the given parameter. Returns the encoded parameter. std::string UrlEncode(const string& param); // Returns true if the given passwd contains valid fields. If pw_dir, pw_shell, // or pw_passwd are not set, this will populate these entries with default // values. bool ValidatePasswd(struct passwd* result, BufferManager* buf, int* errnop); // Adds users and associated array of char* to provided buffer and store pointer // to array in result.gr_mem. bool AddUsersToGroup(std::vector users, struct group* result, BufferManager* buf, int* errnop); // Iterates through all groups until one matching provided group is found, // replacing gr_name with a buffermanager provided string. bool FindGroup(struct group* grp, BufferManager* buf, int* errnop); // Iterates through all users for a group, storing results in a provided string // vector. bool GetUsersForGroup(string groupname, std::vector* users, int* errnop); // Iterates through all groups for a user, storing results in a provided string // vector. bool GetGroupsForUser(string username, std::vector* groups, int* errnop); // Parses a JSON groups response, storing results in a provided Group vector. bool ParseJsonToGroups(const string& json, std::vector* groups); // Parses a JSON users response, storing results in a provided string vector. bool ParseJsonToUsers(const string& json, std::vector* users); // Adds users and associated array of char* to provided buffer and store pointer // to array in result.gr_mem. bool AddUsersToGroup(std::vector users, struct group* result, BufferManager* buf, int* errnop); // Gets group matching name. bool GetGroupByName(string name, struct group* grp, BufferManager* buf, int* errnop); // Gets group matching GID. bool GetGroupByGID(int gid, struct group* grp, BufferManager* buf, int* errnop); // Iterates through all users for a group, storing results in a provided string vector. bool GetUsersForGroup(string groupname, std::vector* users, int* errnop); // Iterates through all groups for a user, storing results in a provided string vector. bool GetGroupsForUser(string username, std::vector* groups, int* errnop); // Parses a JSON groups response, storing results in a provided Group vector. bool ParseJsonToGroups(const string& json, std::vector* groups); // Parses a JSON users response, storing results in a provided string vector. bool ParseJsonToUsers(const string& json, std::vector *users); // Parses a JSON LoginProfiles response for SSH keys. Returns a vector of valid // ssh_keys. A key is considered valid if it's expiration date is greater than // current unix time. std::vector ParseJsonToSshKeys(const string& json); std::vector ParseJsonToSshKeysSk(const string& json); // Parses a JSON object and returns the value associated with a given key. bool ParseJsonToKey(const string& json, const string& key, string* response); // Parses a JSON LoginProfiles response and returns the email under the "name" // field. bool ParseJsonToEmail(const string& json, string* email); // Parses a JSON LoginProfiles response and populates the passwd struct with the // corresponding values set in the JSON object. Returns whether the parse was // successful or not. If unsuccessful, errnop will also be set. bool ParseJsonToPasswd(const string& response, struct passwd* result, BufferManager* buf, int* errnop); bool ParseJsonToGroup(const string& response, struct group* result, BufferManager* buf, int* errnop); // Parses a JSON adminLogin or login response and returns whether the user has // the requested privilege. bool ParseJsonToSuccess(const string& json); // Parses a JSON startSession response into a vector of Challenge objects. bool ParseJsonToChallenges(const string& json, vector* challenges); // Calls the startSession API. bool StartSession(const string& email, string* response); // Calls the continueSession API. bool ContinueSession(bool alt, const string& email, const string& user_token, const string& session_id, const Challenge& challenge, string* response); // Returns user information from the metadata server. bool GetUser(const string& username, string* response); } // namespace oslogin_utils guest-oslogin-20220714.00/src/nss/000077500000000000000000000000001426332663300163745ustar00rootroot00000000000000guest-oslogin-20220714.00/src/nss/compat/000077500000000000000000000000001426332663300176575ustar00rootroot00000000000000guest-oslogin-20220714.00/src/nss/compat/getpwent_r.c000066400000000000000000000047371426332663300222140ustar00rootroot00000000000000/* * ---------------------------------------------------------------------- * Copyright © 2005-2014 Rich Felker, et al. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ---------------------------------------------------------------------- * * Adapted from http://www.musl-libc.org/ for libnss-cache * Copyright © 2015 Kevin Bowling */ #include #ifdef BSD #include #include #include #include #include static unsigned atou(char **s) { unsigned x; for (x=0; **s-'0'<10U; ++*s) x=10*x+(**s-'0'); return x; } int fgetpwent_r(FILE *f, struct passwd *pw, char *line, size_t size, struct passwd **res) { char *s; int rv = 0; for (;;) { line[size-1] = '\xff'; if ( (fgets(line, size, f) == NULL) || ferror(f) || line[size-1] != '\xff' ) { rv = (line[size-1] != '\xff') ? ERANGE : ENOENT; line = 0; pw = 0; break; } line[strcspn(line, "\n")] = 0; s = line; pw->pw_name = s++; if (!(s = strchr(s, ':'))) continue; *s++ = 0; pw->pw_passwd = s; if (!(s = strchr(s, ':'))) continue; *s++ = 0; pw->pw_uid = atou(&s); if (*s != ':') continue; *s++ = 0; pw->pw_gid = atou(&s); if (*s != ':') continue; *s++ = 0; pw->pw_gecos = s; if (!(s = strchr(s, ':'))) continue; *s++ = 0; pw->pw_dir = s; if (!(s = strchr(s, ':'))) continue; *s++ = 0; pw->pw_shell = s; break; } *res = pw; if (rv) errno = rv; return rv; } #endif // ifdef BSD guest-oslogin-20220714.00/src/nss/new_nss_oslogin.c000066400000000000000000000236361426332663300217600ustar00rootroot00000000000000// Copyright 2020 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_GR_MEM 100 #define MAX_ARGLEN 100 #define PW_NAME 0 #define PW_PASSWD 1 #define PW_UID 2 #define PW_GID 3 #define PW_GECOS 4 #define PW_DIR 5 #define PW_SHELL 6 #define PW_END 7 #define GR_NAME 0 #define GR_PASSWD 1 #define GR_GID 2 #define GR_MEM 3 #define GR_END 4 #define SOCK_PATH "/var/run/oslogin" #define BUFSIZE 1024 #define MAXBUFSIZE 32768 #define LEN(index) ((fields[index+1] - fields[index]) - 1) #define COPYINT(index, inner_result) \ do { \ memset(buffer, 0, buflen); \ memcpy(buffer, &str[fields[index]], LEN(index)); \ buffer[LEN(index)+1] = '\0'; \ inner_result = atoi(buffer); \ } while(0) #define COPYSTR(index, inner_result) \ do { \ inner_result = buffer; \ memcpy(buffer, &str[fields[index]], LEN(index)); \ buffer[LEN(index)+1] = '\0'; \ buffer += LEN(index)+1; \ } while(0) #define MIN(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) #define DEBUGF(...) \ do { \ fprintf (stderr, __VA_ARGS__); \ } while(0) int parsepasswd(char *str, struct passwd *result, char *buffer, size_t buflen) { int fields[PW_END+1] = {0}; fields[PW_END] = strlen(str)+1; if (fields[PW_END] > (int)buflen) { return ERANGE; } int i, field; for(field = 1, i = 0; i < fields[PW_END]; i++) { if (str[i] == ':') { fields[field++] = i+1; } } if (field != PW_END) { return ENOENT; } COPYINT(PW_UID, result->pw_uid); COPYINT(PW_GID, result->pw_gid); memset(buffer, 0, fields[PW_END]); COPYSTR(PW_NAME, result->pw_name); COPYSTR(PW_PASSWD, result->pw_passwd); COPYSTR(PW_GECOS, result->pw_gecos); COPYSTR(PW_DIR, result->pw_dir); COPYSTR(PW_SHELL, result->pw_shell); return 0; } int parsegroup(char *str, struct group *result, char *buffer, size_t buflen) { int fields[GR_END+1] = {0}; int members[MAX_GR_MEM] = {0}; int i, field, len; char **bufp; // Check whether buffer can fit the string. fields[GR_END] = strlen(str)+1; if (fields[GR_END] > (int)buflen) { return ERANGE; } // Record field indexes. for(field = 1, i = 0; i < fields[GR_END]; i++) { if (str[i] == ':') { fields[field++] = i+1; } } // Wrong number of fields in record. if (field != GR_END) { return ENOENT; } // Record member indexes. members[0] = fields[GR_MEM]; for(field = 1, i = fields[GR_MEM]; i < fields[GR_END]; i++) { if (str[i] == ',') { members[field++] = i+1; } } members[field] = fields[GR_END]; // Check whether the buffer can fit the char* array. if ((fields[GR_END] + ((field+1) * sizeof(char *))) > buflen) { return ERANGE; } COPYINT(GR_GID, result->gr_gid); memset(buffer, 0, fields[GR_END]); COPYSTR(GR_NAME, result->gr_name); COPYSTR(GR_PASSWD, result->gr_passwd); result->gr_mem = bufp = (char **)buffer; buffer += (sizeof(char *) * (field + 1)); for(i = 0; i < field; i++) { len = ((members[i+1] - members[i]) - 1); memcpy(buffer, &str[members[i]], len); buffer[len+1] = '\0'; *(bufp++) = buffer; buffer += len+1; } *bufp = NULL; return 0; } struct Buffer { ssize_t buflen; // how much data we read into the buffer ssize_t bufsize; // allocated space for buffer char *buf; // the buffer we copy results into int socket; }; struct Buffer pwbuf; struct Buffer grbuf; int dial(struct Buffer *const buffer) { if (buffer->socket != 0) { return 0; } if ((buffer->socket = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)) == -1) { return -1; } int len; struct sockaddr_un remote; remote.sun_family = AF_UNIX; strcpy(remote.sun_path, SOCK_PATH); len = strlen(remote.sun_path) + sizeof(remote.sun_family); if (connect(buffer->socket, (struct sockaddr *)&remote, len) == -1) { return -1; } return 0; } int recvline(struct Buffer *const buffer) { int res = 0; ssize_t recvlen, new_size = 0; fd_set fds; struct timeval tmout = {2,0}; // TODO: catch malloc errors char *recvbuf = (char *)malloc(BUFSIZE); while(1) { FD_ZERO(&fds); FD_SET(buffer->socket, &fds); res = select(buffer->socket+1, &fds, NULL, NULL, &tmout); if (res <= 0 || !(FD_ISSET(buffer->socket, &fds))) { free(recvbuf); return -1; } if ((recvlen = recv(buffer->socket, recvbuf, BUFSIZE, 0)) <= 0) { free(recvbuf); return -1; } // Determine if buffer needs resizing. if ((buffer->buflen + recvlen) > buffer->bufsize) { new_size = MIN((buffer->bufsize * 2), MAXBUFSIZE); if (new_size == buffer->bufsize) { // We were already at limit! free(recvbuf); return -1; } if (realloc(buffer->buf, new_size) == NULL) { free(recvbuf); return -1; } buffer->bufsize = new_size; } memcpy(&(buffer->buf[buffer->buflen]), recvbuf, recvlen); buffer->buflen += recvlen; if (recvbuf[recvlen - 1] == '\n') { free(recvbuf); return buffer->buflen; } } free(recvbuf); return -1; // Unreachable code. } static enum nss_status _nss_oslogin_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errnop) { int res; struct Buffer mgr; memset(&mgr, 0, sizeof(struct Buffer)); *errnop = 0; if (dial(&mgr) != 0) { *errnop = ENOENT; return NSS_STATUS_UNAVAIL; } // send the verb GETPWNAM with the argument // TODO: validate incoming length of 'name' fits in 100 char char str[MAX_ARGLEN]; sprintf(str, "GETPWNAM %s\n", name); if ((res = send(mgr.socket, str, strlen(str), 0)) == -1) { return NSS_STATUS_NOTFOUND; } mgr.bufsize = BUFSIZE; mgr.buf = (char *)malloc(BUFSIZE); if ((recvline(&mgr)) < 0) { free(mgr.buf); return NSS_STATUS_NOTFOUND; } if (mgr.buf[0] == '\n') { free(mgr.buf); return NSS_STATUS_NOTFOUND; } res = parsepasswd(mgr.buf, result, buffer, buflen); free(mgr.buf); if (res == 0) { return NSS_STATUS_SUCCESS; } *errnop = res; if (res == ERANGE) { return NSS_STATUS_TRYAGAIN; } return NSS_STATUS_NOTFOUND; } static enum nss_status _nss_oslogin_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errnop) { int res; struct Buffer mgr; memset(&mgr, 0, sizeof(struct Buffer)); *errnop = 0; if (dial(&mgr) != 0) { *errnop = ENOENT; return NSS_STATUS_UNAVAIL; } // send the verb GETPWUID with the argument // TODO: validate incoming length of 'uid' fits in 100 char char str[MAX_ARGLEN]; sprintf(str, "GETPWUID %d\n", uid); if (send(mgr.socket, str, strlen(str), 0) == -1) { return NSS_STATUS_NOTFOUND; } mgr.bufsize = BUFSIZE; mgr.buf = (char *)malloc(BUFSIZE); if ((recvline(&mgr)) < 0) { free(mgr.buf); return NSS_STATUS_NOTFOUND; } if (mgr.buf[0] == '\n') { free(mgr.buf); return NSS_STATUS_NOTFOUND; } res = parsepasswd(mgr.buf, result, buffer, buflen); free(mgr.buf); if (res == 0) { return NSS_STATUS_SUCCESS; } *errnop = res; if (res == ERANGE) { return NSS_STATUS_TRYAGAIN; } return NSS_STATUS_NOTFOUND; } static enum nss_status _nss_oslogin_getgrnam_r(const char *name, struct group *result, char *buffer, size_t buflen, int *errnop) { int res; struct Buffer mgr; memset(&mgr, 0, sizeof(struct Buffer)); *errnop = 0; if (dial(&mgr) != 0) { *errnop = ENOENT; return NSS_STATUS_UNAVAIL; } // send the verb GETPWNAM with the argument // TODO: validate incoming length of 'name' fits in 100 char char str[MAX_ARGLEN]; sprintf(str, "GETGRNAM %s\n", name); if (send(mgr.socket, str, strlen(str), 0) == -1) { return NSS_STATUS_NOTFOUND; } mgr.bufsize = BUFSIZE; mgr.buf = (char *)malloc(BUFSIZE); if ((recvline(&mgr)) < 0) { free(mgr.buf); return NSS_STATUS_NOTFOUND; } if (mgr.buf[0] == '\n') { free(mgr.buf); return NSS_STATUS_NOTFOUND; } res = parsegroup(mgr.buf, result, buffer, buflen); free(mgr.buf); if (res == 0) { return NSS_STATUS_SUCCESS; } *errnop = res; if (res == ERANGE) { return NSS_STATUS_TRYAGAIN; } return NSS_STATUS_NOTFOUND; } static enum nss_status _nss_oslogin_getgrgid_r(gid_t gid, struct group *result, char *buffer, size_t buflen, int *errnop) { int res; struct Buffer mgr; memset(&mgr, 0, sizeof(struct Buffer)); *errnop = 0; if (dial(&mgr) != 0) { *errnop = ENOENT; return NSS_STATUS_UNAVAIL; } // send the verb GETGRGID with the argument char str[MAX_ARGLEN]; sprintf(str, "GETGRGID %d\n", gid); if (send(mgr.socket, str, strlen(str), 0) == -1) { return NSS_STATUS_NOTFOUND; } mgr.bufsize = BUFSIZE; mgr.buf = (char *)malloc(BUFSIZE); if ((recvline(&mgr)) < 0) { free(mgr.buf); return NSS_STATUS_NOTFOUND; } if (mgr.buf[0] == '\n') { free(mgr.buf); return NSS_STATUS_NOTFOUND; } res = parsegroup(mgr.buf, result, buffer, buflen); free(mgr.buf); if (res == 0) { return NSS_STATUS_SUCCESS; } *errnop = res; if (res == ERANGE) { return NSS_STATUS_TRYAGAIN; } return NSS_STATUS_NOTFOUND; } guest-oslogin-20220714.00/src/nss/nss_cache_oslogin.c000066400000000000000000000325371426332663300222320ustar00rootroot00000000000000// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // An NSS module which adds supports for file /etc/oslogin_passwd.cache #include #include #include #include #include // Locking implementation: use pthreads. #include static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define NSS_CACHE_OSLOGIN_LOCK() \ do { \ pthread_mutex_lock(&mutex); \ } while (0) #define NSS_CACHE_OSLOGIN_UNLOCK() \ do { \ pthread_mutex_unlock(&mutex); \ } while (0) static FILE *p_file = NULL; static FILE *g_file = NULL; #ifdef BSD extern int fgetpwent_r(FILE *, struct passwd *, char *, size_t, struct passwd **); extern int fgetgrent_r(FILE *, struct group *, char *, size_t, struct group **); #endif // ifdef BSD /* Common return code routine for all *ent_r_locked functions. * We need to return TRYAGAIN if the underlying files guy raises ERANGE, * so that our caller knows to try again with a bigger buffer. */ static inline enum nss_status _nss_cache_oslogin_ent_bad_return_code( int errnoval) { enum nss_status ret; switch (errnoval) { case ERANGE: DEBUG("ERANGE: Try again with a bigger buffer\n"); ret = NSS_STATUS_TRYAGAIN; break; case ENOENT: default: DEBUG("ENOENT or default case: Not found\n"); ret = NSS_STATUS_NOTFOUND; }; return ret; } // // Routines for passwd map defined below here // // _nss_cache_oslogin_setpwent_locked() // Internal setup routine static enum nss_status _nss_cache_oslogin_setpwent_locked(void) { DEBUG("%s %s\n", "Opening", OSLOGIN_PASSWD_CACHE_PATH); if (p_file) { fclose(p_file); } p_file = fopen(OSLOGIN_PASSWD_CACHE_PATH, "re"); if (p_file) { return NSS_STATUS_SUCCESS; } else { return NSS_STATUS_UNAVAIL; } } // _nss_cache_oslogin_setpwent() // Called by NSS to open the passwd file // 'stayopen' parameter is ignored. enum nss_status _nss_cache_oslogin_setpwent(int stayopen) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_setpwent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_endpwent_locked() // Internal close routine static enum nss_status _nss_cache_oslogin_endpwent_locked(void) { DEBUG("Closing %s\n", OSLOGIN_PASSWD_CACHE_PATH); if (p_file) { fclose(p_file); p_file = NULL; } return NSS_STATUS_SUCCESS; } // _nss_cache_oslogin_endpwent() // Called by NSS to close the passwd file enum nss_status _nss_cache_oslogin_endpwent(void) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_endpwent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_getpwent_r_locked() // Called internally to return the next entry from the passwd file static enum nss_status _nss_cache_oslogin_getpwent_r_locked( struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret = NSS_STATUS_SUCCESS; if (p_file == NULL) { DEBUG("p_file == NULL, going to setpwent\n"); ret = _nss_cache_oslogin_setpwent_locked(); } if (ret == NSS_STATUS_SUCCESS) { if (fgetpwent_r(p_file, result, buffer, buflen, &result) == 0) { DEBUG("Returning user %s (%u)\n", result->pw_name, result->pw_uid); } else { if (errno == ENOENT) { errno = 0; } *errnop = errno; ret = _nss_cache_oslogin_ent_bad_return_code(*errnop); } } return ret; } // _nss_cache_oslogin_getpwent_r() // Called by NSS to look up next entry in passwd file enum nss_status _nss_cache_oslogin_getpwent_r(struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_getpwent_r_locked(result, buffer, buflen, errnop); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_getpwuid_r() // Find a user account by uid enum nss_status _nss_cache_oslogin_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_setpwent_locked(); if (ret == NSS_STATUS_SUCCESS) { while ((ret = _nss_cache_oslogin_getpwent_r_locked( result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) { if (result->pw_uid == uid) break; } } _nss_cache_oslogin_endpwent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_getpwnam_r() // Find a user account by name enum nss_status _nss_cache_oslogin_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_setpwent_locked(); if (ret == NSS_STATUS_SUCCESS) { while ((ret = _nss_cache_oslogin_getpwent_r_locked( result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) { if (!strcmp(result->pw_name, name)) break; } } _nss_cache_oslogin_endpwent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_setgrent_locked() // Internal setup routine static enum nss_status _nss_cache_oslogin_setgrent_locked(void) { if (g_file) { fclose(g_file); } g_file = fopen(OSLOGIN_GROUP_CACHE_PATH, "re"); if (g_file) { return NSS_STATUS_SUCCESS; } else { return NSS_STATUS_UNAVAIL; } } // _nss_cache_oslogin_setgrent() // Called by NSS to open the group file // 'stayopen' parameter is ignored. enum nss_status _nss_cache_oslogin_setgrent(int stayopen) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_setgrent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_endgrent_locked() // Internal close routine static enum nss_status _nss_cache_oslogin_endgrent_locked(void) { DEBUG("%s %s\n", "Closing", OSLOGIN_GROUP_CACHE_PATH); if (g_file) { fclose(g_file); g_file = NULL; } return NSS_STATUS_SUCCESS; } // _nss_cache_oslogin_endgrent() // Called by NSS to close the group file enum nss_status _nss_cache_oslogin_endgrent(void) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_endgrent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_getgrent_r_locked() // Called internally to return the next entry from the group file static enum nss_status _nss_cache_oslogin_getgrent_r_locked(struct group *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret = NSS_STATUS_SUCCESS; if (g_file == NULL) { DEBUG("g_file == NULL, going to setgrent\n"); ret = _nss_cache_oslogin_setgrent_locked(); } if (ret == NSS_STATUS_SUCCESS) { fpos_t position; fgetpos(g_file, &position); if (fgetgrent_r(g_file, result, buffer, buflen, &result) == 0) { DEBUG("Returning group %s (%u)\n", result->gr_name, result->gr_gid); } else { /* Rewind back to where we were just before, otherwise the data read * into the buffer is probably going to be lost because there's no * guarantee that the caller is going to have preserved the line we * just read. Note that glibc's nss/nss_files/files-XXX.c does * something similar in CONCAT(_nss_files_get,ENTNAME_r) (around * line 242 in glibc 2.4 sources). */ if (errno == ENOENT) { errno = 0; } else { fsetpos(g_file, &position); } *errnop = errno; ret = _nss_cache_oslogin_ent_bad_return_code(*errnop); } } return ret; } // _nss_cache_oslogin_getgrent_r() // Called by NSS to look up next entry in group file enum nss_status _nss_cache_oslogin_getgrent_r(struct group *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret; NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_getgrent_r_locked(result, buffer, buflen, errnop); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_getgrgid_r() // Find a group by gid enum nss_status _nss_cache_oslogin_getgrgid_r(gid_t gid, struct group *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret; // First check for user whose UID matches requested GID, for self-groups. struct passwd user; size_t userbuflen = 1024; char userbuf[userbuflen]; ret = _nss_cache_oslogin_getpwuid_r(gid, &user, userbuf, userbuflen, errnop); if (ret == NSS_STATUS_SUCCESS && user.pw_gid == user.pw_uid) { result->gr_gid = user.pw_gid; // store "x" for password. char* string = buffer; strncpy(string, "x", 2); result->gr_passwd = string; // store name. string = (char *)((size_t) string + 2); size_t name_len = strlen(user.pw_name)+1; strncpy(string, user.pw_name, name_len); result->gr_name = string; // member array starts past strings. char **strarray = (char **)((size_t) string + name_len); strarray[0] = string; strarray[1] = NULL; result->gr_mem = strarray; return NSS_STATUS_SUCCESS; } NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_setgrent_locked(); if (ret == NSS_STATUS_SUCCESS) { while ((ret = _nss_cache_oslogin_getgrent_r_locked(result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) { if (result->gr_gid == gid) break; } } _nss_cache_oslogin_endgrent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } // _nss_cache_oslogin_getgrnam_r() // Find a group by name enum nss_status _nss_cache_oslogin_getgrnam_r(const char *name, struct group *result, char *buffer, size_t buflen, int *errnop) { enum nss_status ret; // First check for user whose name matches request, for self-groups. struct passwd user; size_t userbuflen = 1024; char userbuf[userbuflen]; ret = _nss_cache_oslogin_getpwnam_r(name, &user, userbuf, userbuflen, errnop); if (ret == NSS_STATUS_SUCCESS && user.pw_gid == user.pw_uid) { result->gr_gid = user.pw_gid; // store "x" for password. char* string = buffer; strncpy(string, "x", 2); result->gr_passwd = string; // store name. string = (char *)((size_t) string + 2); size_t name_len = strlen(user.pw_name)+1; strncpy(string, user.pw_name, name_len); result->gr_name = string; // member array starts past strings. char **strarray = (char **)((size_t) string + name_len); strarray[0] = string; strarray[1] = NULL; result->gr_mem = strarray; return NSS_STATUS_SUCCESS; } NSS_CACHE_OSLOGIN_LOCK(); ret = _nss_cache_oslogin_setgrent_locked(); if (ret == NSS_STATUS_SUCCESS) { while ((ret = _nss_cache_oslogin_getgrent_r_locked(result, buffer, buflen, errnop)) == NSS_STATUS_SUCCESS) { if (!strcmp(result->gr_name, name)) break; } } _nss_cache_oslogin_endgrent_locked(); NSS_CACHE_OSLOGIN_UNLOCK(); return ret; } NSS_METHOD_PROTOTYPE(__nss_compat_getpwnam_r); NSS_METHOD_PROTOTYPE(__nss_compat_getpwuid_r); NSS_METHOD_PROTOTYPE(__nss_compat_getpwent_r); NSS_METHOD_PROTOTYPE(__nss_compat_setpwent); NSS_METHOD_PROTOTYPE(__nss_compat_endpwent); NSS_METHOD_PROTOTYPE(__nss_compat_getgrnam_r); NSS_METHOD_PROTOTYPE(__nss_compat_getgrgid_r); NSS_METHOD_PROTOTYPE(__nss_compat_getgrent_r); NSS_METHOD_PROTOTYPE(__nss_compat_setgrent); NSS_METHOD_PROTOTYPE(__nss_compat_endgrent); DECLARE_NSS_METHOD_TABLE(methods, { NSDB_PASSWD, "getpwnam_r", __nss_compat_getpwnam_r, (void*)_nss_cache_oslogin_getpwnam_r }, { NSDB_PASSWD, "getpwuid_r", __nss_compat_getpwuid_r, (void*)_nss_cache_oslogin_getpwuid_r }, { NSDB_PASSWD, "getpwent_r", __nss_compat_getpwent_r, (void*)_nss_cache_oslogin_getpwent_r }, { NSDB_PASSWD, "endpwent", __nss_compat_endpwent, (void*)_nss_cache_oslogin_endpwent }, { NSDB_PASSWD, "setpwent", __nss_compat_setpwent, (void*)_nss_cache_oslogin_setpwent }, { NSDB_PASSWD, "getgrnam_r", __nss_compat_getgrnam_r, (void*)_nss_cache_oslogin_getgrnam_r }, { NSDB_PASSWD, "getgrgid_r", __nss_compat_getgrgid_r, (void*)_nss_cache_oslogin_getgrgid_r }, { NSDB_PASSWD, "getgrent_r", __nss_compat_getgrent_r, (void*)_nss_cache_oslogin_getgrent_r }, { NSDB_PASSWD, "endgrent", __nss_compat_endgrent, (void*)_nss_cache_oslogin_endgrent }, { NSDB_PASSWD, "setgrent", __nss_compat_setgrent, (void*)_nss_cache_oslogin_setgrent }, ) NSS_REGISTER_METHODS(methods) guest-oslogin-20220714.00/src/nss/nss_oslogin.cc000066400000000000000000000322031426332663300212400ustar00rootroot00000000000000// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAXBUFSIZE 32768 using std::string; using oslogin_utils::AddUsersToGroup; using oslogin_utils::BufferManager; using oslogin_utils::GetGroupByName; using oslogin_utils::GetGroupByGID; using oslogin_utils::GetGroupsForUser; using oslogin_utils::GetUsersForGroup; using oslogin_utils::Group; using oslogin_utils::HttpGet; using oslogin_utils::kMetadataServerUrl; using oslogin_utils::ParseJsonToPasswd; using oslogin_utils::UrlEncode; extern "C" { // Get a passwd entry by id. enum nss_status _nss_oslogin_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errnop) { BufferManager buffer_manager(buffer, buflen); std::stringstream url; url << kMetadataServerUrl << "users?uid=" << uid; string response; long http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { *errnop = ENOENT; return NSS_STATUS_NOTFOUND; } if (!ParseJsonToPasswd(response, result, &buffer_manager, errnop)) { if (*errnop == EINVAL) { openlog("nss_oslogin", LOG_PID, LOG_USER); syslog(LOG_ERR, "Received malformed response from server: %s", response.c_str()); closelog(); } return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; } return NSS_STATUS_SUCCESS; } // Get a passwd entry by name. enum nss_status _nss_oslogin_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errnop) { BufferManager buffer_manager(buffer, buflen); std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(name); string response; long http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { *errnop = ENOENT; return NSS_STATUS_NOTFOUND; } if (!ParseJsonToPasswd(response, result, &buffer_manager, errnop)) { if (*errnop == EINVAL) { openlog("nss_oslogin", LOG_PID, LOG_USER); syslog(LOG_ERR, "Received malformed response from server: %s", response.c_str()); closelog(); } return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; } return NSS_STATUS_SUCCESS; } // Look for OS Login user with uid matching the requested gid, and craft a // self-group for it. enum nss_status getselfgrgid(gid_t gid, struct group *grp, char *buf, size_t buflen, int *errnop) { BufferManager buffer_manager(buf, buflen); // Look for a matching user in cache. FILE *p_file = fopen(OSLOGIN_PASSWD_CACHE_PATH, "re"); if (p_file != NULL) { struct passwd user; struct passwd *userp = NULL; char userbuf[MAXBUFSIZE]; while (fgetpwent_r(p_file, &user, userbuf, MAXBUFSIZE, &userp) == 0) { if (user.pw_uid == gid) { memset(grp, 0, sizeof(struct group)); // Copy from userbuf to user-provided buffer. if (!buffer_manager.AppendString(user.pw_name, &grp->gr_name, errnop)) { fclose(p_file); return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; } // Add user to group. std::vector members; members.push_back(string(user.pw_name)); if (!AddUsersToGroup(members, grp, &buffer_manager, errnop)) { fclose(p_file); return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; } fclose(p_file); return NSS_STATUS_SUCCESS; } } fclose(p_file); } // Look for matching user in backend. std::stringstream url; url << kMetadataServerUrl << "users?uid=" << gid; string response; long http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { return NSS_STATUS_NOTFOUND; } struct passwd result; if (!ParseJsonToPasswd(response, &result, &buffer_manager, errnop)) return NSS_STATUS_NOTFOUND; if (result.pw_gid != result.pw_uid) return NSS_STATUS_NOTFOUND; // Set the group name to the name of the matching user. if (!buffer_manager.AppendString(result.pw_name, &grp->gr_name, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; grp->gr_gid = result.pw_uid; // Create a list of only the matching user and add to members list. std::vector members; members.push_back(string(result.pw_name)); if (!AddUsersToGroup(members, grp, &buffer_manager, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; return NSS_STATUS_SUCCESS; } // Look for OS Login user with name matching the requested name, and craft a // self-group for it. enum nss_status getselfgrnam(const char* name, struct group *grp, char *buf, size_t buflen, int *errnop) { BufferManager buffer_manager(buf, buflen); // Look for a matching user in cache. FILE *p_file = fopen(OSLOGIN_PASSWD_CACHE_PATH, "re"); if (p_file != NULL) { struct passwd user; struct passwd *userp = NULL; char userbuf[MAXBUFSIZE]; while (fgetpwent_r(p_file, &user, userbuf, MAXBUFSIZE, &userp) == 0) { if (strcmp(user.pw_name, name) == 0) { memset(grp, 0, sizeof(struct group)); grp->gr_gid = user.pw_uid; // Add user to group. std::vector members; members.push_back(string(name)); if (!AddUsersToGroup(members, grp, &buffer_manager, errnop)) { fclose(p_file); return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; } fclose(p_file); return NSS_STATUS_SUCCESS; } } fclose(p_file); } // Look for matching user in backend. std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(string(name)); string response; long http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { return NSS_STATUS_NOTFOUND; } struct passwd result; if (!ParseJsonToPasswd(response, &result, &buffer_manager, errnop)) return NSS_STATUS_NOTFOUND; if (result.pw_gid != result.pw_uid) return NSS_STATUS_NOTFOUND; // Set the group name to the name of the matching user. if (!buffer_manager.AppendString(result.pw_name, &grp->gr_name, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; grp->gr_gid = result.pw_uid; // Create a list of only the matching user and add to members list. std::vector members; members.push_back(string(result.pw_name)); if (!AddUsersToGroup(members, grp, &buffer_manager, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; return NSS_STATUS_SUCCESS; } // _nss_olosing_getgrgid_r() // Get a group entry by id. enum nss_status _nss_oslogin_getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen, int *errnop) { // If there is no cache file, we will assume there are no groups. if (access(OSLOGIN_GROUP_CACHE_PATH, R_OK) != 0) return getselfgrgid(gid, grp, buf, buflen, errnop); memset(grp, 0, sizeof(struct group)); BufferManager buffer_manager(buf, buflen); if (!GetGroupByGID(gid, grp, &buffer_manager, errnop)) { if (*errnop == ERANGE) { return NSS_STATUS_TRYAGAIN; } return getselfgrgid(gid, grp, buf, buflen, errnop); } std::vector users; if (!GetUsersForGroup(grp->gr_name, &users, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; if (!users.empty() && !AddUsersToGroup(users, grp, &buffer_manager, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; return NSS_STATUS_SUCCESS; } // _nss_oslogin_getgrnam_r() // Get a group entry by name. enum nss_status _nss_oslogin_getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, int *errnop) { // If there is no cache file, we will assume there are no groups. if (access(OSLOGIN_GROUP_CACHE_PATH, R_OK) != 0) return getselfgrnam(name, grp, buf, buflen, errnop); memset(grp, 0, sizeof(struct group)); BufferManager buffer_manager(buf, buflen); if (!GetGroupByName(string(name), grp, &buffer_manager, errnop)) { if (*errnop == ERANGE) { return NSS_STATUS_TRYAGAIN; } return getselfgrnam(name, grp, buf, buflen, errnop); } std::vector users; if (!GetUsersForGroup(grp->gr_name, &users, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; if (!users.empty() && !AddUsersToGroup(users, grp, &buffer_manager, errnop)) return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; return NSS_STATUS_SUCCESS; } // _nss_cache_oslogin_initgroups_dyn() // Initialize groups for new session. enum nss_status _nss_oslogin_initgroups_dyn(const char *user, gid_t skipgroup, long int *start, long int *size, gid_t **groupsp, long int limit, int *errnop) { // check if user exists in local passwd DB FILE *p_file = fopen(PASSWD_PATH, "re"); if (p_file == NULL) return NSS_STATUS_NOTFOUND; struct passwd *userp; while ((userp = fgetpwent(p_file)) != NULL) { if (strcmp(userp->pw_name, user) == 0) { fclose(p_file); return NSS_STATUS_NOTFOUND; } } fclose(p_file); std::vector grouplist; if (!GetGroupsForUser(string(user), &grouplist, errnop)) { return NSS_STATUS_NOTFOUND; } gid_t *groups = *groupsp; int i; for (i = 0; i < (int) grouplist.size(); i++) { // Resize the buffer if needed. if (*start == *size) { gid_t *newgroups; long int newsize = 2 * *size; // Stop at limit if provided. if (limit > 0) { if (*size >= limit) { *errnop = ERANGE; return NSS_STATUS_TRYAGAIN; } newsize = MIN(limit, newsize); } newgroups = (gid_t *)realloc(groups, newsize * sizeof(gid_t *)); if (newgroups == NULL) { *errnop = EAGAIN; return NSS_STATUS_TRYAGAIN; } *groupsp = groups = newgroups; *size = newsize; } groups[(*start)++] = grouplist[i].gid; } return NSS_STATUS_SUCCESS; } // nss_getpwent_r() is intentionally left unimplemented. This functionality is // now covered by the nss_cache binary and nss_cache module. nss_status _nss_oslogin_getpwent_r() { return NSS_STATUS_NOTFOUND; } nss_status _nss_oslogin_endpwent() { return NSS_STATUS_SUCCESS; } nss_status _nss_oslogin_setpwent() { return NSS_STATUS_SUCCESS; } nss_status _nss_oslogin_getgrent_r() { return NSS_STATUS_NOTFOUND; } nss_status _nss_oslogin_endgrent() { return NSS_STATUS_SUCCESS; } nss_status _nss_oslogin_setgrent() { return NSS_STATUS_SUCCESS; } NSS_METHOD_PROTOTYPE(__nss_compat_getpwnam_r); NSS_METHOD_PROTOTYPE(__nss_compat_getpwuid_r); NSS_METHOD_PROTOTYPE(__nss_compat_getpwent_r); NSS_METHOD_PROTOTYPE(__nss_compat_setpwent); NSS_METHOD_PROTOTYPE(__nss_compat_endpwent); NSS_METHOD_PROTOTYPE(__nss_compat_getgrnam_r); NSS_METHOD_PROTOTYPE(__nss_compat_getgrgid_r); NSS_METHOD_PROTOTYPE(__nss_compat_getgrent_r); NSS_METHOD_PROTOTYPE(__nss_compat_setgrent); NSS_METHOD_PROTOTYPE(__nss_compat_endgrent); DECLARE_NSS_METHOD_TABLE(methods, {NSDB_PASSWD, "getpwnam_r", __nss_compat_getpwnam_r, (void *)_nss_oslogin_getpwnam_r}, {NSDB_PASSWD, "getpwuid_r", __nss_compat_getpwuid_r, (void *)_nss_oslogin_getpwuid_r}, {NSDB_PASSWD, "getpwent_r", __nss_compat_getpwent_r, (void *)_nss_oslogin_getpwent_r}, {NSDB_PASSWD, "endpwent", __nss_compat_endpwent, (void *)_nss_oslogin_endpwent}, {NSDB_PASSWD, "setpwent", __nss_compat_setpwent, (void *)_nss_oslogin_setpwent}, {NSDB_GROUP, "getgrnam_r", __nss_compat_getgrnam_r, (void *)_nss_oslogin_getgrnam_r}, {NSDB_GROUP, "getgrgid_r", __nss_compat_getgrgid_r, (void *)_nss_oslogin_getgrgid_r}, ) NSS_REGISTER_METHODS(methods) } // extern "C" guest-oslogin-20220714.00/src/oslogin_utils.cc000066400000000000000000000776031426332663300210070ustar00rootroot00000000000000// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Requires libcurl4-openssl-dev libjson0 and libjson0-dev #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__clang__) || __GNUC__ > 4 || \ (__GNUC__ == 4 && \ (__GNUC_MINOR__ > 9 || (__GNUC_MINOR__ == 9 && __GNUC_PATCHLEVEL__ > 0))) #include #define Regex std #else #include #define Regex boost #endif #include #include using std::string; // Maximum number of retries for HTTP requests. const int kMaxRetries = 1; // Regex for validating user names. const char kUserNameRegex[] = "^[a-zA-Z0-9._][a-zA-Z0-9._-]{0,31}$"; namespace oslogin_utils { // ----------------- Buffer Manager ----------------- BufferManager::BufferManager(char* buf, size_t buflen) : buf_(buf), buflen_(buflen) {} bool BufferManager::AppendString(const string& value, char** buffer, int* errnop) { size_t bytes_to_write = value.length() + 1; *buffer = static_cast(Reserve(bytes_to_write, errnop)); if (*buffer == NULL) { return false; } strncpy(*buffer, value.c_str(), bytes_to_write); return true; } bool BufferManager::CheckSpaceAvailable(size_t bytes_to_write) const { if (bytes_to_write > buflen_) { return false; } return true; } void* BufferManager::Reserve(size_t bytes, int* errnop) { if (!CheckSpaceAvailable(bytes)) { *errnop = ERANGE; return NULL; } void* result = buf_; buf_ += bytes; buflen_ -= bytes; return result; } // ----------------- NSS Cache helper ----------------- NssCache::NssCache(int cache_size) : cache_size_(cache_size), entry_cache_(cache_size), page_token_(""), on_last_page_(false) {} void NssCache::Reset() { page_token_ = ""; index_ = 0; entry_cache_.clear(); on_last_page_ = false; } bool NssCache::HasNextEntry() { return (index_ < entry_cache_.size()) && !entry_cache_[index_].empty(); } bool NssCache::GetNextPasswd(BufferManager* buf, struct passwd* result, int* errnop) { if (!HasNextEntry()) { *errnop = ENOENT; return false; } string cached_passwd = entry_cache_[index_++]; return ParseJsonToPasswd(cached_passwd, result, buf, errnop); } bool NssCache::GetNextGroup(BufferManager* buf, struct group* result, int* errnop) { if (!HasNextEntry()) { *errnop = ENOENT; return false; } string cached_passwd = entry_cache_[index_++]; return ParseJsonToGroup(cached_passwd, result, buf, errnop); } bool NssCache::LoadJsonUsersToCache(string response) { Reset(); json_object* root = NULL; root = json_tokener_parse(response.c_str()); if (root == NULL) { return false; } bool ret = false; int arraylen = 0; json_object* login_profiles = NULL; // First grab the page token. json_object* page_token_object; if (json_object_object_get_ex(root, "nextPageToken", &page_token_object)) { page_token_ = json_object_get_string(page_token_object); } else { goto cleanup; } // A page_token of 0 means we are done. This response will not contain any // login profiles. if (page_token_ == "0") { page_token_ = ""; on_last_page_ = true; ret = true; goto cleanup; } // Now grab all of the loginProfiles. if (!json_object_object_get_ex(root, "loginProfiles", &login_profiles)) { goto cleanup; } if (json_object_get_type(login_profiles) != json_type_array) { goto cleanup; } arraylen = json_object_array_length(login_profiles); if (arraylen == 0 || arraylen > cache_size_) { goto cleanup; } for (int i = 0; i < arraylen; i++) { json_object* profile = json_object_array_get_idx(login_profiles, i); entry_cache_.push_back(json_object_to_json_string_ext(profile, JSON_C_TO_STRING_PLAIN)); } ret = true; cleanup: json_object_put(root); return ret; } bool NssCache::LoadJsonGroupsToCache(string response, int* errnop) { Reset(); *errnop = ENOENT; json_object* root = NULL; root = json_tokener_parse(response.c_str()); if (root == NULL) { return false; } bool ret = false; int arraylen = 0; json_object* groups = NULL; // First grab the page token. json_object* page_token_object; if (json_object_object_get_ex(root, "nextPageToken", &page_token_object)) { page_token_ = json_object_get_string(page_token_object); } else { goto cleanup; } // A page_token of 0 for groups is different than for users. This is the last // page, but it WILL contain groups if there are any. if (page_token_ == "0") { on_last_page_ = true; page_token_ = ""; } if (!json_object_object_get_ex(root, "posixGroups", &groups)) { // Valid JSON but no groups, set ENOMSG as a 'no groups' code. *errnop = ENOMSG; goto cleanup; } if (json_object_get_type(groups) != json_type_array) { goto cleanup; } arraylen = json_object_array_length(groups); if (arraylen == 0 || arraylen > cache_size_) { goto cleanup; } for (int i = 0; i < arraylen; i++) { json_object* group = json_object_array_get_idx(groups, i); entry_cache_.push_back(json_object_to_json_string_ext(group, JSON_C_TO_STRING_PLAIN)); } ret = true; *errnop = 0; cleanup: json_object_put(root); return ret; } // Gets the next entry from the cache, refreshing as needed. Returns true if a // passwd entry was loaded into the result parameter. Returns false in all other // cases, setting errno as follows: // // * EINVAL - current user entry was malformed in some way. // * ERANGE - the page of results did not fit into the provided buffer. // * ENOMSG - a 404 error was received when contacting the metadata server, indicating that // OS Login is not enabled in the instance metadata. // * ENOENT - a general failure to load the cache occurred. Behavior of retries // following ENOENT is undefined. bool NssCache::NssGetpwentHelper(BufferManager* buf, struct passwd* result, int* errnop) { if (!HasNextEntry() && !OnLastPage()) { std::stringstream url; url << kMetadataServerUrl << "users?pagesize=" << cache_size_; string page_token = GetPageToken(); if (!page_token.empty()) { url << "&pagetoken=" << page_token; } string response; long http_code = 0; bool status = HttpGet(url.str(), &response, &http_code); // 404 means OS Login is not enabled. if (http_code == 404) { *errnop = ENOMSG; return false; } // General failure to load the cache occurred. if (!status || http_code != 200 || response.empty() || !LoadJsonUsersToCache(response)) { *errnop = ENOENT; return false; } } return (HasNextEntry() && GetNextPasswd(buf, result, errnop)); } // Gets the next entry from the cache, refreshing as needed. Returns true if a // group entry was loaded into the result parameter. Returns false in all other // cases, setting errno as follows: // // * EINVAL - current group entry was malformed in some way. // * ERANGE - the page of results did not fit into the provided buffer. // * ENOMSG - a 404 error was received when contacting the metadata server, indicating that // OS Login is not enabled in the instance metadata. // * ENOENT - a general failure to load the cache occurred. Behavior of retries // following ENOENT is undefined. bool NssCache::NssGetgrentHelper(BufferManager* buf, struct group* result, int* errnop) { if (!HasNextEntry() && !OnLastPage()) { std::stringstream url; url << kMetadataServerUrl << "groups?pagesize=" << cache_size_; string page_token = GetPageToken(); if (!page_token.empty()) { url << "&pagetoken=" << page_token; } string response; long http_code = 0; bool status = HttpGet(url.str(), &response, &http_code); // 404 means OS Login is not enabled. if (http_code == 404) { *errnop = ENOMSG; return false; } // Failed to make the request or empty response. if (!status || http_code != 200 || response.empty()) { *errnop = ENOENT; return false; } // General failure to load the cache occurred. if (!LoadJsonGroupsToCache(response, errnop)) { return false; } } if (!HasNextEntry() || !GetNextGroup(buf, result, errnop)) { return false; } std::vector users; std::string name(result->gr_name); if (!GetUsersForGroup(name, &users, errnop)) { return false; } return AddUsersToGroup(users, result, buf, errnop); } // ----------------- HTTP functions ----------------- size_t OnCurlWrite(void* buf, size_t size, size_t nmemb, void* userp) { if (userp) { std::ostream& os = *static_cast(userp); std::streamsize len = size * nmemb; if (os.write(static_cast(buf), len)) { return len; } } return 0; } bool HttpDo(const string& url, const string& data, string* response, long* http_code) { if (response == NULL || http_code == NULL) { return false; } CURLcode code(CURLE_FAILED_INIT); curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL); CURL* curl = curl_easy_init(); std::ostringstream response_stream; int retry_count = 0; if (curl) { struct curl_slist* header_list = NULL; header_list = curl_slist_append(header_list, "Metadata-Flavor: Google"); if (header_list == NULL) { curl_easy_cleanup(curl); curl_global_cleanup(); return false; } do { response_stream.str(""); response_stream.clear(); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &OnCurlWrite); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_stream); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); if (data != "") { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); } code = curl_easy_perform(curl); if (code != CURLE_OK) { curl_easy_cleanup(curl); curl_global_cleanup(); return false; } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); } while (retry_count++ < kMaxRetries && *http_code == 500); curl_slist_free_all(header_list); } *response = response_stream.str(); curl_easy_cleanup(curl); curl_global_cleanup(); return true; } bool HttpGet(const string& url, string* response, long* http_code) { return HttpDo(url, "", response, http_code); } bool HttpPost(const string& url, const string& data, string* response, long* http_code) { return HttpDo(url, data, response, http_code); } string UrlEncode(const string& param) { CURL* curl = curl_easy_init(); char* encoded = curl_easy_escape(curl, param.c_str(), param.length()); if (encoded == NULL) { curl_easy_cleanup(curl); return ""; } string encoded_param = encoded; curl_free(encoded); curl_easy_cleanup(curl); return encoded_param; } bool ValidateUserName(const string& user_name) { Regex::regex r(kUserNameRegex); return Regex::regex_match(user_name, r); } bool ValidatePasswd(struct passwd* result, BufferManager* buf, int* errnop) { // OS Login disallows uids less than 1000. if (result->pw_uid < 1000) { *errnop = EINVAL; return false; } if (result->pw_gid == 0) { *errnop = EINVAL; return false; } if (strlen(result->pw_name) == 0) { *errnop = EINVAL; return false; } if (strlen(result->pw_dir) == 0) { string home_dir = "/home/"; home_dir.append(result->pw_name); if (!buf->AppendString(home_dir, &result->pw_dir, errnop)) { return false; } } if (strlen(result->pw_shell) == 0) { if (!buf->AppendString(DEFAULT_SHELL, &result->pw_shell, errnop)) { return false; } } if (strlen(result->pw_passwd) == 0) { if (!buf->AppendString(DEFAULT_PASSWD, &result->pw_passwd, errnop)) { return false; } } // OS Login reserves the GECOS field. if (!buf->AppendString("", &result->pw_gecos, errnop)) { return false; } return true; } // ----------------- JSON Parsing ----------------- bool ParseJsonToUsers(const string& json, std::vector* result) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } bool ret = false; json_object* users = NULL; if (!json_object_object_get_ex(root, "usernames", &users)) { ret = true; // means no users, not invalid. goto cleanup; } if (json_object_get_type(users) != json_type_array) { goto cleanup; } for (int idx=0; idx < (int)json_object_array_length(users); idx++) { json_object* user = json_object_array_get_idx(users, idx); const char* username = json_object_get_string(user); result->push_back(string(username)); } ret = true; cleanup: json_object_put(root); return ret; } bool ParseJsonToGroups(const string& json, std::vector* result) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } bool ret = false; json_object* groups = NULL; if (!json_object_object_get_ex(root, "posixGroups", &groups)) { goto cleanup; } if (json_object_get_type(groups) != json_type_array) { goto cleanup; } for (int idx = 0; idx < (int)json_object_array_length(groups); idx++) { json_object* group = json_object_array_get_idx(groups, idx); json_object* gid; if (!json_object_object_get_ex(group, "gid", &gid)) { goto cleanup; } json_object* name; if (!json_object_object_get_ex(group, "name", &name)) { goto cleanup; } Group g; g.gid = json_object_get_int64(gid); // get_int64 will confusingly return 0 if the string can't be converted to // an integer. We can't rely on type check as it may be a string in the API. // Also 0 is invalid because it creates a 'root group'. if (g.gid == 0) { goto cleanup; } g.name = json_object_get_string(name); if (g.name == "") { goto cleanup; } result->push_back(g); } ret = true; cleanup: json_object_put(root); return ret; } bool ParseJsonToGroup(const string& json, struct group* result, BufferManager* buf, int* errnop) { *errnop = EINVAL; int gr_gid = 65535; json_object* group = NULL; group = json_tokener_parse(json.c_str()); if (group == NULL) { return false; } bool ret = false; json_object* gid; if (!json_object_object_get_ex(group, "gid", &gid)) { goto cleanup; } json_object* name; if (!json_object_object_get_ex(group, "name", &name)) { goto cleanup; } if ((gr_gid = json_object_get_int64(gid)) == 0) { goto cleanup; } result->gr_gid = gr_gid; if (!buf->AppendString("", &result->gr_passwd, errnop)) goto cleanup; if (!buf->AppendString(json_object_get_string(name), &result->gr_name, errnop)) goto cleanup; *errnop = 0; ret = true; cleanup: json_object_put(group); return ret; } std::vector ParseJsonToSshKeys(const string& json) { std::vector result; json_object* ssh_public_keys = NULL; json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return result; } // Locate the sshPublicKeys object. json_object* login_profiles = NULL; if (!json_object_object_get_ex(root, "loginProfiles", &login_profiles)) { goto cleanup; } if (json_object_get_type(login_profiles) != json_type_array) { goto cleanup; } login_profiles = json_object_array_get_idx(login_profiles, 0); if (!json_object_object_get_ex(login_profiles, "sshPublicKeys", &ssh_public_keys)) { goto cleanup; } if (json_object_get_type(ssh_public_keys) != json_type_object) { goto cleanup; } { // Extra braces to indicate scope of key, obj below to compiler. Otherwise // g++ complains that `goto` bypasses initializers. json_object_object_foreach(ssh_public_keys, key, obj) { (void)(key); if (json_object_get_type(obj) != json_type_object) { continue; } string key_to_add = ""; bool expired = false; json_object_object_foreach(obj, key, val) { string string_key(key); int val_type = json_object_get_type(val); if (string_key == "key") { if (val_type != json_type_string) { continue; } key_to_add = json_object_get_string(val); } if (string_key == "expirationTimeUsec") { if (val_type == json_type_int || val_type == json_type_string) { uint64_t expiry_usec = (uint64_t)json_object_get_int64(val); struct timeval tp; gettimeofday(&tp, NULL); uint64_t cur_usec = tp.tv_sec * 1000000 + tp.tv_usec; expired = cur_usec > expiry_usec; } else { continue; } } } if (!key_to_add.empty() && !expired) { result.push_back(key_to_add); } } } cleanup: json_object_put(root); return result; } std::vector ParseJsonToSshKeysSk(const string& json) { std::vector result; json_object* security_keys = NULL; json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return result; } // Locate the securityKeys array. json_object* login_profiles = NULL; if (!json_object_object_get_ex(root, "loginProfiles", &login_profiles)) { goto cleanup; } if (json_object_get_type(login_profiles) != json_type_array) { goto cleanup; } login_profiles = json_object_array_get_idx(login_profiles, 0); if (!json_object_object_get_ex(login_profiles, "securityKeys", &security_keys)) { goto cleanup; } if (json_object_get_type(security_keys) != json_type_array) { goto cleanup; } { size_t number_of_keys = 0; size_t idx; json_object* security_key = NULL; json_object* public_key = NULL; string key_to_add = ""; number_of_keys = json_object_array_length(security_keys); for (idx = 0; idx < number_of_keys; idx++) { security_key = json_object_array_get_idx(security_keys, idx); if (json_object_get_type(security_key) != json_type_object) { goto cleanup; } if (!json_object_object_get_ex(security_key, "publicKey", &public_key)) { goto cleanup; } key_to_add = json_object_get_string(public_key); result.push_back(key_to_add); key_to_add.clear(); } } cleanup: json_object_put(root); return result; } bool ParseJsonToPasswd(const string& json, struct passwd* result, BufferManager* buf, int* errnop) { *errnop = EINVAL; json_object* root = NULL; json_object* origroot = NULL; origroot = root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } bool ret = false; json_object* posix_accounts = NULL; json_object* login_profiles = NULL; // If this is called from getpwent_r, loginProfiles won't be in the response. if (json_object_object_get_ex(root, "loginProfiles", &login_profiles)) { if (json_object_get_type(login_profiles) != json_type_array) { goto cleanup; } // This overwrites root but we still have origroot for cleanup; root = json_object_array_get_idx(login_profiles, 0); } // Locate the posixAccounts object. if (!json_object_object_get_ex(root, "posixAccounts", &posix_accounts)) { goto cleanup; } if (json_object_get_type(posix_accounts) != json_type_array) { goto cleanup; } posix_accounts = json_object_array_get_idx(posix_accounts, 0); // Populate with some default values that ValidatePasswd can detect if they // are not set. result->pw_uid = 0; result->pw_shell = (char*)""; result->pw_name = (char*)""; result->pw_dir = (char*)""; result->pw_passwd = (char*)""; // Iterate through the json response and populate the passwd struct. if (json_object_get_type(posix_accounts) != json_type_object) { goto cleanup; } { // Extra braces to indicate scope of key, obj below to compiler. Otherwise // g++ complains that `goto` bypasses initializers. json_object_object_foreach(posix_accounts, key, val) { int val_type = json_object_get_type(val); // Convert char* to c++ string for easier comparison. string string_key(key); if (string_key == "uid") { if (val_type == json_type_int || val_type == json_type_string) { result->pw_uid = (uint32_t)json_object_get_int64(val); if (result->pw_uid == 0) { goto cleanup; } } else { goto cleanup; } } else if (string_key == "gid") { if (val_type == json_type_int || val_type == json_type_string) { result->pw_gid = (uint32_t)json_object_get_int64(val); // Use the uid as the default group when gid is not set or is zero. if (result->pw_gid == 0) { result->pw_gid = result->pw_uid; } } else { goto cleanup; } } else if (string_key == "username") { if (val_type != json_type_string) { goto cleanup; } if (!buf->AppendString(json_object_get_string(val), &result->pw_name, errnop)) { goto cleanup; } } else if (string_key == "homeDirectory") { if (val_type != json_type_string) { goto cleanup; } if (!buf->AppendString(json_object_get_string(val), &result->pw_dir, errnop)) { goto cleanup; } } else if (string_key == "shell") { if (val_type != json_type_string) { goto cleanup; } if (!buf->AppendString(json_object_get_string(val), &result->pw_shell, errnop)) { goto cleanup; } } } } *errnop = 0; ret = ValidatePasswd(result, buf, errnop); cleanup: json_object_put(origroot); return ret; } bool AddUsersToGroup(std::vector users, struct group* result, BufferManager* buf, int* errnop) { if (users.size() < 1) { return true; } // Get some space for the char* array for number of users + 1 for NULL cap. char** bufp; if (!(bufp = (char**)buf->Reserve(sizeof(char*) * (users.size() + 1), errnop))) { return false; } result->gr_mem = bufp; for (int i = 0; i < (int)users.size(); i++) { if (!buf->AppendString(users[i], bufp, errnop)) { result->gr_mem = NULL; return false; } bufp++; } *bufp = NULL; // End the array with a null pointer. return true; } bool ParseJsonToEmail(const string& json, string* email) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } bool ret = false; json_object* json_email = NULL; // Locate the email object. json_object* login_profiles = NULL; if (!json_object_object_get_ex(root, "loginProfiles", &login_profiles)) { goto cleanup; } if (json_object_get_type(login_profiles) != json_type_array) { goto cleanup; } login_profiles = json_object_array_get_idx(login_profiles, 0); if (!json_object_object_get_ex(login_profiles, "name", &json_email)) { goto cleanup; } ret = true; *email = json_object_get_string(json_email); cleanup: json_object_put(root); return ret; } bool ParseJsonToSuccess(const string& json) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } json_object* success = NULL; if (!json_object_object_get_ex(root, "success", &success)) { json_object_put(root); return false; } bool ret = (bool)json_object_get_boolean(success); json_object_put(root); return ret; } bool ParseJsonToKey(const string& json, const string& key, string* response) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } bool ret = false; json_object* json_response = NULL; const char* c_response = NULL; if (!json_object_object_get_ex(root, key.c_str(), &json_response)) { goto cleanup; } if (!(c_response = json_object_get_string(json_response))) { goto cleanup; } *response = c_response; ret = true; cleanup: json_object_put(root); return ret; } bool ParseJsonToChallenges(const string& json, std::vector* challenges) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); if (root == NULL) { return false; } bool ret = false; json_object* challengeId = NULL; json_object* challengeType = NULL; json_object* challengeStatus = NULL; json_object* jsonChallenges = NULL; if (!json_object_object_get_ex(root, "challenges", &jsonChallenges)) { goto cleanup; } for (int i = 0; i < (int)json_object_array_length(jsonChallenges); ++i) { if (!json_object_object_get_ex(json_object_array_get_idx(jsonChallenges, i), "challengeId", &challengeId)) { goto cleanup; } if (!json_object_object_get_ex(json_object_array_get_idx(jsonChallenges, i), "challengeType", &challengeType)) { goto cleanup; } if (!json_object_object_get_ex(json_object_array_get_idx(jsonChallenges, i), "status", &challengeStatus)) { goto cleanup; } Challenge challenge; challenge.id = json_object_get_int(challengeId); challenge.type = json_object_get_string(challengeType); challenge.status = json_object_get_string(challengeStatus); challenges->push_back(challenge); } ret = true; cleanup: json_object_put(root); return ret; } // ----------------- OS Login functions ----------------- bool GetGroupsForUser(string username, std::vector* groups, int* errnop) { string response; if (!(GetUser(username, &response))) { DEBUG("GetGroupsForUser: !GetUser\n"); *errnop = ENOENT; return false; } string email; if (!ParseJsonToEmail(response, &email) || email.empty()) { DEBUG("GetGroupsForUser: !ParseJsonToEmail\n"); *errnop = ENOENT; return false; } std::stringstream url; long http_code; string pageToken (""); do { url.str(""); url << kMetadataServerUrl << "groups?email=" << email; if (pageToken != "") url << "&pagetoken=" << pageToken; response.clear(); http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { *errnop = EAGAIN; return false; } if (!ParseJsonToKey(response, "nextPageToken", &pageToken)) { *errnop = ENOENT; return false; } if (!ParseJsonToGroups(response, groups)) { *errnop = ENOENT; return false; } } while (pageToken != "0"); return true; } bool GetGroupByName(string name, struct group* result, BufferManager* buf, int* errnop) { std::stringstream url; std::vector groups; string response; long http_code; url.str(""); url << kMetadataServerUrl << "groups?groupname=" << name; response.clear(); http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { *errnop = EAGAIN; return false; } groups.clear(); if (!ParseJsonToGroups(response, &groups) || groups.empty() || groups.size() != 1) { *errnop = ENOENT; return false; } Group el = groups[0]; result->gr_gid = el.gid; if (!buf->AppendString(el.name, &result->gr_name, errnop)) { return false; } return true; } bool GetGroupByGID(int gid, struct group* result, BufferManager* buf, int* errnop) { std::stringstream url; std::vector groups; string response; long http_code; url.str(""); url << kMetadataServerUrl << "groups?gid=" << gid; response.clear(); http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { *errnop = EAGAIN; return false; } groups.clear(); if (!ParseJsonToGroups(response, &groups) || groups.empty() || groups.size() != 1) { *errnop = ENOENT; return false; } Group el = groups[0]; result->gr_gid = el.gid; if (!buf->AppendString(el.name, &result->gr_name, errnop)) { return false; } return true; } bool GetUsersForGroup(string groupname, std::vector* users, int* errnop) { string response; long http_code; string pageToken (""); std::stringstream url; do { url.str(""); url << kMetadataServerUrl << "users?groupname=" << groupname; if (pageToken != "") url << "&pagetoken=" << pageToken; response.clear(); http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || response.empty()) { *errnop = EAGAIN; return false; } if (!ParseJsonToKey(response, "nextPageToken", &pageToken)) { *errnop = EINVAL; return false; } if (!ParseJsonToUsers(response, users)) { *errnop = EINVAL; return false; } } while (pageToken != "0"); return true; } bool GetUser(const string& username, string* response) { std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(username); long http_code = 0; if (!HttpGet(url.str(), response, &http_code) || response->empty() || http_code != 200) { return false; } return true; } bool StartSession(const string& email, string* response) { bool ret = true; json_object* jobj = NULL; json_object* jarr = NULL; jarr = json_object_new_array(); json_object_array_add(jarr, json_object_new_string(INTERNAL_TWO_FACTOR)); json_object_array_add(jarr, json_object_new_string(SECURITY_KEY_OTP)); json_object_array_add(jarr, json_object_new_string(AUTHZEN)); json_object_array_add(jarr, json_object_new_string(TOTP)); json_object_array_add(jarr, json_object_new_string(IDV_PREREGISTERED_PHONE)); jobj = json_object_new_object(); json_object_object_add(jobj, "email", json_object_new_string(email.c_str())); json_object_object_add(jobj, "supportedChallengeTypes", jarr); // Ownership transferred to jobj. const char* data; data = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); std::stringstream url; url << kMetadataServerUrl << "authenticate/sessions/start"; long http_code = 0; if (!HttpPost(url.str(), data, response, &http_code) || response->empty() || http_code != 200) { ret = false; } json_object_put(jobj); return ret; } bool ContinueSession(bool alt, const string& email, const string& user_token, const string& session_id, const Challenge& challenge, string* response) { bool ret = true; json_object* jobj = NULL; json_object* jresp = NULL; jobj = json_object_new_object(); json_object_object_add(jobj, "email", json_object_new_string(email.c_str())); json_object_object_add(jobj, "challengeId", json_object_new_int(challenge.id)); if (alt) { json_object_object_add(jobj, "action", json_object_new_string("START_ALTERNATE")); } else { json_object_object_add(jobj, "action", json_object_new_string("RESPOND")); } // AUTHZEN type and START_ALTERNATE action don't provide credentials. if (challenge.type != AUTHZEN && !alt) { jresp = json_object_new_object(); json_object_object_add(jresp, "credential", json_object_new_string(user_token.c_str())); json_object_object_add(jobj, "proposalResponse", jresp); // Ownership transferred to jobj. } const char* data = NULL; data = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); std::stringstream url; url << kMetadataServerUrl << "authenticate/sessions/" << session_id << "/continue"; long http_code = 0; if (!HttpPost(url.str(), data, response, &http_code) || response->empty() || http_code != 200) { ret = false; } json_object_put(jobj); return ret; } } // namespace oslogin_utils guest-oslogin-20220714.00/src/pam/000077500000000000000000000000001426332663300163465ustar00rootroot00000000000000guest-oslogin-20220714.00/src/pam/pam_oslogin_admin.cc000066400000000000000000000057111426332663300223400ustar00rootroot00000000000000// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define PAM_SM_ACCOUNT #include #include #include #include #include #include #include #include #include #include #include #include using std::string; using oslogin_utils::HttpGet; using oslogin_utils::GetUser; using oslogin_utils::kMetadataServerUrl; using oslogin_utils::ParseJsonToKey; using oslogin_utils::ParseJsonToEmail; using oslogin_utils::ParseJsonToSuccess; using oslogin_utils::UrlEncode; using oslogin_utils::ValidateUserName; static const char kSudoersDir[] = "/var/google-sudoers.d/"; extern "C" { PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { // The return value for this module should generally be ignored. By default we // will return PAM_SUCCESS. int pam_result = PAM_SUCCESS; const char *user_name; if ((pam_result = pam_get_user(pamh, &user_name, NULL)) != PAM_SUCCESS) { PAM_SYSLOG(pamh, LOG_INFO, "Could not get pam user."); return pam_result; } if (!ValidateUserName(user_name)) { // If the user name is not a valid oslogin user, don't bother continuing. return PAM_SUCCESS; } string response; if (!GetUser(user_name, &response)) { return PAM_SUCCESS; } string email; if (!ParseJsonToEmail(response, &email) || email.empty()) { return PAM_SUCCESS; } std::stringstream url; url << kMetadataServerUrl << "authorize?email=" << UrlEncode(email) << "&policy=adminLogin"; string filename = kSudoersDir; filename.append(user_name); struct stat buffer; bool file_exists = !stat(filename.c_str(), &buffer); long http_code; if (HttpGet(url.str(), &response, &http_code) && http_code == 200 && ParseJsonToSuccess(response)) { if (!file_exists) { PAM_SYSLOG(pamh, LOG_INFO, "Granting sudo permissions to organization user %s.", user_name); std::ofstream sudoers_file; sudoers_file.open(filename.c_str()); sudoers_file << user_name << " ALL=(ALL) NOPASSWD: ALL" << "\n"; sudoers_file.close(); chown(filename.c_str(), 0, 0); chmod(filename.c_str(), S_IRUSR | S_IRGRP); } } else if (file_exists) { remove(filename.c_str()); } return pam_result; } } guest-oslogin-20220714.00/src/pam/pam_oslogin_login.cc000066400000000000000000000225471426332663300223660ustar00rootroot00000000000000// Copyright 2020 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define PAM_SM_ACCOUNT #include #include #include #include #include #include #include #include #include #include #include #include #include using oslogin_utils::ContinueSession; using oslogin_utils::GetUser; using oslogin_utils::HttpGet; using oslogin_utils::HttpPost; using oslogin_utils::kMetadataServerUrl; using oslogin_utils::ParseJsonToChallenges; using oslogin_utils::ParseJsonToKey; using oslogin_utils::ParseJsonToEmail; using oslogin_utils::ParseJsonToSuccess; using oslogin_utils::StartSession; using oslogin_utils::UrlEncode; using oslogin_utils::ValidateUserName; static const char kUsersDir[] = "/var/google-users.d/"; extern "C" { PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *user_name; if (pam_get_user(pamh, &user_name, NULL) != PAM_SUCCESS) { PAM_SYSLOG(pamh, LOG_INFO, "Could not get pam user."); return PAM_AUTH_ERR; } if (!ValidateUserName(user_name)) { // Not a valid OS Login username. return PAM_IGNORE; } std::string users_filename = kUsersDir; users_filename.append(user_name); struct stat buffer; bool file_exists = !stat(users_filename.c_str(), &buffer); std::string str_user_name(user_name); std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(str_user_name); std::string response; long http_code = 0; if (!HttpGet(url.str(), &response, &http_code) || response.empty() || http_code != 200) { if (http_code == 404) { // This module is only consulted for OS Login users. return PAM_IGNORE; } // Check local file for that user as a last resort. if (file_exists) { return PAM_PERM_DENIED; } // We can't confirm this is an OS Login user, ignore module. return PAM_IGNORE; } std::string email; if (!ParseJsonToEmail(response, &email) || email.empty()) { return PAM_AUTH_ERR; } url.str(""); url << kMetadataServerUrl << "authorize?email=" << UrlEncode(email) << "&policy=login"; if (!HttpGet(url.str(), &response, &http_code)) { PAM_SYSLOG(pamh, LOG_INFO, "Failed to validate organization user %s has login permission.", user_name); return PAM_PERM_DENIED; } if (http_code != 200) { PAM_SYSLOG(pamh, LOG_INFO, "Failed to validate organization user %s has login permission, got HTTP response code %d.", user_name, http_code); return PAM_PERM_DENIED; } if (!ParseJsonToSuccess(response)) { PAM_SYSLOG(pamh, LOG_INFO, "Organization user %s does not have login permission.", user_name); if (file_exists) { remove(users_filename.c_str()); } return PAM_PERM_DENIED; } PAM_SYSLOG(pamh, LOG_INFO, "Organization user %s has login permission.", user_name); if (!file_exists) { std::ofstream users_file(users_filename.c_str()); chown(users_filename.c_str(), 0, 0); chmod(users_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP); } return PAM_SUCCESS; } PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char **argv) { const char* user_name; if (pam_get_user(pamh, &user_name, NULL) != PAM_SUCCESS) { PAM_SYSLOG(pamh, LOG_INFO, "Could not get pam user."); return PAM_PERM_DENIED; } std::string str_user_name(user_name); if (!ValidateUserName(user_name)) { return PAM_PERM_DENIED; } std::string response; if (!(GetUser(str_user_name, &response))) { return PAM_PERM_DENIED; } // System accounts begin with the prefix `sa_`. std::string sa_prefix = "sa_"; if (str_user_name.compare(0, sa_prefix.size(), sa_prefix) == 0) { return PAM_SUCCESS; } std::string email; if (!ParseJsonToEmail(response, &email) || email.empty()) { return PAM_PERM_DENIED; } response = ""; if (!StartSession(email, &response)) { PAM_SYSLOG(pamh, LOG_ERR, "Bad response from the two-factor start session request: %s", response.empty() ? "empty response" : response.c_str()); return PAM_PERM_DENIED; } std::string status; if (!ParseJsonToKey(response, "status", &status)) { PAM_SYSLOG(pamh, LOG_ERR, "Failed to parse status from start session response"); return PAM_PERM_DENIED; } if (status == "NO_AVAILABLE_CHALLENGES") { return PAM_PERM_DENIED; // User is not two-factor enabled, deny login. } std::string session_id; if (!ParseJsonToKey(response, "sessionId", &session_id)) { return PAM_PERM_DENIED; } std::vector challenges; if (!ParseJsonToChallenges(response, &challenges)) { PAM_SYSLOG(pamh, LOG_ERR, "Failed to parse challenge values from JSON response"); return PAM_PERM_DENIED; } std::map user_prompts; user_prompts[AUTHZEN] = "Google phone prompt"; user_prompts[TOTP] = "Security code from Google Authenticator application"; user_prompts[INTERNAL_TWO_FACTOR] = "Security code from security key"; user_prompts[IDV_PREREGISTERED_PHONE] = "Voice or text message verification code"; user_prompts[SECURITY_KEY_OTP] = "Security code from a security key"; oslogin_utils::Challenge challenge; if (challenges.size() > 1) { std::stringstream prompt; prompt << "Please choose from the available authentication methods: "; for(vector::size_type i = 0; i != challenges.size(); ++i) prompt << "\n" << i+1 << ": " << user_prompts[challenges[i].type]; prompt << "\n\nEnter the number for the authentication method to use: "; char *choice = NULL; if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &choice, "%s", prompt.str().c_str()) != PAM_SUCCESS) { pam_error(pamh, "Unable to get user input"); return PAM_PERM_DENIED; } int choicei; if (sscanf(choice, "%d", &choicei) != 1) { pam_error(pamh, "Error parsing user input"); return PAM_PERM_DENIED; } if (size_t(choicei) > challenges.size() || choicei <= 0) { pam_error(pamh, "Invalid option"); return PAM_PERM_DENIED; } challenge = challenges[choicei - 1]; } else { challenge = challenges[0]; } if (challenge.status != "READY") { // Call continueSession with the START_ALTERNATE flag. if (!ContinueSession(true, email, "", session_id, challenge, &response)) { PAM_SYSLOG(pamh, LOG_ERR, "Bad response from two-factor continue session request: %s", response.empty() ? "empty response" : response.c_str()); return PAM_PERM_DENIED; } } char* user_token = NULL; if (challenge.type == INTERNAL_TWO_FACTOR) { if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_token, "Enter your security code: ") != PAM_SUCCESS) { pam_error(pamh, "Unable to get user input"); return PAM_PERM_DENIED; } } else if (challenge.type == SECURITY_KEY_OTP) { if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_token, "Enter your security code by visiting g.co/sc: ") != PAM_SUCCESS) { pam_error(pamh, "Unable to get user input"); return PAM_PERM_DENIED; } } else if (challenge.type == TOTP) { if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_token, "Enter your one-time password: ") != PAM_SUCCESS) { pam_error(pamh, "Unable to get user input"); return PAM_PERM_DENIED; } } else if (challenge.type == AUTHZEN) { if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_token, "A login prompt has been sent to your enrolled device. " "Press enter to continue") != PAM_SUCCESS) { pam_error(pamh, "Unable to get user input"); return PAM_PERM_DENIED; } } else if (challenge.type == IDV_PREREGISTERED_PHONE) { if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &user_token, "A security code has been sent to your phone. " "Enter code to continue: ") != PAM_SUCCESS) { pam_error(pamh, "Unable to get user input"); return PAM_PERM_DENIED; } } else { PAM_SYSLOG(pamh, LOG_ERR, "Unsupported challenge type %s", challenge.type.c_str()); return PAM_PERM_DENIED; } if (!ContinueSession(false, email, user_token, session_id, challenge, &response)) { PAM_SYSLOG(pamh, LOG_ERR, "Bad response from two-factor continue session request: %s", response.empty() ? "empty response" : response.c_str()); return PAM_PERM_DENIED; } if (!ParseJsonToKey(response, "status", &status) || status != "AUTHENTICATED") { if (ParseJsonToKey(response, "rejectionReason", &status) && !status.empty()) { pam_error(pamh, status.c_str()); } return PAM_PERM_DENIED; } return PAM_SUCCESS; } } guest-oslogin-20220714.00/test/000077500000000000000000000000001426332663300157615ustar00rootroot00000000000000guest-oslogin-20220714.00/test/Makefile000066400000000000000000000027011426332663300174210ustar00rootroot00000000000000TOPDIR = $(realpath ..) ifeq ($(GTEST_DIR),) GTEST_DIR = /usr/src/googletest/googletest/ endif TEST_RUNNER = ./test_runner --gtest_output=xml NEW_TEST_RUNNER = ./new_test_runner --gtest_output=xml CPPFLAGS += -I$(TOPDIR)/src/include -I/usr/include/json-c -I$(GTEST_DIR) -isystem $(GTEST_DIR)/include CXXFLAGS += -g -Wall -Wextra -std=c++11 LDLIBS = -lcurl -ljson-c -lpthread all : test_runner new_test_runner non_network_tests clean : rm -f test_runner *.o gtest-all.o : $(GTEST_DIR)/src/gtest-all.cc $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $^ test_runner : oslogin_utils_test.o $(TOPDIR)/src/oslogin_utils.o gtest-all.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ -o $@ $(LDLIBS) new_test_runner : oslogin_test.o gtest-all.o $(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ -o $@ $(LDLIBS) new_tests : new_test_runner $(TOPDIR)/src/nss/new_nss_oslogin.c $(NEW_TEST_RUNNER) ${GTESTARGS} non_network_tests : test_runner new_test_runner $(TEST_RUNNER) --gtest_filter=*-GetGroupByTest.*:GetUsersForGroupTest.* $(NEW_TEST_RUNNER) --gtest_filter=ParserTest.* network_tests : test_runner ping reset $(TEST_RUNNER) --gtest_filter=GetGroupByTest.*:GetUsersForGroupTest.* # run as $ make tests GTESTARGS="--gtest_filter=GetGroupByTest.*" alltests : test_runner $(TEST_RUNNER) ${GTESTARGS} ping : nc -vzw2 169.254.169.254 80 >/dev/null 2>&1 reset : curl -Ss http://169.254.169.254/reset >/dev/null 2>&1 .PHONY : all clean alltests ping reset gtest prowtest non_network_tests network_tests guest-oslogin-20220714.00/test/oslogin_test.cc000066400000000000000000000107521426332663300210060ustar00rootroot00000000000000// Copyright 2020 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Requires libgtest-dev and gtest compiled and installed. #include #include #include "../src/nss/new_nss_oslogin.c" // yes, the c file. #include #include #include TEST(ParserTest, TestParsepasswd) { int res; ssize_t buflen; struct passwd result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = parsepasswd((char *)"username:x:601004:89939:User Name:/home/username:/bin/bash", &result, buf, buflen); ASSERT_EQ(res, 0); ASSERT_EQ(result.pw_uid, 601004); ASSERT_EQ(result.pw_gid, 89939); ASSERT_STREQ(result.pw_name, "username"); ASSERT_STREQ(result.pw_passwd, "x"); ASSERT_STREQ(result.pw_gecos , "User Name"); ASSERT_STREQ(result.pw_dir, "/home/username"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); } TEST(ParserTest, TestParsepasswdErange) { int res; ssize_t buflen; struct passwd result; char *buf; buflen = 32; buf = (char *)malloc(buflen); res = parsepasswd((char *)"username:x:601004:89939:User Name:/home/username:/bin/bash", &result, buf, buflen); ASSERT_EQ(res, ERANGE); } TEST(ParserTest, TestParsepasswdEnoent) { int res; ssize_t buflen; struct passwd result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = parsepasswd((char *)"username:x:601004:89939:User Name:/home/username", &result, buf, buflen); ASSERT_EQ(res, ENOENT); } TEST(ParserTest, TestParsegroup) { int res; ssize_t buflen; struct group result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = parsegroup((char *)"group-name:x:1000:member1,member2,member3,member4", &result, buf, buflen); ASSERT_EQ(res, 0); ASSERT_EQ(result.gr_gid, 1000); ASSERT_STREQ(result.gr_name, "group-name"); ASSERT_STREQ(result.gr_passwd, "x"); ASSERT_STREQ(result.gr_mem[0], "member1"); ASSERT_STREQ(result.gr_mem[1], "member2"); ASSERT_STREQ(result.gr_mem[2], "member3"); ASSERT_STREQ(result.gr_mem[3], "member4"); } TEST(ParserTest, TestParsegroupErange) { int res; ssize_t buflen; struct group result; char *buf; buflen = 32; buf = (char *)malloc(buflen); res = parsegroup((char *)"group-name:x:1000:member1,member2,member3,member4", &result, buf, buflen); ASSERT_EQ(res, ERANGE); } TEST(ParserTest, TestParsegroupEnoent) { int res; ssize_t buflen; struct group result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = parsegroup((char *)"group-name:x", &result, buf, buflen); ASSERT_EQ(res, ENOENT); } TEST(IntegTest, TestGetpwnam) { int res, errnop; ssize_t buflen; struct passwd result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = _nss_oslogin_getpwnam_r("testuser", &result, buf, buflen, &errnop); ASSERT_EQ(res, NSS_STATUS_SUCCESS); } TEST(IntegTest, TestGetpwuid) { int res, errnop; ssize_t buflen; struct passwd result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = _nss_oslogin_getpwuid_r(1000, &result, buf, buflen, &errnop); ASSERT_EQ(res, NSS_STATUS_SUCCESS); } TEST(IntegTest, TestGetgrnam) { int res, errnop; ssize_t buflen; struct group result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = _nss_oslogin_getgrnam_r("testuser", &result, buf, buflen, &errnop); ASSERT_EQ(res, NSS_STATUS_SUCCESS); ASSERT_STREQ(result.gr_name, "testuser"); ASSERT_EQ(result.gr_gid, 1000); } TEST(IntegTest, TestGetgrgid) { nss_status res; int errnop; ssize_t buflen; struct group result; char *buf; buflen = 32768; buf = (char *)malloc(buflen); res = _nss_oslogin_getgrgid_r(1000, &result, buf, buflen, &errnop); ASSERT_EQ(res, NSS_STATUS_SUCCESS); ASSERT_STREQ(result.gr_name, "testuser"); ASSERT_EQ(result.gr_gid, 1000); } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } guest-oslogin-20220714.00/test/oslogin_utils_test.cc000066400000000000000000000540141426332663300222250ustar00rootroot00000000000000// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Requires libgtest-dev and gtest compiled and installed. #include #include #include #include #include using std::string; using std::vector; namespace oslogin_utils { // Test that the buffer can successfully append multiple strings. TEST(BufferManagerTest, TestAppendString) { size_t buflen = 20; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); char* first_string; char* second_string; int test_errno = 0; oslogin_utils::BufferManager buffer_manager(buffer, buflen); buffer_manager.AppendString("test1", &first_string, &test_errno); buffer_manager.AppendString("test2", &second_string, &test_errno); ASSERT_EQ(test_errno, 0); ASSERT_STREQ(first_string, "test1"); ASSERT_STREQ(second_string, "test2"); ASSERT_STREQ(buffer, "test1"); ASSERT_STREQ((buffer + 6), "test2"); } // Test that attempting to append a string larger than the buffer can handle // fails with ERANGE. TEST(BufferManagerTest, TestAppendStringTooLarge) { size_t buflen = 1; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); char* first_string; int test_errno = 0; oslogin_utils::BufferManager buffer_manager(buffer, buflen); ASSERT_FALSE( buffer_manager.AppendString("test1", &first_string, &test_errno)); ASSERT_EQ(test_errno, ERANGE); } // Test successfully loading and retrieving an array of JSON posix accounts. TEST(NssCacheTest, TestLoadJsonArray) { NssCache nss_cache(2); string test_user1 = "{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":1337,\"gid\":1337," "\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}"; string test_user2 = "{\"name\":\"bar@example.com\"," "\"posixAccounts\":[" "{\"primary\":true,\"username\":\"bar\",\"uid\":1338,\"gid\":1338," "\"homeDirectory\":\"/home/bar\",\"shell\":\"/bin/bash\"}]}"; string response = "{\"loginProfiles\": [" + test_user1 + ", " + test_user2 + "], \"nextPageToken\": \"token\"}"; ASSERT_TRUE(nss_cache.LoadJsonUsersToCache(response)); size_t buflen = 500; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; // Verify that the first user was stored. ASSERT_TRUE(nss_cache.HasNextEntry()); ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); ASSERT_EQ(test_errno, 0); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); // Verify that the second user was stored. ASSERT_TRUE(nss_cache.HasNextEntry()); ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); ASSERT_EQ(test_errno, 0); ASSERT_EQ(result.pw_uid, 1338); ASSERT_EQ(result.pw_gid, 1338); ASSERT_STREQ(result.pw_name, "bar"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/bar"); // Verify that there are no more users stored. ASSERT_FALSE(nss_cache.HasNextEntry()); ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); ASSERT_EQ(test_errno, ENOENT); } // Test successfully loading and retrieving a partial array. TEST(NssCacheTest, TestLoadJsonPartialArray) { NssCache nss_cache(2); string test_user1 = "{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":1337,\"gid\":1337," "\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}"; string response = "{\"loginProfiles\": [" + test_user1 + "], \"nextPageToken\": \"token\"}"; ASSERT_TRUE(nss_cache.LoadJsonUsersToCache(response)); size_t buflen = 500; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; // Verify that the first user was stored. ASSERT_TRUE(nss_cache.HasNextEntry()); ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); ASSERT_EQ(test_errno, 0); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); ASSERT_EQ(nss_cache.GetPageToken(), "token"); // Verify that there are no more users stored. ASSERT_FALSE(nss_cache.HasNextEntry()); ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); ASSERT_EQ(test_errno, ENOENT); } // Test successfully loading and retrieving the final response. TEST(NssCacheTest, TestLoadJsonFinalResponse) { NssCache nss_cache(2); string response = "{\"nextPageToken\": \"0\"}"; ASSERT_TRUE(nss_cache.LoadJsonUsersToCache(response)); ASSERT_EQ(nss_cache.GetPageToken(), ""); size_t buflen = 500; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; // Verify that there are no more users stored. ASSERT_FALSE(nss_cache.HasNextEntry()); ASSERT_TRUE(nss_cache.OnLastPage()); ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); ASSERT_EQ(test_errno, ENOENT); } // Tests that resetting, and checking HasNextEntry does not crash. TEST(NssCacheTest, ResetNullPtrTest) { NssCache nss_cache(2); nss_cache.Reset(); ASSERT_FALSE(nss_cache.HasNextEntry()); } // Test parsing a valid JSON response from the metadata server. TEST(ParseJsonPasswdTest, ParseJsonToPasswdSucceeds) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":1337,\"gid\":1338," "\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1338); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); } // Test parsing a valid JSON response from the metadata server with uid > 2^31. TEST(ParseJsonPasswdTest, ParseJsonToPasswdSucceedsWithHighUid) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":4294967295,\"gid\":" "4294967295,\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(result.pw_uid, 4294967295); ASSERT_EQ(result.pw_gid, 4294967295); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); } TEST(ParseJsonPasswdTest, ParseJsonToPasswdSucceedsWithStringUid) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":\"1337\",\"gid\":" "\"1338\",\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1338); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); } TEST(ParseJsonPasswdTest, ParseJsonToPasswdNoLoginProfilesSucceeds) { string test_user = "{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":1337,\"gid\":1337," "\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); } // Test parsing a JSON response without enough space in the buffer. TEST(ParseJsonPasswdTest, ParseJsonToPasswdFailsWithERANGE) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":1337,\"gid\":1337," "\"homeDirectory\":\"/home/foo\",\"shell\":\"/bin/bash\"}]}]}"; size_t buflen = 1; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_FALSE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(test_errno, ERANGE); } // Test parsing malformed JSON responses. TEST(ParseJsonPasswdTest, ParseJsonToPasswdFailsWithEINVAL) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\": \"bad_stuff\"" ",\"gid\":1337,\"homeDirectory\":\"/home/foo\"," "\"shell\":\"/bin/bash\"}]}]}"; string test_user2 = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\": 1337," "\"gid\":\"bad_stuff\",\"homeDirectory\":\"/home/foo\"," "\"shell\":\"/bin/bash\"}]}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_FALSE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(test_errno, EINVAL); // Reset errno. test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user2, &result, &buf, &test_errno)); ASSERT_EQ(test_errno, 0); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1337); } // Test parsing a partially filled response. Validate should fill empty fields // with default values. TEST(ParseJsonPasswdTest, ValidatePartialJsonResponse) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"uid\":1337,\"gid\":1337}]" "}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(result.pw_uid, 1337); ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); } // Test parsing an invalid response. Validate should cause the parse to fail if // there is no uid. TEST(ParseJsonPasswdTest, ValidateInvalidJsonResponse) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"gid\":1337}]" "}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); struct passwd result; int test_errno = 0; ASSERT_FALSE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); ASSERT_EQ(test_errno, EINVAL); } // Test parsing a valid JSON response from the metadata server. TEST(ParseJsonToGroupsTest, ParseJsonToGroupsSucceeds) { string test_group = "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":123452}]}"; std::vector groups; ASSERT_TRUE(ParseJsonToGroups(test_group, &groups)); ASSERT_EQ(groups[0].gid, 123452); ASSERT_EQ(groups[0].name, "demo"); } // Test parsing a valid JSON response from the metadata server with gid > 2^31. TEST(ParseJsonToGroupsTest, ParseJsonToGroupsSucceedsWithHighGid) { string test_group = "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":4294967295}]}"; std::vector groups; ASSERT_TRUE(ParseJsonToGroups(test_group, &groups)); ASSERT_EQ(groups[0].gid, 4294967295); ASSERT_EQ(groups[0].name, "demo"); } TEST(ParseJsonToGroupsTest, ParseJsonToGroupsSucceedsWithStringGid) { string test_group = "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":\"123452\"}]}"; std::vector groups; ASSERT_TRUE(ParseJsonToGroups(test_group, &groups)); ASSERT_EQ(groups[0].gid, 123452); ASSERT_EQ(groups[0].name, "demo"); } // Test parsing malformed JSON responses. TEST(ParseJsonToGroupsTest, ParseJsonToGroupsFails) { string test_badgid = "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":\"this-should-be-int\"}]}"; string test_nogid = "{\"posixGroups\":[{\"name\":\"demo\"}]}"; string test_noname = "{\"posixGroups\":[{\"gid\":123452}]}"; std::vector groups; ASSERT_FALSE(ParseJsonToGroups(test_badgid, &groups)); ASSERT_FALSE(ParseJsonToGroups(test_nogid, &groups)); ASSERT_FALSE(ParseJsonToGroups(test_noname, &groups)); } // Test parsing a valid JSON response from the metadata server. TEST(ParseJsonToUsersTest, ParseJsonToUsersSucceeds) { string test_group_users = "{\"usernames\":[\"user0001\",\"user0002\",\"user0003\",\"user0004\"," "\"user0005\"]}"; std::vector users; ASSERT_TRUE(ParseJsonToUsers(test_group_users, &users)); ASSERT_FALSE(users.empty()); ASSERT_EQ(users.size(), 5); ASSERT_EQ(users[0], "user0001"); ASSERT_EQ(users[1], "user0002"); ASSERT_EQ(users[2], "user0003"); ASSERT_EQ(users[3], "user0004"); ASSERT_EQ(users[4], "user0005"); } // Test parsing a valid JSON response from the metadata server. TEST(ParseJsonToUsersTest, ParseJsonToUsersEmptyGroupSucceeds) { string test_group_users = "{\"nextPageToken\":\"0\"}"; std::vector users; ASSERT_TRUE(ParseJsonToUsers(test_group_users, &users)); ASSERT_TRUE(users.empty()); } TEST(GetUsersForGroupTest, GetUsersForGroupSucceeds) { string response; long http_code; ASSERT_TRUE(HttpGet("http://169.254.169.254/reset", &response, &http_code)); std::vector users; int errnop = 0; ASSERT_TRUE(GetUsersForGroup("demo", &users, &errnop)); ASSERT_FALSE(users.empty()); ASSERT_EQ(users[0], "user000173_grande_focustest_org"); ASSERT_EQ(errnop, 0); } TEST(GetGroupByTest, GetGroupByNameSucceeds) { string response; long http_code; ASSERT_TRUE(HttpGet("http://169.254.169.254/reset", &response, &http_code)); ASSERT_EQ(http_code, 200); size_t buflen = 200 * sizeof(char); char* buffer = (char*)malloc(buflen); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); int errnop = 0; struct group grp = {}; ASSERT_TRUE(GetGroupByName("demo", &grp, &buf, &errnop)); ASSERT_EQ(errnop, 0); } TEST(GetGroupByTest, GetGroupByGIDSucceeds) { string response; long http_code; ASSERT_TRUE(HttpGet("http://169.254.169.254/reset", &response, &http_code)); size_t buflen = 200 * sizeof(char); char* buffer = (char*)malloc(buflen); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); int errnop = 0; struct group grp = {}; ASSERT_TRUE(GetGroupByGID(123452, &grp, &buf, &errnop)); ASSERT_EQ(errnop, 0); } TEST(ParseJsonEmailTest, SuccessfullyParsesEmail) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"posixAccounts\":[" "{\"primary\":true,\"username\":\"foo\",\"gid\":1337}]" "}]}"; string email; ASSERT_TRUE(ParseJsonToEmail(test_user, &email)); ASSERT_EQ(email, "foo@example.com"); } TEST(ParseJsonEmailTest, FailsParseEmail) { string email; ASSERT_FALSE(ParseJsonToEmail("random_junk", &email)); ASSERT_EQ(email, ""); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysSucceeds) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"sshPublicKeys\":" "{\"fingerprint\": {\"key\": \"test_key\"}}}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); std::vector result = ParseJsonToSshKeys(test_user); ASSERT_EQ(result.size(), 1); ASSERT_EQ(result[0], "test_key"); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysMultipleKeys) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"sshPublicKeys\":" "{\"fingerprint\": {\"key\": \"test_key\"}, \"fingerprint2\": {\"key\": " "\"test_key2\"}}}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); std::vector result = ParseJsonToSshKeys(test_user); ASSERT_EQ(result.size(), 2); ASSERT_EQ(result[0], "test_key"); ASSERT_EQ(result[1], "test_key2"); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysFiltersExpiredKeys) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"sshPublicKeys\":" "{\"fingerprint\": {\"key\": \"test_key\"}, \"fingerprint2\": {\"key\": " "\"test_key2\", \"expirationTimeUsec\": 0}}}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); std::vector result = ParseJsonToSshKeys(test_user); ASSERT_EQ(result.size(), 1); ASSERT_EQ(result[0], "test_key"); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysFiltersMalformedExpiration) { string test_user = "{\"loginProfiles\":[{\"name\":\"foo@example.com\",\"sshPublicKeys\":" "{\"fingerprint\": {\"key\": \"test_key\"}, \"fingerprint2\": {\"key\": " "\"test_key2\", \"expirationTimeUsec\": \"bad_stuff\"}}}]}"; size_t buflen = 200; char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); std::vector result = ParseJsonToSshKeys(test_user); ASSERT_EQ(result.size(), 1); ASSERT_EQ(result[0], "test_key"); } TEST(ParseJsonAuthorizeSuccess, SuccessfullyAuthorized) { string response = "{\"success\": true}"; ASSERT_TRUE(ParseJsonToSuccess(response)); } TEST(ValidateUserNameTest, ValidateValidUserNames) { string cases[] = {"user", "_", ".", ".abc_", "_abc-", "ABC", "A_.-", "ausernamethirtytwocharacterslong"}; for (auto test_user : cases) { ASSERT_TRUE(ValidateUserName(test_user)); } } TEST(ValidateUserNameTest, ValidateInvalidUserNames) { string cases[] = { "", "!#$%^", "-abc", "#abc", "^abc", "abc*xyz", "abc xyz", "xyz*", "xyz$", "usernamethirtythreecharacterslong", "../../etc/shadow", }; for (auto test_user : cases) { ASSERT_FALSE(ValidateUserName(test_user)); } } TEST(ParseJsonKeyTest, TestKey) { string test_json = "{\"some_key\":\"some_value\"}"; string value; ASSERT_TRUE(ParseJsonToKey(test_json, "some_key", &value)); ASSERT_EQ(value, "some_value"); } TEST(ParseJsonKeyTest, TestMissingKey) { string test_json = "{\"some_key\":\"some_value\"}"; string value; ASSERT_FALSE(ParseJsonToKey(test_json, "some_other_key", &value)); ASSERT_EQ(value, ""); } TEST(ParseJsonChallengesTest, TestChallenges) { string challenges_json = "{\"status\":\"CHALLENGE_REQUIRED\",\"sessionId\":" "\"testSessionId\",\"challenges\":[{\"challengeId\":1,\"challengeType\":" "\"TOTP\",\"status\":\"READY\"}, {\"challengeId\":2,\"challengeType\":" "\"AUTHZEN\",\"status\":\"PROPOSED\"}]}"; vector challenges; ASSERT_TRUE(ParseJsonToChallenges(challenges_json, &challenges)); ASSERT_EQ(challenges.size(), 2); ASSERT_EQ(challenges[0].id, 1); ASSERT_EQ(challenges[0].type, "TOTP"); } TEST(ParseJsonChallengesTest, TestMalformedChallenges) { string challenges_json = "{\"status\":\"CHALLENGE_REQUIRED\",\"sessionId\":" "\"testSessionId\",\"challenges\":[{\"challengeId\":1,\"challengeType\":" "\"TOTP\",\"status\":\"READY\"}, {\"challengeId\":2,\"challengeType\":" "\"AUTHZEN\"}]}"; vector challenges; ASSERT_FALSE(ParseJsonToChallenges(challenges_json, &challenges)); ASSERT_EQ(challenges.size(), 1); } TEST(ParseJsonToGroupTest, TestGroups) { size_t buflen = 20; char* buffer = (char*)malloc(buflen *sizeof(char)); ASSERT_STRNE(buffer, NULL); oslogin_utils::BufferManager buffer_manager(buffer, buflen); string group_json = "{\"name\":\"mygroup\",\"gid\":\"123452\"}"; struct group result; int error_code; ASSERT_TRUE(ParseJsonToGroup(group_json, &result, &buffer_manager, &error_code)); ASSERT_EQ(result.gr_gid, 123452); ASSERT_STREQ(result.gr_name, "mygroup"); ASSERT_STREQ(result.gr_passwd, ""); } TEST(ParseJsonToGroupsTest, TestGroups) { string group_json = "{\"posixGroups\": [{\"name\":\"mygroup\",\"gid\":\"123452\"},{\"name\":\"mygroup2\",\"gid\":\"123453\"}]}"; vector groups; ASSERT_TRUE(ParseJsonToGroups(group_json, &groups)); ASSERT_EQ(groups.size(), 2); ASSERT_EQ(groups[0].gid, 123452); ASSERT_EQ(groups[1].gid, 123453); ASSERT_STREQ(groups[0].name.c_str(), "mygroup"); ASSERT_STREQ(groups[1].name.c_str(), "mygroup2"); } } // namespace oslogin_utils int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }