summaryrefslogtreecommitdiff
path: root/packages/google-compute-engine-oslogin
diff options
context:
space:
mode:
authorLiam Hopkins <liamh@google.com>2018-12-14 12:44:47 -0800
committerGitHub <noreply@github.com>2018-12-14 12:44:47 -0800
commitf773905cc0a70927c7180dd60d939fbf21264c92 (patch)
treea8aa77f094f896d6689fcee711eb490822b6b1f0 /packages/google-compute-engine-oslogin
parent091c4251a0d5e4af7c006af747251af7d7bcee62 (diff)
downloadgoogle-compute-image-packages-f773905cc0a70927c7180dd60d939fbf21264c92.tar.gz
Repo layout changes (#688)
Diffstat (limited to 'packages/google-compute-engine-oslogin')
-rw-r--r--packages/google-compute-engine-oslogin/Makefile154
-rw-r--r--packages/google-compute-engine-oslogin/README.md245
-rw-r--r--packages/google-compute-engine-oslogin/authorized_keys/authorized_keys.cc78
-rw-r--r--packages/google-compute-engine-oslogin/bin/google_oslogin_control378
-rw-r--r--packages/google-compute-engine-oslogin/libnss_cache_oslogin/compat/getpwent_r.c87
-rw-r--r--packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.c437
-rw-r--r--packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.h65
-rw-r--r--packages/google-compute-engine-oslogin/nss_cache/nss_cache.cc118
-rw-r--r--packages/google-compute-engine-oslogin/nss_module/nss_oslogin.cc106
-rw-r--r--packages/google-compute-engine-oslogin/packaging/debian/changelog119
-rw-r--r--packages/google-compute-engine-oslogin/packaging/debian/compat1
-rw-r--r--packages/google-compute-engine-oslogin/packaging/debian/control13
-rw-r--r--packages/google-compute-engine-oslogin/packaging/debian/copyright27
-rw-r--r--packages/google-compute-engine-oslogin/packaging/debian/google-compute-engine-oslogin.links2
-rwxr-xr-xpackages/google-compute-engine-oslogin/packaging/debian/rules6
-rw-r--r--packages/google-compute-engine-oslogin/packaging/debian/source/format1
-rw-r--r--packages/google-compute-engine-oslogin/packaging/google-compute-engine-oslogin.spec88
-rwxr-xr-xpackages/google-compute-engine-oslogin/packaging/setup_deb.sh44
-rwxr-xr-xpackages/google-compute-engine-oslogin/packaging/setup_rpm.sh39
-rw-r--r--packages/google-compute-engine-oslogin/pam_module/pam_oslogin_admin.cc100
-rw-r--r--packages/google-compute-engine-oslogin/pam_module/pam_oslogin_login.cc264
-rw-r--r--packages/google-compute-engine-oslogin/policy/Makefile17
-rw-r--r--packages/google-compute-engine-oslogin/policy/README.md17
-rw-r--r--packages/google-compute-engine-oslogin/policy/oslogin.fc2
-rw-r--r--packages/google-compute-engine-oslogin/policy/oslogin.ppbin0 -> 1798 bytes
-rw-r--r--packages/google-compute-engine-oslogin/policy/oslogin.te24
-rw-r--r--packages/google-compute-engine-oslogin/utils/oslogin_utils.cc657
-rw-r--r--packages/google-compute-engine-oslogin/utils/oslogin_utils.h202
-rw-r--r--packages/google-compute-engine-oslogin/utils/oslogin_utils_test.cc510
-rwxr-xr-xpackages/google-compute-engine-oslogin/utils/run_tests.sh19
30 files changed, 3820 insertions, 0 deletions
diff --git a/packages/google-compute-engine-oslogin/Makefile b/packages/google-compute-engine-oslogin/Makefile
new file mode 100644
index 0000000..c838166
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/Makefile
@@ -0,0 +1,154 @@
+SHELL = /bin/sh
+
+BASENAME = oslogin
+NAME = google-compute-engine-$(BASENAME)
+MAJOR = 1
+MINOR = 4
+REVISION = 3
+
+LIBNSS_CACHE_OSLOGIN = libnss_cache_$(BASENAME)
+LIBNSS_CACHE_OSLOGIN_NAME = libnss_cache_$(NAME)-$(MAJOR).$(MINOR).$(REVISION).so
+LIBNSS_CACHE_OSLOGIN_SONAME = $(LIBNSS_CACHE_OSLOGIN).so.2
+NSS_LIBRARY_NAME = libnss_$(NAME)-$(MAJOR).$(MINOR).$(REVISION).so
+NSS_LIBRARY_SONAME = libnss_$(BASENAME).so.2
+NSS_INSTALL_PATH = /lib
+PAM_INSTALL_PATH = /lib/security
+AUTHKEYS_INSTALL_PATH = /usr/bin
+
+JSON_INCLUDE_PATH = /usr/include/json-c
+INCLUDE_FLAGS = -I$(JSON_INCLUDE_PATH)
+
+CXX ?= g++
+CXXFLAGS += -fPIC# -Wall
+CC ?= gcc
+PAMFLAGS = $(LDFLAGS) $(INCLUDE_FLAGS) -shared
+NSSFLAGS = $(LDFLAGS) $(INCLUDE_FLAGS) -shared -Wl,-soname,$(NSS_LIBRARY_SONAME)
+LIBNSSFLAGS = $(LDFLAGS) -Wall -Wstrict-prototypes -fPIC -g
+LIBNSS_SO_FLAGS = $(LIBNSSFLAGS) -shared -Wl,-soname,$(LIBNSS_CACHE_OSLOGIN_SONAME)
+
+# UTILS
+UTILS_DIR = utils
+UTILS_SRC = $(UTILS_DIR)/$(BASENAME)_utils.cc
+UTILS = $(UTILS_DIR)/$(BASENAME)_utils.o
+
+# AUTHORIZED KEYS
+AUTHKEYS_DIR = authorized_keys
+AUTHKEYS_SRC = $(AUTHKEYS_DIR)/authorized_keys.cc
+AUTHKEYS_BIN = google_authorized_keys
+
+# NSS
+NSS = nss_$(BASENAME)
+NSS_DIR = nss_module
+NSS_SRC = $(NSS_DIR)/$(NSS).cc
+
+# NSS CACHE
+NSS_CACHE = nss_cache
+NSS_CACHE_DIR = nss_cache
+NSS_CACHE_BIN = google_$(BASENAME)_nss_cache
+NSS_CACHE_SRC = $(NSS_CACHE_DIR)/$(NSS_CACHE).cc
+
+# LIBNSS OSLOGIN CACHE
+LIBNSS_CACHE_OSLOGIN_DIR = $(LIBNSS_CACHE_OSLOGIN)
+LIBNSS_CACHE = nss_cache_$(BASENAME)
+LIBNSS_CACHE_SRC = $(LIBNSS_CACHE_OSLOGIN_DIR)/$(LIBNSS_CACHE).c
+LIBNSS_CACHE_OBJ = $(LIBNSS_CACHE_OSLOGIN_DIR)/$(LIBNSS_CACHE).o
+LIBNSS_COMPAT = compat/getpwent_r
+LIBNSS_COMPAT_SRC = $(LIBNSS_CACHE_OSLOGIN_DIR)/$(LIBNSS_COMPAT).c
+LIBNSS_COMPAT_OBJ = $(LIBNSS_CACHE_OSLOGIN_DIR)/$(LIBNSS_COMPAT).o
+
+# PAM
+PAM = pam_$(BASENAME)
+PAM_DIR = pam_module
+PAM_ADMIN = $(PAM)_admin
+PAM_ADMIN_SRC = $(PAM_DIR)/$(PAM_ADMIN).cc
+PAM_ADMIN_OBJ = $(PAM_DIR)/$(PAM_ADMIN).o
+PAM_ADMIN_MOD = $(PAM_ADMIN).so
+PAM_LOGIN = $(PAM)_login
+PAM_LOGIN_SRC = $(PAM_DIR)/$(PAM_LOGIN).cc
+PAM_LOGIN_OBJ = $(PAM_DIR)/$(PAM_LOGIN).o
+PAM_LOGIN_MOD = $(PAM_LOGIN).so
+
+# HELPER SCRIPTS
+BIN_DIR = bin
+OSLOGIN_HELPER = $(BIN_DIR)/google_oslogin_control
+BIN_INSTALL_PATH = /usr/bin
+
+# SELINUX POLICY
+INSTALL_SELINUX =
+POLICY_DIR = policy
+SELINUX_INSTALL_NAME = oslogin.pp
+SELINUX_MODULE = $(POLICY_DIR)/$(SELINUX_INSTALL_NAME)
+SELINUX_INSTALL_PATH = /usr/share/selinux/packages
+
+LIBS = -lcurl -ljson-c
+PAM_LIBS = -lpam $(LIBS)
+
+ifdef INSTALL_SELINUX
+all: $(NSS) $(NSS_CACHE_BIN) $(LIBNSS_CACHE_OSLOGIN_NAME) $(PAM) $(AUTHKEYS_BIN)
+else
+all: $(NSS) $(NSS_CACHE_BIN) $(LIBNSS_CACHE_OSLOGIN_NAME) $(PAM) $(AUTHKEYS_BIN)
+endif
+
+$(NSS): $(NSS_LIBRARY_SOURCE) $(UTILS)
+ $(CXX) $(CXXFLAGS) $(NSSFLAGS) -o $(NSS_LIBRARY_NAME) \
+ $(NSS_SRC) $(UTILS) $(LIBS)
+
+$(NSS_CACHE_BIN): $(NSS_CACHE_SRC) $(UTILS_SRC)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) $(INCLUDE_FLAGS) -o $(NSS_CACHE_BIN) $(NSS_CACHE_SRC) $(UTILS_SRC) $(LIBS)
+
+$(LIBNSS_CACHE_OSLOGIN_NAME): $(LIBNSS_CACHE_OBJ) $(LIBNSS_COMPAT_OBJ)
+ $(CXX) $(LIBNSS_SO_FLAGS) -o $(LIBNSS_CACHE_OSLOGIN_NAME) $(LIBNSS_CACHE_OBJ) $(LIBNSS_COMPAT_OBJ)
+
+$(LIBNSS_CACHE_OBJ): $(LIBNSS_CACHE_SRC)
+ $(CC) $(LIBNSSFLAGS) -c -o $(LIBNSS_CACHE_OBJ) $(LIBNSS_CACHE_SRC)
+
+$(LIBNSS_COMPAT_OBJ): $(LIBNSS_COMPAT_SRC)
+ $(CC) $(LIBNSSFLAGS) -c -o $(LIBNSS_COMPAT_OBJ) $(LIBNSS_COMPAT_SRC)
+
+$(PAM): $(PAM_ADMIN_MOD) $(PAM_LOGIN_MOD)
+
+$(PAM_LOGIN_MOD): $(PAM_LOGIN_OBJ) $(UTILS)
+ $(CXX) $(PAMFLAGS) -o $(PAM_LOGIN_MOD) $(PAM_LOGIN_OBJ) $(UTILS) $(PAM_LIBS)
+
+$(PAM_ADMIN_MOD): $(PAM_ADMIN_OBJ) $(UTILS)
+ $(CXX) $(PAMFLAGS) -o $(PAM_ADMIN_MOD) $(PAM_ADMIN_OBJ) $(UTILS) $(PAM_LIBS)
+
+$(PAM_LOGIN_OBJ): $(PAM_LOGIN_SRC)
+ $(CXX) $(CXXFLAGS) -c $(PAM_LOGIN_SRC) -o $(PAM_LOGIN_OBJ)
+
+$(PAM_ADMIN_OBJ): $(PAM_ADMIN_SRC)
+ $(CXX) $(CXXFLAGS) -c $(PAM_ADMIN_SRC) -o $(PAM_ADMIN_OBJ)
+
+$(AUTHKEYS_BIN): $(AUTHKEYS_SRC) $(UTILS_SRC)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) $(INCLUDE_FLAGS) -o $(AUTHKEYS_BIN) $(AUTHKEYS_SRC) $(UTILS_SRC) $(LIBS)
+
+$(UTILS): $(UTILS_SRC)
+ $(CXX) $(CXXFLAGS) $(INCLUDE_FLAGS) -c $(UTILS_SRC) -o $(UTILS)
+
+install: $(NSS_LIBRARY_NAME) $(LIBNSS_OSLOGIN_CACHE_NAME) $(PAM_ADMIN_MOD) $(PAM_LOGIN_MOD) $(AUTHKEYS_BIN) $(NSS_CACHE_BIN)
+ mkdir -p $(DESTDIR)$(PREFIX)/$(NSS_INSTALL_PATH)
+ mkdir -p $(DESTDIR)$(PREFIX)/$(PAM_INSTALL_PATH)
+ mkdir -p $(DESTDIR)$(PREFIX)/$(AUTHKEYS_INSTALL_PATH)
+ mkdir -p $(DESTDIR)$(PREFIX)/$(BIN_INSTALL_PATH)
+ install -m 0644 $(LIBNSS_CACHE_OSLOGIN_NAME) $(DESTDIR)$(PREFIX)/$(NSS_INSTALL_PATH)
+ install -m 0644 $(NSS_LIBRARY_NAME) $(DESTDIR)$(PREFIX)/$(NSS_INSTALL_PATH)
+ install -m 0644 $(PAM_ADMIN_MOD) $(PAM_LOGIN_MOD) $(DESTDIR)$(PREFIX)/$(PAM_INSTALL_PATH)
+ install -m 0755 $(AUTHKEYS_BIN) $(DESTDIR)$(PREFIX)/$(AUTHKEYS_INSTALL_PATH)
+ install -m 0755 $(OSLOGIN_HELPER) $(DESTDIR)$(PREFIX)/$(BIN_INSTALL_PATH)
+ install -m 0755 $(NSS_CACHE_BIN) $(DESTDIR)$(PREFIX)/$(BIN_INSTALL_PATH)
+ifdef INSTALL_SELINUX
+ mkdir -p $(DESTDIR)$(PREFIX)/$(SELINUX_INSTALL_PATH)
+ install -T -m 0644 $(SELINUX_MODULE) $(DESTDIR)$(PREFIX)/$(SELINUX_INSTALL_PATH)/$(SELINUX_INSTALL_NAME)
+endif
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/$(NSS_INSTALL_PATH)/$(LIBNSS_CACHE_OSLOGIN_NAME)
+ rm -f $(DESTDIR)$(PREFIX)/$(NSS_INSTALL_PATH)/$(NSS_LIBRARY_NAME)
+ rm -f $(DESTDIR)$(PREFIX)/$(PAM_INSTALL_PATH)/$(PAM_ADMIN_MOD)
+ rm -f $(DESTDIR)$(PREFIX)/$(PAM_INSTALL_PATH)/$(PAM_LOGIN_MOD)
+ rm -f $(DESTDIR)$(PREFIX)/$(AUTHKEYS_INSTALL_PATH)/$(AUTHKEYS_BIN)
+ rm -f $(DESTDIR)$(PREFIX)/$(BIN_INSTALL_PATH)/$(OSLOGIN_HELPER)
+ rm -f $(DESTDIR)$(PREFIX)/$(BIN_INSTALL_PATH)/$(NSS_CACHE_BIN)
+clean:
+ rm -f $(UTILS) $(NSS_LIBRARY_NAME) $(LIBNSS_CACHE_OSLOGIN_NAME) $(LIBNSS_CACHE_OBJ) $(LIBNSS_COMPAT_OBJ) $(PAM_ADMIN_OBJ) $(PAM_ADMIN_MOD) $(PAM_LOGIN_OBJ) $(PAM_LOGIN_MOD) $(AUTHKEYS_BIN) $(NSS_CACHE_BIN)
+
diff --git a/packages/google-compute-engine-oslogin/README.md b/packages/google-compute-engine-oslogin/README.md
new file mode 100644
index 0000000..ce15018
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/README.md
@@ -0,0 +1,245 @@
+## OS Login Guest Environment for Google Compute Engine
+
+This package enables Google Cloud OS Login features on Google Compute Engine
+instances.
+
+**Table of Contents**
+
+* [Overview](#overview)
+* [Components](#components)
+ * [Authorized Keys Command](#authorized-keys-command)
+ * [NSS Module](#nss-module)
+ * [PAM Module](#pam-module)
+ * [Utils](#utils)
+* [Utility Directories](#utility-directories)
+ * [bin](#bin)
+ * [packaging](#packaging)
+ * [policy](#policy)
+* [Source Packages](#source-packages)
+ * [DEB](#deb)
+ * [RPM](#rpm)
+* [Version Updates](#version-updates)
+
+## Overview
+
+The OS Login package has the following components:
+
+* **Authorized Keys Command** to fetch SSH keys from the user's OS Login
+ profile and make them available to sshd.
+* **NSS Module** provides support for making OS Login user and group
+ information available to the system, using NSS (Name Service Switch)
+ functionality.
+* **PAM Module** provides authorization and authentication support allowing
+ the system to use data stored in Google Cloud IAM permissions to control
+ both, the ability to log into an instance, and to perform operations as root
+ (sudo).
+* **Utils** provides common code to support the components listed above.
+
+In addition to the main components, there are also utilities for packaging and
+installing these components:
+
+* **bin** contains a shell script for activating/deactivating the package
+ components.
+* **packaging** contains files used to generate `.deb` and `.rpm` packages for
+ the OS Login components.
+* **policy** contains SELinux "type enforcement" files for configuring SELinux
+ on CentOS/RHEL systems.
+
+## Components
+
+#### Authorized Keys Command
+
+The `google_authorized_keys` binary is designed to be used with the sshd
+[AuthorizedKeysCommand](https://linux.die.net/man/5/sshd_config) option in
+`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=<username>
+ ```
+* Checks to make sure that the user is authorized to log in.
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/authorize?email=<user_email>&policy=login
+ ```
+* If the check is successful, returns the SSH keys associated with the user
+ for use by sshd.
+
+#### NSS Module
+
+The `nss_oslogin` module is built and installed in the appropriate `lib`
+directory as a shared object with the name `libnss_oslogin.so.2`. The module is
+then activated by an `oslogin` entry in `/etc/nsswitch.conf`. The NSS module
+supports looking up `passwd` entries from the metadata server via
+`getent passwd`.
+
+* To return a list of all users, the NSS module queries:
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/users?pagesize=<pagesize>
+ ```
+* To look up a user by username, the NSS module queries:
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/users?username=<username
+ ```
+* To look up a user by UID, the NSS module queries:
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/users?uid=<uid>
+ ```
+
+#### PAM Module
+
+The `pam_module` directory contains two modules used by Linux PAM (Pluggable
+Authentication Modules).
+
+The first module, `pam_oslogin_login.so`, determines whether a given user is
+allowed to SSH into an instance. It is activated by adding an
+`account requisite` line to the PAM sshd config file and does the following:
+
+* Retrieves the user's profile information from the metadata server.
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/users?username=<username>
+ ```
+* If the user has OS Login profile information (as opposed to a local user
+ account), confirms whether the user has permissions to SSH into the
+ instance.
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/authorize?email=<user_email>&policy=login
+ ```
+* If the user is a local user account or is authorized, PAM returns a success
+ message and SSH can proceed. Otherwise, PAM returns a denied message and the
+ SSH check will fail.
+
+The second module, `pam_oslogin_admin.so`, determines whether a given user
+should have admin (sudo) permissions on the instance. It is activated by adding
+an `account optional` line to the PAM sshd config file and does the following:
+
+* Retrieves the user's profile information from the metadata server.
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/users?username=<username>
+ ```
+* If the user is a local user account, the module exits with success.
+* If the user is an OS Login user, the module perform an authorization check
+ to determine if the user has admin permissions.
+ ```
+ http://metadata.google.internal/computeMetadata/v1/oslogin/authorize?email=<user_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.
+
+#### Utils
+
+`oslogin_utils` contains common functions for making HTTP calls,
+interacting with the metadata server, and for parsing JSON objects.
+
+## Utility Directories
+
+#### bin
+
+The `bin` directory contains a shell script called `google_oslogin_control` that
+activates or deactivates the OS Login features. It is called in the pre and post
+install scripts in the `.deb` and `.rpm` packages. 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` 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.
+
+#### packaging
+
+The `packaging` directory contains files for creating `.deb` and `.rpm`
+packages. See [Source Packages](#source-packages) for details.
+
+#### policy
+
+The `policy` directory contains `.te` (type enforcement) 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 `oslogin.pp`
+that is intstalled in the appropriate SELinux directory.
+
+## Source Packages
+
+There is currently support for creating packages for the following distros:
+* Debian 8
+* Debian 9
+* CentOS/RHEL 6
+* CentOS/RHEL 7
+
+#### 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
+ appropriate `debian` directory into the top level. (e.g. When working on
+ Debian 8, copy the `debian8` directory to a directory named `debian` within
+ the code directory.)
+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
+ ```
+
+
+## Version Updates
+
+When updating version numbers, changes need to be made in a few different
+places:
+
+* `Makefile` Update the MAJOR, MINOR, and REVISION variables.
+* `packaging/debian8/changelog` Add a new entry with the new version.
+* `packaging/debian9/changelog` Add a new entry with the new version.
+* `packaging/debian10/changelog` Add a new entry with the new version.
+* `packaging/debian8/google-compute-engine-oslogin.links` Update the libnss
+ version string.
+* `packaging/debian9/google-compute-engine-oslogin.links` Update the libnss
+ version string.
+* `packaging/debian10/google-compute-engine-oslogin.links` Update the libnss
+ version string.
+* `packaging/rpmbuild/SPECS/google-compute-engine-oslogin.spec` Update the
+ Version field.
+* `packaging/setup_deb.sh` Update VERSION variable.
+* `packaging/setup_rpm.sh` Update VERSION variable.
diff --git a/packages/google-compute-engine-oslogin/authorized_keys/authorized_keys.cc b/packages/google-compute-engine-oslogin/authorized_keys/authorized_keys.cc
new file mode 100644
index 0000000..24d1b26
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/authorized_keys/authorized_keys.cc
@@ -0,0 +1,78 @@
+// 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 <iostream>
+#include <sstream>
+#include <string>
+
+#include "../utils/oslogin_utils.h"
+
+using std::cout;
+using std::endl;
+using std::string;
+
+using oslogin_utils::HttpGet;
+using oslogin_utils::ParseJsonToSuccess;
+using oslogin_utils::ParseJsonToKey;
+using oslogin_utils::ParseJsonToEmail;
+using oslogin_utils::ParseJsonToSshKeys;
+using oslogin_utils::UrlEncode;
+using oslogin_utils::kMetadataServerUrl;
+
+int main(int argc, char* argv[]) {
+ if (argc != 2) {
+ cout << "usage: authorized_keys [username]" << 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.
+ 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<string> ssh_keys = ParseJsonToSshKeys(user_response);
+ for (int i = 0; i < ssh_keys.size(); i++) {
+ cout << ssh_keys[i] << endl;
+ }
+ return 0;
+}
diff --git a/packages/google-compute-engine-oslogin/bin/google_oslogin_control b/packages/google-compute-engine-oslogin/bin/google_oslogin_control
new file mode 100644
index 0000000..65c0f15
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/bin/google_oslogin_control
@@ -0,0 +1,378 @@
+#!/bin/sh
+# 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.
+
+nss_config="/etc/nsswitch.conf"
+pam_config="/etc/pam.d/sshd"
+sshd_config="/etc/ssh/sshd_config"
+sudoers_dir="/var/google-sudoers.d"
+users_dir="/var/google-users.d"
+sudoers_file="/etc/sudoers.d/google-oslogin"
+added_comment="# Added by Google Compute Engine OS Login."
+sshd_block="#### Google OS Login control. Do not edit this section. ####"
+sshd_end_block="#### End Google OS Login control section. ####"
+
+is_freebsd() {
+ [ "$(uname)" = "FreeBSD" ]
+ return $?
+}
+
+# Update nsswitch.conf to include OS Login NSS module for passwd.
+modify_nsswitch_conf() {
+ local nss_config="${1:-${nss_config}}"
+
+ if ! grep -q '^passwd:.*oslogin' ${nss_config}; then
+ $sed -i"" '/^passwd:/ s/$/ cache_oslogin oslogin/' ${nss_config}
+ fi
+
+ if is_freebsd && grep -q '^passwd:.*compat'; then
+ $sed -i"" '/^passwd:/ s/compat/files/' ${nss_config}
+ fi
+}
+
+restore_nsswitch_conf() {
+ local nss_config="${1:-${nss_config}}"
+
+ $sed -i"" '/^passwd:/ s/ cache_oslogin oslogin//' ${nss_config}
+ if is_freebsd; then
+ $sed -i"" '/^passwd:/ s/files/compat/' ${nss_config}
+ fi
+}
+
+modify_sshd_conf() (
+ set -e
+
+ local sshd_config="${1:-${sshd_config}}"
+
+ local sshd_auth_keys_command="AuthorizedKeysCommand /usr/bin/google_authorized_keys"
+ local sshd_auth_keys_command_user="AuthorizedKeysCommandUser root"
+ local sshd_auth_methods="AuthenticationMethods publickey,keyboard-interactive"
+ local sshd_challenge="ChallengeResponseAuthentication yes"
+
+ # Update directives for EL 6.
+ if grep -qs "release 6" /etc/redhat-release; then
+ sshd_auth_keys_command_user="AuthorizedKeysCommandRunAs root"
+ sshd_auth_methods="RequiredAuthentications2 publickey,keyboard-interactive"
+ fi
+
+ add_or_update_sshd() {
+ local entry="$1"
+ local sshd_config="$2"
+ local directive="$(echo "$entry" | cut -d' ' -f1)"
+ local value="$(echo "$entry" | cut -d' ' -f2-)"
+
+ # Check if directive is present.
+ if grep -Eq "^\s*${directive}" "$sshd_config"; then
+ # Check if value is incorrect.
+ if ! grep -Eq "^\s*${directive}(\s|=)+${value}" "$sshd_config"; then
+ # Comment out the line (because sshd_config is first-directive-found)
+ # and add to end section.
+ $sed -i"" -E "/^\s*${directive}/ s/^/${added_comment}\n#/" "$sshd_config"
+ $sed -i"" "/$sshd_end_block/ i${entry}" "$sshd_config"
+ fi
+ else
+ $sed -i"" "/$sshd_end_block/ i${entry}" "$sshd_config"
+ fi
+ }
+
+ # Setup Google config block.
+ if ! grep -q "$sshd_block" "$sshd_config"; then
+ # Remove old-style additions.
+ $sed -i"" "/${added_comment}/,+1d" "$sshd_config"
+ /bin/echo -e "\n\n${sshd_block}\n${sshd_end_block}" >> "$sshd_config"
+ fi
+
+ for entry in "$sshd_auth_keys_command" "$sshd_auth_keys_command_user"; do
+ add_or_update_sshd "$entry" "$sshd_config"
+ done
+
+ if [ -n "$two_factor" ]; then
+ for entry in "$sshd_auth_methods" "$sshd_challenge"; do
+ add_or_update_sshd "$entry" "$sshd_config"
+ done
+ fi
+)
+
+restore_sshd_conf() {
+ local sshd_config="${1:-${sshd_config}}"
+
+ if ! grep -q "$sshd_block" "$sshd_config"; then
+ # Remove old-style additions.
+ $sed -i"" "/${added_comment}/,+1d" "$sshd_config"
+ else
+ # Uncomment commented-out fields and remove Google config block.
+ $sed -i"" "/${added_comment}/{n;s/^#//}" "$sshd_config"
+ $sed -i"" "/${added_comment}/d" "$sshd_config"
+ $sed -i"" "/${sshd_block}/,/${sshd_end_block}/d" "$sshd_config"
+ fi
+}
+
+# Inserts pam modules to relevant pam stacks if missing.
+modify_pam_sshd() (
+ set -e
+
+ local pam_config="${1:-${pam_config}}"
+
+ local pam_auth_oslogin="auth [success=done perm_denied=bad default=ignore] pam_oslogin_login.so"
+ local pam_account_oslogin="account [success=ok default=ignore] pam_oslogin_admin.so"
+ local pam_account_admin="account [success=ok ignore=ignore default=die] pam_oslogin_login.so"
+ local pam_session_homedir="session [success=ok default=ignore] pam_mkhomedir.so"
+
+ # In FreeBSD, the used flags are not supported, replacing them with the
+ # previous ones (requisite and optional).
+ if is_freebsd; then
+ pam_auth_oslogin="auth optional pam_oslogin_login.so"
+ pam_account_oslogin="account optional pam_oslogin_admin.so"
+ pam_account_admin="account requisite pam_oslogin_login.so"
+ pam_session_homedir="session optional pam_mkhomedir.so"
+ fi
+
+ local added_config=""
+
+ # For COS this file is solely includes, so simply prepend the new config,
+ # making each entry the top of its stack.
+ if [ -e /etc/os-release ] && grep -q "ID=cos" /etc/os-release; then
+ added_config="${added_comment}\n"
+ for cfg in "$pam_account_admin" "$pam_account_oslogin" \
+ "$pam_session_homedir"; do
+ grep -qE "^${cfg%% *}.*${cfg##* }" ${pam_config} || added_config+="${cfg}\n"
+ done
+
+ if [ -n "$two_factor" ]; then
+ grep -q "$pam_auth_oslogin" "$pam_config" || added_config+="${pam_auth_oslogin}\n"
+ fi
+
+ $sed -i"" "1i ${added_config}\n\n" "$pam_config"
+
+ return 0
+ fi
+
+ # Find the distro-specific insertion point for auth and insert OS Login
+ # two-factor auth module if requested.
+ if [ -n "$two_factor" ] && ! grep -q "$pam_auth_oslogin" ${pam_config}; then
+ if [ -e /etc/debian_version ]; then
+ # Get location of common-auth and check if preceding line is a comment.
+ insert=$($sed -rn "/^@include\s+common-auth/=" "$pam_config")
+ $sed -n "$((insert-1))p" "$pam_config" | grep -q '^#' && insert=$((insert-1))
+ elif [ -e /etc/redhat-release ]; then
+ # Get location of password-auth.
+ insert=$($sed -rn "/^auth\s+(substack|include)\s+password-auth/=" "$pam_config")
+ elif [ -e /etc/os-release ] && grep -q 'ID="sles"' /etc/os-release; then
+ # Get location of common-auth.
+ insert=$($sed -rn "/^auth\s+include\s+common-auth/=" "$pam_config")
+ fi
+
+ # Insert pam_auth_oslogin at insertion point detected above.
+ if [ -n "$insert" ]; then
+ added_config="${added_comment}\n${pam_auth_oslogin}"
+ $sed -i"" "${insert}i ${added_config}" "$pam_config"
+ fi
+ fi
+
+ # Append account modules at end of `account` stack.
+ if ! grep -qE '^account.*oslogin' ${pam_config}; then
+ added_config="\\\n${added_comment}\n${pam_account_admin}\n${pam_account_oslogin}"
+ account_end=$($sed -n '/^account/=' "$pam_config" | tail -1)
+ $sed -i"" "${account_end}a ${added_config}" "$pam_config"
+ fi
+
+ # Append mkhomedir module at end of `session` stack.
+ if ! grep -qE '^session.*mkhomedir' ${pam_config}; then
+ added_config="\\\n${added_comment}\n${pam_session_homedir}"
+ session_end=$($sed -n '/^session/=' "$pam_config" | tail -1)
+ $sed -i"" "${session_end}a ${added_config}" "$pam_config"
+ fi
+
+ # TODO: SLES, others?
+)
+
+restore_pam_sshd() {
+ local pam_config="${1:-${pam_config}}"
+
+ $sed -i"" "/${added_comment}/d" "$pam_config"
+ $sed -i"" "/pam_oslogin/d" "$pam_config"
+ $sed -i"" "/^session.*mkhomedir/d" "$pam_config"
+}
+
+restart_service() {
+ local service="$1"
+
+ # The other options will be wrappers to systemctl on
+ # systemd-enabled systems, so stop if found.
+ if readlink -f /sbin/init|grep -q systemd; then
+ if systemctl is-active --quiet "$service"; then
+ systemctl restart "$service"
+ return $?
+ else
+ return 0
+ fi
+ fi
+
+ # Use the service helper if it exists.
+ if command -v service > /dev/null; then
+ if ! service "$service" status 2>&1 | grep -q unrecognized; then
+ service "$service" restart
+ return $?
+ else
+ return 0
+ fi
+ fi
+
+ # Fallback to trying sysvinit script of the same name.
+ if command -v /etc/init.d/"$service" > /dev/null; then
+ if /etc/init.d/"$service" status > /dev/null 2>&1; then
+ /etc/init.d/"$service" restart
+ return $?
+ else
+ return 0
+ fi
+ fi
+
+ # We didn't find any way to restart this service.
+ return 1
+}
+
+# Restart sshd unless --norestartsshd flag is set.
+restart_sshd() {
+ if [ -n "$no_restart_sshd" ]; then
+ return 0
+ fi
+ echo "Restarting SSHD"
+ for svc in "ssh" "sshd"; do
+ restart_service "$svc"
+ done
+}
+
+restart_svcs() {
+ echo "Restarting optional services."
+ for svc in "nscd" "unscd" "systemd-logind"; do
+ restart_service "$svc"
+ done
+}
+
+setup_google_dirs() {
+ for dir in "$sudoers_dir" "$users_dir"; do
+ [ -d "$dir" ] && continue
+ mkdir -p "$dir"
+ chmod 750 "$dir"
+ if fixfiles=$(command -v fixfiles); then
+ $fixfiles restore "$dir"
+ fi
+ done
+ echo "#includedir ${sudoers_dir}" > "$sudoers_file"
+}
+
+remove_google_dirs() {
+ for dir in "$sudoers_dir" "$users_dir"; do
+ rm -rf "$dir"
+ done
+ rm -f "$sudoers_file"
+}
+
+activate() {
+ for func in modify_sshd_conf modify_nsswitch_conf \
+ modify_pam_sshd setup_google_dirs restart_svcs restart_sshd; do
+ $func
+ [ $? -eq 0 ] || return 1
+ done
+}
+
+deactivate() {
+ for func in remove_google_dirs restore_nsswitch_conf \
+ restore_sshd_conf restore_pam_sshd restart_svcs restart_sshd; do
+ $func
+ done
+}
+
+# get_status checks each file for appropriate updates and exits on first
+# failure. Checks for two factor config changes only if requested.
+get_status() (
+ set -e
+
+ grep -Eq '^account.*oslogin' "$pam_config"
+ grep -Eq 'google_authorized_keys' "$sshd_config"
+ grep -Eq 'passwd:.*oslogin' "$nss_config"
+ if [ -n "$two_factor" ]; then
+ grep -Eq '^auth.*oslogin' "$pam_config"
+ grep -Eq '^(AuthenticationMethods|RequiredAuthentications2).*publickey,keyboard-interactive' "$sshd_config"
+ fi
+)
+
+usage() {
+ echo "Usage: $(basename "$0") {activate|deactivate|status} [--norestartsshd] [--twofactor]"
+ echo "This script will activate or deactivate the features for"
+ echo "Google Compute Engine OS Login and (optionally) two-factor authentication."
+ echo "This script must be run as root."
+ exit 1
+}
+
+
+# Main
+if [ $(id -u) -ne 0 ] || [ $# -lt 1 ]; then
+ usage
+fi
+
+sed="sed"
+is_freebsd && sed="gsed"
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --norestartsshd)
+ no_restart_sshd="true"
+ shift
+ ;;
+ --twofactor)
+ two_factor="true"
+ shift
+ ;;
+ activate)
+ action="activate"
+ shift
+ ;;
+ deactivate)
+ action="deactivate"
+ shift
+ ;;
+ status)
+ action="status"
+ shift
+ ;;
+ *)
+ shift
+ ;;
+ esac
+done
+
+case "$action" in
+ activate)
+ echo "Activating Google Compute Engine OS Login."
+ activate
+ if [ $? -ne 0 ]; then
+ echo "Failed to apply changes, rolling back"
+ deactivate
+ exit 1
+ fi
+ ;;
+ deactivate)
+ echo "Deactivating Google Compute Engine OS Login."
+ deactivate
+ ;;
+ status)
+ get_status
+ exit $?
+ ;;
+ *)
+ usage
+ ;;
+esac
diff --git a/packages/google-compute-engine-oslogin/libnss_cache_oslogin/compat/getpwent_r.c b/packages/google-compute-engine-oslogin/libnss_cache_oslogin/compat/getpwent_r.c
new file mode 100644
index 0000000..b1be6fc
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/libnss_cache_oslogin/compat/getpwent_r.c
@@ -0,0 +1,87 @@
+/*
+ * ----------------------------------------------------------------------
+ * 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 <k@kev009.com>
+ */
+
+#include <sys/param.h>
+
+#ifdef BSD
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+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
diff --git a/packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.c b/packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.c
new file mode 100644
index 0000000..56fe8a0
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.c
@@ -0,0 +1,437 @@
+// 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 "nss_cache_oslogin.h"
+
+#include <sys/mman.h>
+
+// Locking implementation: use pthreads.
+#include <pthread.h>
+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 char p_filename[NSS_CACHE_OSLOGIN_PATH_LENGTH] =
+ "/etc/oslogin_passwd.cache";
+#ifdef BSD
+extern int fgetpwent_r(FILE *, struct passwd *, char *, size_t,
+ struct passwd **);
+#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;
+}
+
+//
+// Binary search routines below here
+//
+
+int _nss_cache_oslogin_bsearch2_compare(const void *key, const void *value) {
+ struct nss_cache_oslogin_args *args = (struct nss_cache_oslogin_args *)key;
+ const char *value_text = (const char *)value;
+
+ // Using strcmp as the generation of the index sorts without
+ // locale awareness.
+ return strcmp(args->lookup_key, value_text);
+}
+
+enum nss_status _nss_cache_oslogin_bsearch2(struct nss_cache_oslogin_args *args,
+ int *errnop) {
+ enum nss_cache_oslogin_match (*lookup)(
+ FILE *, struct nss_cache_oslogin_args *) = args->lookup_function;
+ FILE *file = NULL;
+ FILE *system_file_stream = NULL;
+ struct stat system_file;
+ struct stat sorted_file;
+ enum nss_status ret = 100;
+ long offset = 0;
+ void *mapped_data = NULL;
+
+ file = fopen(args->sorted_filename, "r");
+ if (file == NULL) {
+ DEBUG("error opening %s\n", args->sorted_filename);
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ // if the sorted file is older than the system file, do not risk stale
+ // data and abort
+ // TODO(vasilios): should be a compile or runtime option
+ if (stat(args->system_filename, &system_file) != 0) {
+ DEBUG("failed to stat %s\n", args->system_filename);
+ fclose(file);
+ return NSS_STATUS_UNAVAIL;
+ }
+ if (fstat(fileno(file), &sorted_file) != 0) {
+ DEBUG("failed to stat %s\n", args->sorted_filename);
+ fclose(file);
+ return NSS_STATUS_UNAVAIL;
+ }
+ if (difftime(system_file.st_mtime, sorted_file.st_mtime) > 0) {
+ DEBUG("%s may be stale, aborting lookup\n", args->sorted_filename);
+ fclose(file);
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ mapped_data =
+ mmap(NULL, sorted_file.st_size, PROT_READ, MAP_PRIVATE, fileno(file), 0);
+ if (mapped_data == MAP_FAILED) {
+ DEBUG("mmap failed\n");
+ fclose(file);
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ const char *data = (const char *)mapped_data;
+ while (*data != '\n') {
+ ++data;
+ }
+ long entry_size = data - (const char *)mapped_data + 1;
+ long entry_count = sorted_file.st_size / entry_size;
+
+ void *entry = bsearch(args, mapped_data, entry_count, entry_size,
+ &_nss_cache_oslogin_bsearch2_compare);
+ if (entry != NULL) {
+ const char *entry_text = entry;
+ sscanf(entry_text + strlen(entry_text) + 1, "%ld", &offset);
+ }
+
+ if (munmap(mapped_data, sorted_file.st_size) == -1) {
+ DEBUG("munmap failed\n");
+ }
+ fclose(file);
+
+ if (entry == NULL) {
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ system_file_stream = fopen(args->system_filename, "r");
+ if (system_file_stream == NULL) {
+ DEBUG("error opening %s\n", args->system_filename);
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (fseek(system_file_stream, offset, SEEK_SET) != 0) {
+ DEBUG("fseek fail\n");
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ switch (lookup(system_file_stream, args)) {
+ case NSS_CACHE_OSLOGIN_EXACT:
+ ret = NSS_STATUS_SUCCESS;
+ break;
+ case NSS_CACHE_OSLOGIN_ERROR:
+ if (errno == ERANGE) {
+ // let the caller retry
+ *errnop = errno;
+ ret = _nss_cache_oslogin_ent_bad_return_code(*errnop);
+ }
+ break;
+ default:
+ ret = NSS_STATUS_UNAVAIL;
+ break;
+ }
+
+ fclose(system_file_stream);
+ return ret;
+}
+
+//
+// Routines for passwd map defined below here
+//
+
+// _nss_cache_oslogin_setpwent_path()
+// Helper function for testing
+
+extern char *_nss_cache_oslogin_setpwent_path(const char *path) {
+ DEBUG("%s %s\n", "Setting p_filename to", path);
+ return strncpy(p_filename, path, NSS_CACHE_OSLOGIN_PATH_LENGTH - 1);
+}
+
+// _nss_cache_oslogin_pwuid_wrap()
+// Internal wrapper for binary searches, using uid-specific calls.
+
+static enum nss_cache_oslogin_match _nss_cache_oslogin_pwuid_wrap(
+ FILE *file, struct nss_cache_oslogin_args *args) {
+ struct passwd *result = args->lookup_result;
+ uid_t *uid = args->lookup_value;
+
+ if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+ if (result->pw_uid == *uid) {
+ DEBUG("SUCCESS: found user %d:%s\n", result->pw_uid, result->pw_name);
+ return NSS_CACHE_OSLOGIN_EXACT;
+ }
+ DEBUG("Failed match at uid %d\n", result->pw_uid);
+ if (result->pw_uid > *uid) {
+ return NSS_CACHE_OSLOGIN_HIGH;
+ } else {
+ return NSS_CACHE_OSLOGIN_LOW;
+ }
+ }
+
+ return NSS_CACHE_OSLOGIN_ERROR;
+}
+
+// _nss_cache_oslogin_pwnam_wrap()
+// Internal wrapper for binary searches, using username-specific calls.
+
+static enum nss_cache_oslogin_match _nss_cache_oslogin_pwnam_wrap(
+ FILE *file, struct nss_cache_oslogin_args *args) {
+ struct passwd *result = args->lookup_result;
+ char *name = args->lookup_value;
+ int ret;
+
+ if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+ ret = strcoll(result->pw_name, name);
+ if (ret == 0) {
+ DEBUG("SUCCESS: found user %s\n", result->pw_name);
+ return NSS_CACHE_OSLOGIN_EXACT;
+ }
+ DEBUG("Failed match at name %s\n", result->pw_name);
+ if (ret > 0) {
+ return NSS_CACHE_OSLOGIN_HIGH;
+ } else {
+ return NSS_CACHE_OSLOGIN_LOW;
+ }
+ }
+
+ return NSS_CACHE_OSLOGIN_ERROR;
+}
+
+// _nss_cache_oslogin_setpwent_locked()
+// Internal setup routine
+
+static enum nss_status _nss_cache_oslogin_setpwent_locked(void) {
+ DEBUG("%s %s\n", "Opening", p_filename);
+ p_file = fopen(p_filename, "r");
+
+ 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 passwd.cache\n");
+ 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 %d:%s\n", result->pw_uid, result->pw_name);
+ } 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) {
+ char filename[NSS_CACHE_OSLOGIN_PATH_LENGTH];
+ struct nss_cache_oslogin_args args;
+ enum nss_status ret;
+
+ strncpy(filename, p_filename, NSS_CACHE_OSLOGIN_PATH_LENGTH - 1);
+ if (strlen(filename) > NSS_CACHE_OSLOGIN_PATH_LENGTH - 7) {
+ DEBUG("filename too long\n");
+ return NSS_STATUS_UNAVAIL;
+ }
+ strncat(filename, ".ixuid", 6);
+
+ args.sorted_filename = filename;
+ args.system_filename = p_filename;
+ args.lookup_function = _nss_cache_oslogin_pwuid_wrap;
+ args.lookup_value = &uid;
+ args.lookup_result = result;
+ args.buffer = buffer;
+ args.buflen = buflen;
+ char uid_text[11];
+ snprintf(uid_text, sizeof(uid_text), "%d", uid);
+ args.lookup_key = uid_text;
+ args.lookup_key_length = strlen(uid_text);
+
+ DEBUG("Binary search for uid %d\n", uid);
+ NSS_CACHE_OSLOGIN_LOCK();
+ ret = _nss_cache_oslogin_bsearch2(&args, errnop);
+
+ if (ret == NSS_STATUS_UNAVAIL) {
+ DEBUG("Binary search failed, falling back to full linear search\n");
+ 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) {
+ char *pw_name;
+ char filename[NSS_CACHE_OSLOGIN_PATH_LENGTH];
+ struct nss_cache_oslogin_args args;
+ enum nss_status ret;
+
+ NSS_CACHE_OSLOGIN_LOCK();
+
+ // name is a const char, we need a non-const copy
+ pw_name = malloc(strlen(name) + 1);
+ if (pw_name == NULL) {
+ DEBUG("malloc error\n");
+ return NSS_STATUS_UNAVAIL;
+ }
+ strncpy(pw_name, name, strlen(name) + 1);
+
+ strncpy(filename, p_filename, NSS_CACHE_OSLOGIN_PATH_LENGTH - 1);
+ if (strlen(filename) > NSS_CACHE_OSLOGIN_PATH_LENGTH - 8) {
+ DEBUG("filename too long\n");
+ free(pw_name);
+ return NSS_STATUS_UNAVAIL;
+ }
+ strncat(filename, ".ixname", 7);
+
+ args.sorted_filename = filename;
+ args.system_filename = p_filename;
+ args.lookup_function = _nss_cache_oslogin_pwnam_wrap;
+ args.lookup_value = pw_name;
+ args.lookup_result = result;
+ args.buffer = buffer;
+ args.buflen = buflen;
+ args.lookup_key = pw_name;
+ args.lookup_key_length = strlen(pw_name);
+
+ DEBUG("Binary search for user %s\n", pw_name);
+ ret = _nss_cache_oslogin_bsearch2(&args, errnop);
+
+ if (ret == NSS_STATUS_UNAVAIL) {
+ DEBUG("Binary search failed, falling back to full linear search\n");
+ 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;
+ }
+ }
+ }
+
+ free(pw_name);
+ _nss_cache_oslogin_endpwent_locked();
+ NSS_CACHE_OSLOGIN_UNLOCK();
+
+ return ret;
+}
diff --git a/packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.h b/packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.h
new file mode 100644
index 0000000..25c7274
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/libnss_cache_oslogin/nss_cache_oslogin.h
@@ -0,0 +1,65 @@
+// 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 <errno.h>
+#include <nss.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NSS_CACHE_OSLOGIN_H
+#define NSS_CACHE_OSLOGIN_H
+
+#ifdef DEBUG
+#undef DEBUG
+#define DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, fmt, ##args); \
+ } while (0)
+#else
+#define DEBUG(fmt, ...) \
+ do { \
+ } while (0)
+#endif /* DEBUG */
+
+#define NSS_CACHE_OSLOGIN_PATH_LENGTH 255
+extern char *_nss_cache_oslogin_setpwent_path(const char *path);
+
+enum nss_cache_oslogin_match {
+ NSS_CACHE_OSLOGIN_EXACT = 0,
+ NSS_CACHE_OSLOGIN_HIGH = 1,
+ NSS_CACHE_OSLOGIN_LOW = 2,
+ NSS_CACHE_OSLOGIN_ERROR = 3,
+};
+
+struct nss_cache_oslogin_args {
+ char *system_filename;
+ char *sorted_filename;
+ void *lookup_function;
+ void *lookup_value;
+ void *lookup_result;
+ char *buffer;
+ size_t buflen;
+ char *lookup_key;
+ size_t lookup_key_length;
+};
+
+#endif /* NSS_CACHE_OSLOGIN_H */
diff --git a/packages/google-compute-engine-oslogin/nss_cache/nss_cache.cc b/packages/google-compute-engine-oslogin/nss_cache/nss_cache.cc
new file mode 100644
index 0000000..a720859
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/nss_cache/nss_cache.cc
@@ -0,0 +1,118 @@
+// 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 <errno.h>
+#include <nss.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <fstream>
+
+#include "../utils/oslogin_utils.h"
+
+
+using oslogin_utils::BufferManager;
+using oslogin_utils::MutexLock;
+using oslogin_utils::NssCache;
+
+// File paths for the nss cache file.
+static const char kDefaultFilePath[] = "/etc/oslogin_passwd.cache";
+static const char kDefaultBackupFilePath[] = "/etc/oslogin_passwd.cache.bak";
+
+// Local NSS Cache size. This affects the maximum number of passwd entries per
+// http request.
+static const uint64_t kNssCacheSize = 2048;
+
+// Passwd buffer size. We are guaranteed that a single OS Login user will not
+// exceed 32k.
+static const uint64_t kPasswdBufferSize = 32768;
+
+static NssCache nss_cache(kNssCacheSize);
+
+static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int main(int argc, char* argv[]) {
+ int error_code = 0;
+ // Temporary buffer to hold passwd entries before writing.
+ char buffer[kPasswdBufferSize];
+ struct passwd pwd;
+
+ // Perform the writes under a global lock.
+ MutexLock ml(&cache_mutex);
+ nss_cache.Reset();
+
+ // Check if there is a cache already.
+ struct stat stat_buf;
+ bool backup = !stat(kDefaultFilePath, &stat_buf);
+ if (backup) {
+ // Write a backup file first, in case lookup fails.
+ error_code = rename(kDefaultFilePath, kDefaultBackupFilePath);
+ if (error_code) {
+ openlog("nss_cache_oslogin", LOG_PID, LOG_USER);
+ syslog(LOG_ERR, "Could not create backup file.");
+ closelog();
+ return error_code;
+ }
+ }
+
+ std::ofstream cache_file(kDefaultFilePath);
+ if (cache_file.fail()) {
+ openlog("nss_cache_oslogin", LOG_PID, LOG_USER);
+ syslog(LOG_ERR, "Failed to open file %s.", kDefaultFilePath);
+ closelog();
+ return -1;
+ }
+ chown(kDefaultFilePath, 0, 0);
+ chmod(kDefaultFilePath, S_IRUSR | S_IWUSR | S_IROTH);
+
+ while (!nss_cache.OnLastPage() || nss_cache.HasNextPasswd()) {
+ BufferManager buffer_manager(buffer, kPasswdBufferSize);
+ if (!nss_cache.NssGetpwentHelper(&buffer_manager, &pwd, &error_code)) {
+ break;
+ }
+ 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();
+
+ // Check for errors.
+ if (error_code) {
+ openlog("nss_cache_oslogin", LOG_PID, LOG_USER);
+ if (error_code == ERANGE) {
+ syslog(LOG_ERR, "Received unusually large passwd entry.");
+ } else if (error_code == EINVAL) {
+ syslog(LOG_ERR, "Encountered malformed passwd entry.");
+ } else {
+ syslog(LOG_ERR, "Unknown error while retrieving passwd entry.");
+ }
+ // Restore the backup.
+ if (backup) {
+ if (rename(kDefaultBackupFilePath, kDefaultFilePath)) {
+ syslog(LOG_ERR, "Could not restore data from backup file.");
+ }
+ }
+ closelog();
+ }
+
+ // Remove the backup file on success.
+ if (!error_code && backup) {
+ remove(kDefaultBackupFilePath);
+ }
+ return error_code;
+}
diff --git a/packages/google-compute-engine-oslogin/nss_module/nss_oslogin.cc b/packages/google-compute-engine-oslogin/nss_module/nss_oslogin.cc
new file mode 100644
index 0000000..d808a6f
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/nss_module/nss_oslogin.cc
@@ -0,0 +1,106 @@
+// 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 <curl/curl.h>
+#include <errno.h>
+#include <grp.h>
+#include <nss.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "../utils/oslogin_utils.h"
+
+using std::string;
+
+using oslogin_utils::BufferManager;
+using oslogin_utils::HttpGet;
+using oslogin_utils::MutexLock;
+using oslogin_utils::NssCache;
+using oslogin_utils::ParseJsonToPasswd;
+using oslogin_utils::UrlEncode;
+using oslogin_utils::kMetadataServerUrl;
+
+// Size of the NssCache. This also determines how many users will be requested
+// per HTTP call.
+static const uint64_t kNssCacheSize = 2048;
+
+// NssCache for storing passwd entries.
+static NssCache nss_cache(kNssCacheSize);
+
+// Protects access to nss_cache.
+static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+extern "C" {
+
+// Get a passwd entry by id.
+int _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.
+int _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;
+}
+
+// nss_getpwent_r() is intentionally left unimplemented. This functionality is
+// now covered by the nss_cache binary and nss_cache module.
+
+} // extern "C"
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/changelog b/packages/google-compute-engine-oslogin/packaging/debian/changelog
new file mode 100644
index 0000000..45e2b3e
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/changelog
@@ -0,0 +1,119 @@
+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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> 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 <gc-team@google.com> Thu, 22 Jun 2017 12:00:00 -0700
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/compat b/packages/google-compute-engine-oslogin/packaging/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/control b/packages/google-compute-engine-oslogin/packaging/debian/control
new file mode 100644
index 0000000..a8d0064
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/control
@@ -0,0 +1,13 @@
+Source: google-compute-engine-oslogin
+Maintainer: Google Cloud Team <gc-team@google.com>
+Section: misc
+Priority: optional
+Standards-Version: 3.9.6.1
+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.
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/copyright b/packages/google-compute-engine-oslogin/packaging/debian/copyright
new file mode 100644
index 0000000..f1c5775
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/copyright
@@ -0,0 +1,27 @@
+Format: 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".
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/google-compute-engine-oslogin.links b/packages/google-compute-engine-oslogin/packaging/debian/google-compute-engine-oslogin.links
new file mode 100644
index 0000000..fd75bc5
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/google-compute-engine-oslogin.links
@@ -0,0 +1,2 @@
+/lib/libnss_google-compute-engine-oslogin-1.3.2.so /lib/libnss_oslogin.so.2
+/lib/libnss_cache_google-compute-engine-oslogin-1.3.2.so /lib/libnss_cache_oslogin.so.2
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/rules b/packages/google-compute-engine-oslogin/packaging/debian/rules
new file mode 100755
index 0000000..65b1f4b
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/rules
@@ -0,0 +1,6 @@
+#!/usr/bin/make -f
+%:
+ dh $@
+
+override_dh_auto_install:
+ dh_auto_install -- PAM_INSTALL_PATH=/lib/x86_64-linux-gnu/security BIN_INSTALL_PATH=/usr/bin
diff --git a/packages/google-compute-engine-oslogin/packaging/debian/source/format b/packages/google-compute-engine-oslogin/packaging/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/packages/google-compute-engine-oslogin/packaging/google-compute-engine-oslogin.spec b/packages/google-compute-engine-oslogin/packaging/google-compute-engine-oslogin.spec
new file mode 100644
index 0000000..f3a12e6
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/google-compute-engine-oslogin.spec
@@ -0,0 +1,88 @@
+# 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.
+
+# Force the dist to be el7 to avoid el7.centos.
+%if 0%{?rhel} == 7
+ %define dist .el7
+%endif
+
+Name: google-compute-engine-oslogin
+Version: 1.4.3
+Release: 1%{?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
+BuildRequires: json-c
+BuildRequires: pam-devel
+BuildRequires: policycoreutils-python
+Requires: boost-regex
+Requires: policycoreutils-python
+
+%define pam_install_path /%{_lib}/security
+
+%description
+This package contains several libraries and changes to enable OS Login functionality
+for Google Compute Engine.
+
+%prep
+%setup
+
+%build
+make %{?_smp_mflags} LIBS="-lcurl -ljson-c -lboost_regex"
+
+%install
+rm -rf %{buildroot}
+make install DESTDIR=%{buildroot} NSS_INSTALL_PATH=/%{_lib} PAM_INSTALL_PATH=%{pam_install_path} INSTALL_SELINUX=true
+
+%files
+%doc
+/%{_lib}/libnss_%{name}-%{version}.so
+/%{_lib}/libnss_cache_%{name}-%{version}.so
+%{pam_install_path}/pam_oslogin_admin.so
+%{pam_install_path}/pam_oslogin_login.so
+/usr/bin/google_authorized_keys
+/usr/bin/google_oslogin_control
+/usr/bin/google_oslogin_nss_cache
+/usr/share/selinux/packages/oslogin.pp
+
+%post
+/sbin/ldconfig
+if [ $1 -gt 1 ]; then # This is an upgrade.
+ if semodule -l | grep -qi oslogin.el6; then
+ echo "Removing old SELinux module for OS Login."
+ semodule -r oslogin.el6
+ fi
+fi
+echo "Installing SELinux module for OS Login."
+semodule -i /usr/share/selinux/packages/oslogin.pp
+if [ -e /var/google-sudoers.d ]; then
+ fixfiles restore /var/google-sudoers.d
+fi
+
+%postun
+/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
diff --git a/packages/google-compute-engine-oslogin/packaging/setup_deb.sh b/packages/google-compute-engine-oslogin/packaging/setup_deb.sh
new file mode 100755
index 0000000..3c41f20
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/setup_deb.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# 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.
+
+NAME="google-compute-engine-oslogin"
+VERSION="1.4.3"
+
+working_dir=${PWD}
+if [[ $(basename "$working_dir") != $NAME ]]; then
+ echo "Packaging scripts must be run from top of package dir."
+ exit 1
+fi
+
+# Build dependencies.
+sudo apt-get -y install make g++ libcurl4-openssl-dev libjson-c-dev libpam-dev
+
+# DEB creation tools.
+sudo apt-get -y install debhelper devscripts build-essential
+
+rm -rf /tmp/debpackage
+mkdir /tmp/debpackage
+tar czvf /tmp/debpackage/${NAME}_${VERSION}.orig.tar.gz --exclude .git --exclude packaging --transform "s/^\./${NAME}-${VERSION}/" .
+
+pushd /tmp/debpackage
+tar xzvf ${NAME}_${VERSION}.orig.tar.gz
+
+cd ${NAME}-${VERSION}
+
+cp -r ${working_dir}/packaging/debian ./
+
+debuild -us -uc
+
+popd
diff --git a/packages/google-compute-engine-oslogin/packaging/setup_rpm.sh b/packages/google-compute-engine-oslogin/packaging/setup_rpm.sh
new file mode 100755
index 0000000..640b9e2
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/packaging/setup_rpm.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# 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.
+
+
+NAME="google-compute-engine-oslogin"
+VERSION="1.4.3"
+
+rpm_working_dir=/tmp/rpmpackage/${NAME}-${VERSION}
+working_dir=${PWD}
+if [[ $(basename "$working_dir") != $NAME ]]; then
+ echo "Packaging scripts must be run from top of package dir."
+ exit 1
+fi
+
+# Build dependencies.
+sudo yum -y install make gcc-c++ libcurl-devel json-c json-c-devel pam-devel policycoreutils-python boost-devel
+
+# RPM creation tools.
+sudo yum -y install rpmdevtools
+
+rm -rf /tmp/rpmpackage
+mkdir -p ${rpm_working_dir}/{SOURCES,SPECS}
+cp packaging/${NAME}.spec ${rpm_working_dir}/SPECS/
+
+tar czvf ${rpm_working_dir}/SOURCES/${NAME}_${VERSION}.orig.tar.gz --exclude .git --exclude packaging --transform "s/^\./${NAME}-${VERSION}/" .
+
+rpmbuild --define "_topdir ${rpm_working_dir}/" -ba ${rpm_working_dir}/SPECS/${NAME}.spec
diff --git a/packages/google-compute-engine-oslogin/pam_module/pam_oslogin_admin.cc b/packages/google-compute-engine-oslogin/pam_module/pam_oslogin_admin.cc
new file mode 100644
index 0000000..0b5f3c0
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/pam_module/pam_oslogin_admin.cc
@@ -0,0 +1,100 @@
+// 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 <security/pam_appl.h>
+#include <security/pam_ext.h>
+#include <security/pam_modules.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include "../utils/oslogin_utils.h"
+
+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_IWUSR | S_IRGRP);
+ }
+ } else if (file_exists) {
+ remove(filename.c_str());
+ }
+ return pam_result;
+}
+}
diff --git a/packages/google-compute-engine-oslogin/pam_module/pam_oslogin_login.cc b/packages/google-compute-engine-oslogin/pam_module/pam_oslogin_login.cc
new file mode 100644
index 0000000..fa1b151
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/pam_module/pam_oslogin_login.cc
@@ -0,0 +1,264 @@
+// 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 <security/pam_appl.h>
+#include <security/pam_ext.h>
+#include <security/pam_modules.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <map>
+
+#include "../utils/oslogin_utils.h"
+
+using std::string;
+
+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) {
+ int pam_result = PAM_PERM_DENIED;
+ 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;
+ }
+ string str_user_name(user_name);
+ if (!ValidateUserName(user_name)) {
+ // If the user name is not a valid oslogin user, don't bother continuing.
+ return PAM_SUCCESS;
+ }
+ string users_filename = kUsersDir;
+ users_filename.append(user_name);
+ struct stat buffer;
+ bool file_exists = !stat(users_filename.c_str(), &buffer);
+
+ std::stringstream url;
+ url << kMetadataServerUrl << "users?username=" << UrlEncode(str_user_name);
+ string response;
+ long http_code = 0;
+ if (!HttpGet(url.str(), &response, &http_code) || response.empty() ||
+ http_code != 200) {
+ if (http_code == 404) {
+ // Return success on non-oslogin users.
+ return PAM_SUCCESS;
+ }
+ // If we can't reliably tell if this is an oslogin user, check if there is
+ // a local file for that user as a last resort.
+ if (file_exists) {
+ return PAM_PERM_DENIED;
+ }
+ // Otherwise, fall back on success to allow local users to log in.
+ return PAM_SUCCESS;
+ }
+
+ string email;
+ if (!ParseJsonToEmail(response, &email) || email.empty()) {
+ return PAM_PERM_DENIED;
+ }
+
+ url.str("");
+ url << kMetadataServerUrl << "authorize?email=" << UrlEncode(email)
+ << "&policy=login";
+ if (HttpGet(url.str(), &response, &http_code) && http_code == 200 &&
+ ParseJsonToSuccess(response)) {
+ 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);
+ }
+ pam_syslog(pamh, LOG_INFO,
+ "Granting login permission for organization user %s.",
+ user_name);
+ pam_result = PAM_SUCCESS;
+ } else {
+ if (file_exists) {
+ remove(users_filename.c_str());
+ }
+ pam_syslog(pamh, LOG_INFO,
+ "Denying login permission for organization user %s.",
+ user_name);
+
+ pam_result = PAM_PERM_DENIED;
+ }
+ return pam_result;
+}
+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;
+ }
+
+ string str_user_name(user_name);
+ if (!ValidateUserName(user_name)) {
+ return PAM_PERM_DENIED;
+ }
+
+ string response;
+ if (!(GetUser(str_user_name, &response))) {
+ return PAM_PERM_DENIED;
+ }
+
+ // System accounts begin with the prefix `sa_`.
+ string sa_prefix = "sa_";
+ if (str_user_name.compare(0, sa_prefix.size(), sa_prefix) == 0) {
+ return PAM_SUCCESS;
+ }
+
+ 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;
+ }
+
+ 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_SUCCESS; // User is not two-factor enabled.
+ }
+
+ string session_id;
+ if (!ParseJsonToKey(response, "sessionId", &session_id)) {
+ return PAM_PERM_DENIED;
+ }
+
+ std::vector<oslogin_utils::Challenge> challenges;
+ if (!ParseJsonToChallenges(response, &challenges)) {
+ pam_syslog(pamh, LOG_ERR,
+ "Failed to parse challenge values from JSON response");
+ return PAM_PERM_DENIED;
+ }
+
+ std::map<string,string> 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";
+
+ oslogin_utils::Challenge challenge;
+ if (challenges.size() > 1) {
+ std::stringstream prompt;
+ prompt << "Available authentication methods: ";
+ for(vector<oslogin_utils::Challenge>::size_type i = 0;
+ i != challenges.size(); ++i)
+ prompt << "\n" << i+1 << ": " << user_prompts[challenges[i].type];
+ prompt << "\n\nEnter a number: ";
+
+ 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");
+ }
+
+ int choicei;
+ if (sscanf(choice, "%d", &choicei) == EOF) {
+ pam_error(pamh, "Unable to get user input");
+ }
+ if (choicei > challenges.size()) {
+ pam_error(pamh, "Invalid option");
+ }
+ challenge = challenges[choicei - 1];
+ } else {
+ challenge = challenges[0];
+ }
+
+ 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");
+ }
+ } 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");
+ }
+ } 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");
+ }
+ } 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");
+ }
+ } else {
+ pam_syslog(pamh, LOG_ERR, "Unsupported challenge type %s",
+ challenge.type.c_str());
+ return PAM_PERM_DENIED;
+ }
+
+ if (!ContinueSession(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") {
+ return PAM_PERM_DENIED;
+ }
+
+ return PAM_SUCCESS;
+}
+}
diff --git a/packages/google-compute-engine-oslogin/policy/Makefile b/packages/google-compute-engine-oslogin/policy/Makefile
new file mode 100644
index 0000000..c858c9d
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/policy/Makefile
@@ -0,0 +1,17 @@
+# 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)
diff --git a/packages/google-compute-engine-oslogin/policy/README.md b/packages/google-compute-engine-oslogin/policy/README.md
new file mode 100644
index 0000000..b2cc5a9
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/policy/README.md
@@ -0,0 +1,17 @@
+## 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.
diff --git a/packages/google-compute-engine-oslogin/policy/oslogin.fc b/packages/google-compute-engine-oslogin/policy/oslogin.fc
new file mode 100644
index 0000000..3e70358
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/policy/oslogin.fc
@@ -0,0 +1,2 @@
+/var/google-sudoers.d(/.*)? system_u:object_r:google_t:s0
+/var/google-users.d(/.*)? system_u:object_r:google_t:s0
diff --git a/packages/google-compute-engine-oslogin/policy/oslogin.pp b/packages/google-compute-engine-oslogin/policy/oslogin.pp
new file mode 100644
index 0000000..6ec6ed0
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/policy/oslogin.pp
Binary files differ
diff --git a/packages/google-compute-engine-oslogin/policy/oslogin.te b/packages/google-compute-engine-oslogin/policy/oslogin.te
new file mode 100644
index 0000000..381f769
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/policy/oslogin.te
@@ -0,0 +1,24 @@
+
+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;
diff --git a/packages/google-compute-engine-oslogin/utils/oslogin_utils.cc b/packages/google-compute-engine-oslogin/utils/oslogin_utils.cc
new file mode 100644
index 0000000..f035adb
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/utils/oslogin_utils.cc
@@ -0,0 +1,657 @@
+// 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.
+
+// Requires libcurl4-openssl-dev libjson0 and libjson0-dev
+#include <curl/curl.h>
+#include <errno.h>
+#include <json.h>
+#include <nss.h>
+#include <stdio.h>
+#include <time.h>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+
+#ifdef __GNUC__
+#if __GNUC__ > 4 || \
+ (__GNUC__ == 4 && (__GNUC_MINOR__ > 9 || \
+ (__GNUC_MINOR__ == 9 && \
+ __GNUC_PATCHLEVEL__ > 0)))
+#include <regex>
+#define Regex std
+#else
+#include <boost/regex.hpp>
+#define Regex boost
+#endif
+#endif
+
+#include "oslogin_utils.h"
+
+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 {
+
+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;
+ if (!CheckSpaceAvailable(bytes_to_write)) {
+ *errnop = ERANGE;
+ return false;
+ }
+ *buffer = static_cast<char*>(Reserve(bytes_to_write));
+ 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) {
+ if (buflen_ < bytes) {
+ std::cerr << "Attempted to reserve more bytes than the buffer can hold!"
+ << "\n";
+ abort();
+ }
+ void* result = buf_;
+ buf_ += bytes;
+ buflen_ -= bytes;
+ return result;
+}
+
+NssCache::NssCache(int cache_size)
+ : cache_size_(cache_size),
+ passwd_cache_(cache_size),
+ page_token_(""),
+ on_last_page_(false) {}
+
+void NssCache::Reset() {
+ page_token_ = "";
+ index_ = 0;
+ passwd_cache_.clear();
+ on_last_page_ = false;
+}
+
+bool NssCache::HasNextPasswd() {
+ return index_ < passwd_cache_.size() && !passwd_cache_[index_].empty();
+}
+
+bool NssCache::GetNextPasswd(BufferManager* buf, passwd* result, int* errnop) {
+ if (!HasNextPasswd()) {
+ *errnop = ENOENT;
+ return false;
+ }
+ string cached_passwd = passwd_cache_[index_];
+ bool success = ParseJsonToPasswd(cached_passwd, result, buf, errnop);
+ if (success) {
+ index_++;
+ }
+ return success;
+}
+
+bool NssCache::LoadJsonArrayToCache(string response) {
+ Reset();
+ json_object* root = NULL;
+ root = json_tokener_parse(response.c_str());
+ if (root == NULL) {
+ return false;
+ }
+ // 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 {
+ // If the page token is not found, assume something went wrong.
+ page_token_ = "";
+ on_last_page_ = true;
+ return false;
+ }
+ // 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;
+ return false;
+ }
+ // Now grab all of the loginProfiles.
+ json_object* login_profiles = NULL;
+ if (!json_object_object_get_ex(root, "loginProfiles", &login_profiles)) {
+ page_token_ = "";
+ return false;
+ }
+ if (json_object_get_type(login_profiles) != json_type_array) {
+ return false;
+ }
+ int arraylen = json_object_array_length(login_profiles);
+ if (arraylen == 0 || arraylen > cache_size_) {
+ page_token_ = "";
+ return false;
+ }
+ for (int i = 0; i < arraylen; i++) {
+ json_object* profile = json_object_array_get_idx(login_profiles, i);
+ passwd_cache_.push_back(
+ json_object_to_json_string_ext(profile, JSON_C_TO_STRING_PLAIN));
+ }
+ return true;
+}
+
+bool NssCache::NssGetpwentHelper(BufferManager* buf, struct passwd* result,
+ int* errnop) {
+ if (!HasNextPasswd() && !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;
+ if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 ||
+ response.empty() || !LoadJsonArrayToCache(response)) {
+ // It is possible this to be true after LoadJsonArrayToCache(), so we
+ // must check it again.
+ if(!OnLastPage()) {
+ *errnop = ENOENT;
+ }
+ return false;
+ }
+ }
+ if (HasNextPasswd() && !GetNextPasswd(buf, result, errnop)) {
+ return false;
+ }
+ return true;
+}
+
+size_t OnCurlWrite(void* buf, size_t size, size_t nmemb, void* userp) {
+ if (userp) {
+ std::ostream& os = *static_cast<std::ostream*>(userp);
+ std::streamsize len = size * nmemb;
+ if (os.write(static_cast<char*>(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_FILE, &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);
+}
+
+bool ValidateUserName(const string& user_name) {
+ Regex::regex r(kUserNameRegex);
+ return Regex::regex_match(user_name, r);
+}
+
+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 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("/bin/bash", &result->pw_shell, errnop)) {
+ return false;
+ }
+ }
+
+ // OS Login does not utilize the passwd field and reserves the gecos field.
+ // Set these to be empty.
+ if (!buf->AppendString("", &result->pw_gecos, errnop)) {
+ return false;
+ }
+ if (!buf->AppendString("", &result->pw_passwd, errnop)) {
+ return false;
+ }
+ return true;
+}
+
+std::vector<string> ParseJsonToSshKeys(const string& json) {
+ std::vector<string> result;
+ 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)) {
+ return result;
+ }
+ if (json_object_get_type(login_profiles) != json_type_array) {
+ return result;
+ }
+ login_profiles = json_object_array_get_idx(login_profiles, 0);
+
+ json_object* ssh_public_keys = NULL;
+ if (!json_object_object_get_ex(login_profiles, "sshPublicKeys",
+ &ssh_public_keys)) {
+ return result;
+ }
+
+ if (json_object_get_type(ssh_public_keys) != json_type_object) {
+ return result;
+ }
+ json_object_object_foreach(ssh_public_keys, key, val) {
+ json_object* iter;
+ if (!json_object_object_get_ex(ssh_public_keys, key, &iter)) {
+ return result;
+ }
+ if (json_object_get_type(iter) != json_type_object) {
+ continue;
+ }
+ string key_to_add = "";
+ bool expired = false;
+ json_object_object_foreach(iter, 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 = (char*)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);
+ }
+ }
+ return result;
+}
+
+bool ParseJsonToPasswd(const string& json, struct passwd* result,
+ BufferManager* buf, int* errnop) {
+ json_object* root = NULL;
+ root = json_tokener_parse(json.c_str());
+ if (root == NULL) {
+ *errnop = ENOENT;
+ return false;
+ }
+ 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) {
+ return false;
+ }
+ root = login_profiles;
+ root = json_object_array_get_idx(root, 0);
+ }
+ // Locate the posixAccounts object.
+ json_object* posix_accounts = NULL;
+ if (!json_object_object_get_ex(root, "posixAccounts", &posix_accounts)) {
+ *errnop = ENOENT;
+ return false;
+ }
+ if (json_object_get_type(posix_accounts) != json_type_array) {
+ return false;
+ }
+ 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*)"";
+
+ // Iterate through the json response and populate the passwd struct.
+ if (json_object_get_type(posix_accounts) != json_type_object) {
+ return false;
+ }
+ 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) {
+ *errnop = EINVAL;
+ return false;
+ }
+ } else {
+ *errnop = EINVAL;
+ return false;
+ }
+ } 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 {
+ *errnop = EINVAL;
+ return false;
+ }
+ } else if (string_key == "username") {
+ if (val_type != json_type_string) {
+ *errnop = EINVAL;
+ return false;
+ }
+ if (!buf->AppendString((char*)json_object_get_string(val),
+ &result->pw_name, errnop)) {
+ return false;
+ }
+ } else if (string_key == "homeDirectory") {
+ if (val_type != json_type_string) {
+ *errnop = EINVAL;
+ return false;
+ }
+ if (!buf->AppendString((char*)json_object_get_string(val),
+ &result->pw_dir, errnop)) {
+ return false;
+ }
+ } else if (string_key == "shell") {
+ if (val_type != json_type_string) {
+ *errnop = EINVAL;
+ return false;
+ }
+ if (!buf->AppendString((char*)json_object_get_string(val),
+ &result->pw_shell, errnop)) {
+ return false;
+ }
+ }
+ }
+
+ return ValidatePasswd(result, buf, errnop);
+}
+
+bool ParseJsonToEmail(const string& json, string* email) {
+ json_object* root = NULL;
+ root = json_tokener_parse(json.c_str());
+ if (root == NULL) {
+ return false;
+ }
+ // Locate the email object.
+ json_object* login_profiles = NULL;
+ if (!json_object_object_get_ex(root, "loginProfiles", &login_profiles)) {
+ return false;
+ }
+ if (json_object_get_type(login_profiles) != json_type_array) {
+ return false;
+ }
+ login_profiles = json_object_array_get_idx(login_profiles, 0);
+ json_object* json_email = NULL;
+ if (!json_object_object_get_ex(login_profiles, "name", &json_email)) {
+ return false;
+ }
+
+ *email = json_object_get_string(json_email);
+ return true;
+}
+
+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)) {
+ return false;
+ }
+ return (bool)json_object_get_boolean(success);
+}
+
+bool ParseJsonToKey(const string& json, const string& key, string* response) {
+ json_object* root = NULL;
+ json_object* json_response = NULL;
+ const char* c_response;
+
+ root = json_tokener_parse(json.c_str());
+ if (root==NULL) {
+ return false;
+ }
+
+ if (!json_object_object_get_ex(root, key.c_str(), &json_response)) {
+ return false;
+ }
+
+ if (!(c_response = json_object_get_string(json_response))) {
+ return false;
+ }
+
+ *response = c_response;
+ return true;
+}
+
+bool ParseJsonToChallenges(const string& json,
+ std::vector<Challenge> *challenges) {
+ json_object* root = NULL;
+
+ root = json_tokener_parse(json.c_str());
+ if (root == NULL) {
+ return false;
+ }
+
+ json_object *jsonChallenges = NULL;
+ if (!json_object_object_get_ex(root, "challenges", &jsonChallenges)) {
+ return false;
+ }
+
+ json_object *challengeId, *challengeType, *challengeStatus = NULL;
+ for (int i = 0; i < json_object_array_length(jsonChallenges); ++i) {
+ if (!json_object_object_get_ex(json_object_array_get_idx(jsonChallenges, i),
+ "challengeId", &challengeId)) {
+ return false;
+ }
+ if (!json_object_object_get_ex(json_object_array_get_idx(jsonChallenges, i),
+ "challengeType", &challengeType)) {
+ return false;
+ }
+ if (!json_object_object_get_ex(json_object_array_get_idx(jsonChallenges, i),
+ "status", &challengeStatus)) {
+ return false;
+ }
+ 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);
+ }
+
+ 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;
+ struct json_object *jobj, *jarr;
+
+ 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(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);
+
+ 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(jarr);
+ json_object_put(jobj);
+
+ return ret;
+}
+
+bool ContinueSession(const string& email, const string& user_token,
+ const string& session_id, const Challenge& challenge,
+ string* response) {
+ bool ret = true;
+ struct json_object *jobj, *jresp;
+
+ 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 (challenge.type != AUTHZEN) {
+ 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);
+ }
+
+ if (challenge.status != "READY") {
+ json_object_object_add(jobj, "action",
+ json_object_new_string("startAlternate"));
+ }
+
+ 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);
+ if (challenge.type != AUTHZEN) {
+ json_object_put(jresp);
+ }
+
+ return ret;
+}
+} // namespace oslogin_utils
diff --git a/packages/google-compute-engine-oslogin/utils/oslogin_utils.h b/packages/google-compute-engine-oslogin/utils/oslogin_utils.h
new file mode 100644
index 0000000..3788521
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/utils/oslogin_utils.h
@@ -0,0 +1,202 @@
+// 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 <pthread.h>
+#include <pwd.h>
+#include <string>
+#include <vector>
+
+#define TOTP "TOTP"
+#define AUTHZEN "AUTHZEN"
+#define INTERNAL_TWO_FACTOR "INTERNAL_TWO_FACTOR"
+#define IDV_PREREGISTERED_PHONE "IDV_PREREGISTERED_PHONE"
+
+using std::string;
+using std::vector;
+
+namespace oslogin_utils {
+
+// Metadata server URL.
+static const char kMetadataServerUrl[] =
+ "http://metadata.google.internal/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);
+
+ private:
+ // Return a pointer to a buffer of size bytes.
+ void* Reserve(size_t bytes);
+ // 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;
+};
+
+// 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 passwd entry.
+ bool HasNextPasswd();
+
+ // Whether the cache has reached the last page of the database.
+ bool OnLastPage() { return on_last_page_; }
+
+ // Grabs the next passwd entry. Returns true on success. Sets errnop on
+ // failure.
+ bool GetNextPasswd(BufferManager* buf, passwd* result, int* errnop);
+
+ // Loads a json array of passwd 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 entries. Returns
+ // true on success.
+ bool LoadJsonArrayToCache(string response);
+
+ // Helper method that effectively implements the getpwent_r nss method. Each
+ // call will iterate through the OsLogin database and return the next entry.
+ // Internally, the cache will keep track of pages of passwd entries, and will
+ // make an http call to the server if necessary to retrieve additional
+ // entries. Returns whether passwd retrieval was successful. If true, the
+ // passwd result will contain valid data.
+ bool NssGetpwentHelper(BufferManager* buf, struct passwd* result, int* errnop);
+
+ // Returns the page token for requesting the next page of passwd entries.
+ string GetPageToken() { return page_token_; }
+
+ private:
+ // The maximum size of the cache.
+ int cache_size_;
+
+ // Vector of passwds. These are represented as stringified json object.
+ std::vector<std::string> passwd_cache_;
+
+ // The page token for requesting the next page of passwds.
+ std::string page_token_;
+
+ // Index for requesting the next passwd from the cache.
+ int 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);
+
+// 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<string> ParseJsonToSshKeys(const string& json);
+
+// Parses a JSON LoginProfiles response and returns the email under the "name"
+// field.
+bool ParseJsonToKey(const string& json, const string& key, string* email);
+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);
+
+// 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<Challenge> *challenges);
+
+// Calls the startSession API.
+bool StartSession(const string& email, string* response);
+
+// Calls the continueSession API.
+bool ContinueSession(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
diff --git a/packages/google-compute-engine-oslogin/utils/oslogin_utils_test.cc b/packages/google-compute-engine-oslogin/utils/oslogin_utils_test.cc
new file mode 100644
index 0000000..ecc4c11
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/utils/oslogin_utils_test.cc
@@ -0,0 +1,510 @@
+// 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.
+
+// Requires libgtest-dev and gtest compiled and installed.
+#include "oslogin_utils.h"
+
+#include <errno.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+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);
+ EXPECT_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));
+ EXPECT_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.LoadJsonArrayToCache(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.HasNextPasswd());
+ ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno));
+ EXPECT_EQ(test_errno, 0);
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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.HasNextPasswd());
+ ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno));
+ EXPECT_EQ(test_errno, 0);
+ EXPECT_EQ(result.pw_uid, 1338);
+ EXPECT_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.HasNextPasswd());
+ ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno));
+ EXPECT_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.LoadJsonArrayToCache(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.HasNextPasswd());
+ ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno));
+ EXPECT_EQ(test_errno, 0);
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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.HasNextPasswd());
+ ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno));
+ EXPECT_EQ(test_errno, ENOENT);
+}
+
+// Test successfully loading and retrieving the final response.
+TEST(NssCacheTest, TestLoadJsonFinalResponse) {
+ NssCache nss_cache(2);
+ string response =
+ "{\"nextPageToken\": \"0\"}";
+
+ ASSERT_FALSE(nss_cache.LoadJsonArrayToCache(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.HasNextPasswd());
+ ASSERT_TRUE(nss_cache.OnLastPage());
+ ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno));
+ EXPECT_EQ(test_errno, ENOENT);
+}
+
+
+// Tests that resetting, and checking HasNextPasswd does not crash.
+TEST(NssCacheTest, ResetNullPtrTest) {
+ NssCache nss_cache(2);
+ nss_cache.Reset();
+ ASSERT_FALSE(nss_cache.HasNextPasswd());
+}
+
+// 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));
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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));
+ EXPECT_EQ(result.pw_uid, 4294967295);
+ EXPECT_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));
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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));
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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));
+ EXPECT_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));
+ EXPECT_EQ(test_errno, EINVAL);
+ // Reset errno.
+ test_errno = 0;
+ ASSERT_TRUE(ParseJsonToPasswd(test_user2, &result, &buf, &test_errno));
+ EXPECT_EQ(test_errno, 0);
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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));
+ EXPECT_EQ(result.pw_uid, 1337);
+ EXPECT_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));
+ EXPECT_EQ(test_errno, EINVAL);
+}
+
+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);
+ int test_errno = 0;
+ std::vector<string> result = ParseJsonToSshKeys(test_user);
+ EXPECT_EQ(result.size(), 1);
+ EXPECT_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);
+ int test_errno = 0;
+ std::vector<string> result = ParseJsonToSshKeys(test_user);
+ EXPECT_EQ(result.size(), 2);
+ EXPECT_EQ(result[0], "test_key");
+ EXPECT_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);
+ int test_errno = 0;
+ std::vector<string> result = ParseJsonToSshKeys(test_user);
+ EXPECT_EQ(result.size(), 1);
+ EXPECT_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);
+ int test_errno = 0;
+ std::vector<string> result = ParseJsonToSshKeys(test_user);
+ EXPECT_EQ(result.size(), 1);
+ EXPECT_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<Challenge> challenges;
+ ASSERT_TRUE(ParseJsonToChallenges(challenges_json, &challenges));
+ EXPECT_EQ(challenges.size(), 2);
+ EXPECT_EQ(challenges[0].id, 1);
+ EXPECT_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<Challenge> challenges;
+ ASSERT_FALSE(ParseJsonToChallenges(challenges_json, &challenges));
+ EXPECT_EQ(challenges.size(), 1);
+}
+} // namespace oslogin_utils
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/packages/google-compute-engine-oslogin/utils/run_tests.sh b/packages/google-compute-engine-oslogin/utils/run_tests.sh
new file mode 100755
index 0000000..83adcdc
--- /dev/null
+++ b/packages/google-compute-engine-oslogin/utils/run_tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+# 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.
+
+# Unit tests require gtest to be installed.
+g++ -o test_runner oslogin_utils_test.cc oslogin_utils.cc -I/usr/include/json-c -lcurl -ljson-c -lgtest -lpthread
+./test_runner
+rm ./test_runner