[TACACS+] Add Bash TACACS+ plugin for per-command authorization. (#8715)
This pull request add a bash plugin for TACACS+ per-command authorization #### Why I did it 1. To support TACACS per command authorization, we check user command before execute it. 2. Fix libtacsupport.so can't parse tacplus_nss.conf correctly issue: Support debug=on setting. Support put server address and secret in same row. 3. Fix the parse_config_file method not reset server list before parse config file issue. #### How I did it The bash plugin will be called before every user command, and check user command with remote TACACS+ server for per-command authorization. #### How to verify it UT with CUnit cover all code in this plugin. Also pass all current UT. #### Which release branch to backport (provide reason below if selected) N/A #### Description for the changelog Add Bash TACACS+ plugin. #### A picture of a cute animal (not mandatory but encouraged)
This commit is contained in:
parent
645173307a
commit
ff09b8b8ed
@ -1170,3 +1170,33 @@ Microsoft is offering you a license to use the following components, to the exte
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
5. src/tacacs/bash/bash_tacplus based on https://github.com/daveolson53/tacplus-auth project using GNU GENERAL PUBLIC LICENSE Version 2
|
||||
|
||||
/* Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
* Upstream-Name: tacplus-auth
|
||||
* Source: https://github.com/daveolson53/tacplus-auth
|
||||
*
|
||||
* Files: *
|
||||
* Copyright: 2016 Cumulus Networks, Inc. All rights reserved.,
|
||||
* 2010 Pawel Krawczyk <pawel.krawczyk@hush.com> and Jeroen Nijhof <jeroen@jeroennijhof.nl>.
|
||||
* License: GPL-2+
|
||||
*
|
||||
* License: GPL-2+
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* .
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* .
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* .
|
||||
* On Debian systems, the full copy of the GPL-2 license can be found in
|
||||
* /usr/share/common-licenses/GPL-2
|
||||
*/
|
@ -225,7 +225,7 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/sonic-utilities-data_*.deb || \
|
||||
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
|
||||
|
||||
# Install customized bash version to patch bash plugin support.
|
||||
sudo dpkg --root=$FILESYSTEM_ROOT -i target/debs/bash_*.deb || \
|
||||
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/bash_*.deb || \
|
||||
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
|
||||
|
||||
# sonic-utilities-data installs bash-completion as a dependency. However, it is disabled by default
|
||||
@ -274,6 +274,9 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libpam-tacplus_*.deb || \
|
||||
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
|
||||
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-tacplus_*.deb || \
|
||||
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
|
||||
# Install bash-tacplus
|
||||
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/bash-tacplus_*.deb || \
|
||||
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
|
||||
# Disable tacplus by default
|
||||
sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove tacplus
|
||||
sudo sed -i -e '/^passwd/s/ tacplus//' $FILESYSTEM_ROOT/etc/nsswitch.conf
|
||||
|
@ -21,3 +21,15 @@ $(LIBNSS_TACPLUS)_CACHE_MODE := GIT_CONTENT_SHA
|
||||
$(LIBNSS_TACPLUS)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST)
|
||||
$(LIBNSS_TACPLUS)_DEP_FILES := $(DEP_FILES)
|
||||
|
||||
|
||||
|
||||
|
||||
SPATH := $($(BASH_TACPLUS)_SRC_PATH)
|
||||
DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/tacacs.mk rules/tacacs.dep
|
||||
DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST)
|
||||
DEP_FILES += $(shell git ls-files $(SPATH))
|
||||
|
||||
$(BASH_TACPLUS)_CACHE_MODE := GIT_CONTENT_SHA
|
||||
$(BASH_TACPLUS)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST)
|
||||
$(BASH_TACPLUS)_DEP_FILES := $(DEP_FILES)
|
||||
|
||||
|
@ -29,6 +29,19 @@ $(LIBNSS_TACPLUS)_RDEPENDS += $(LIBTAC2)
|
||||
$(LIBNSS_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/nss
|
||||
SONIC_MAKE_DEBS += $(LIBNSS_TACPLUS)
|
||||
|
||||
|
||||
# bash-tacplus packages
|
||||
BASH_TACPLUS_VERSION = 1.0.0
|
||||
|
||||
export BASH_TACPLUS_VERSION
|
||||
|
||||
BASH_TACPLUS = bash-tacplus_$(BASH_TACPLUS_VERSION)_$(CONFIGURED_ARCH).deb
|
||||
$(BASH_TACPLUS)_DEPENDS += $(LIBTAC_DEV)
|
||||
$(BASH_TACPLUS)_RDEPENDS += $(LIBTAC2)
|
||||
$(BASH_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/bash_tacplus
|
||||
SONIC_DPKG_DEBS += $(BASH_TACPLUS)
|
||||
|
||||
|
||||
# The .c, .cpp, .h & .hpp files under src/{$DBG_SRC_ARCHIVE list}
|
||||
# are archived into debug one image to facilitate debugging.
|
||||
#
|
||||
|
3
slave.mk
3
slave.mk
@ -943,7 +943,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
|
||||
$(PYTHON3_SWSSCOMMON) \
|
||||
$(SONIC_UTILITIES_DATA) \
|
||||
$(SONIC_HOST_SERVICES_DATA) \
|
||||
$(BASH)) \
|
||||
$(BASH) \
|
||||
$(BASH_TACPLUS)) \
|
||||
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
|
||||
$$(addprefix $(TARGET_PATH)/,$$(SONIC_PACKAGES_LOCAL)) \
|
||||
$$(addprefix $(FILES_PATH)/,$$($$*_FILES)) \
|
||||
|
1
src/tacacs/.gitignore
vendored
1
src/tacacs/.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
*
|
||||
!.gitignore
|
||||
!bash_tacplus/*
|
||||
nsm/*
|
||||
!nsm/Makefile
|
||||
!nsm/*.patch
|
||||
|
27
src/tacacs/bash_tacplus/Makefile.am
Normal file
27
src/tacacs/bash_tacplus/Makefile.am
Normal file
@ -0,0 +1,27 @@
|
||||
###########################################################################
|
||||
##
|
||||
## File: ./Makefile.am
|
||||
## Versions: $Id: Makefile.am,v 1.0 2021/08/24 12:04:29 liuh@microsoft.com Exp $
|
||||
## Created: 2021/08/24
|
||||
##
|
||||
###########################################################################
|
||||
|
||||
ACLOCAL_AMFLAGS = -I config
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
|
||||
moduledir = @plugindir@
|
||||
module_LTLIBRARIES = bash_tacplus.la
|
||||
bash_tacplus_la_SOURCES = bash_tacplus.h \
|
||||
bash_tacplus.c
|
||||
bash_tacplus_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libtac/include
|
||||
bash_tacplus_la_LDFLAGS = -module -avoid-version
|
||||
|
||||
EXTRA_DIST = bash_tacplus.spec
|
||||
|
||||
MAINTAINERCLEANFILES = Makefile.in config.h.in configure aclocal.m4 \
|
||||
config/config.guess config/config.sub config/depcomp \
|
||||
config/install-sh config/ltmain.sh config/missing
|
||||
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
|
||||
SUBDIRS = unittest
|
488
src/tacacs/bash_tacplus/bash_tacplus.c
Normal file
488
src/tacacs/bash_tacplus/bash_tacplus.c
Normal file
@ -0,0 +1,488 @@
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <pwd.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Remote user gecos prefix, which been assigned by nss_tacplus */
|
||||
#define REMOTE_USER_GECOS_PREFIX "remote_user"
|
||||
|
||||
/* Default value for _SC_GETPW_R_SIZE_MAX */
|
||||
#define DEFAULT_SC_GETPW_R_SIZE_MAX 1024
|
||||
|
||||
/* Return value for is_local_user method */
|
||||
#define IS_LOCAL_USER 0
|
||||
#define IS_REMOTE_USER 1
|
||||
#define ERROR_CHECK_LOCAL_USER 2
|
||||
|
||||
/* Tacacs+ lib */
|
||||
#include <libtac/libtac.h>
|
||||
|
||||
/* Tacacs+ support lib */
|
||||
#include <libtac/support.h>
|
||||
|
||||
/* Output syslog to mock method when build with UT */
|
||||
#if defined (BASH_PLUGIN_UT)
|
||||
#define syslog mock_syslog
|
||||
#endif
|
||||
|
||||
/* Tacacs+ log format */
|
||||
#define TACACS_LOG_FORMAT "TACACS+: %s"
|
||||
|
||||
/* Tacacs+ config file timestamp string format */
|
||||
#define CONFIG_FILE_TIME_STAMP_FORMAT "%d.%m.%Y %H:%M:%S"
|
||||
|
||||
/* Tacacs+ config file timestamp string length */
|
||||
#define CONFIG_FILE_TIME_STAMP_LEN 100
|
||||
|
||||
/*
|
||||
Convert log to a string because va args resoursive issue:
|
||||
http://www.c-faq.com/varargs/handoff.html
|
||||
*/
|
||||
#define GENERATE_LOG_FROM_VA(logBufferName) \
|
||||
char logBufferName[512]; \
|
||||
va_list args; \
|
||||
va_start(args, format); \
|
||||
vsnprintf(logBufferName, sizeof(logBufferName), format, args); \
|
||||
va_end(args);
|
||||
|
||||
/* Config file path */
|
||||
const char *tacacs_config_file = "/etc/tacplus_nss.conf";
|
||||
|
||||
/* Unknown user name */
|
||||
const char *unknown_username = "UNKNOWN";
|
||||
|
||||
|
||||
/* Config file attribute */
|
||||
struct stat config_file_attr;
|
||||
|
||||
/* Tacacs server config data */
|
||||
typedef struct {
|
||||
struct addrinfo *address;
|
||||
const char *key;
|
||||
} tacacs_server_t;
|
||||
|
||||
/* Tacacs control flag */
|
||||
int tacacs_ctrl;
|
||||
|
||||
/*
|
||||
* Output error message.
|
||||
*/
|
||||
void output_error(const char *format, ...)
|
||||
{
|
||||
GENERATE_LOG_FROM_VA(logBuffer);
|
||||
|
||||
if (tacacs_ctrl & PAM_TAC_DEBUG) {
|
||||
fprintf(stderr, TACACS_LOG_FORMAT, logBuffer);
|
||||
}
|
||||
|
||||
syslog(LOG_ERR, TACACS_LOG_FORMAT, logBuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Output debug message.
|
||||
*/
|
||||
void output_debug(const char *format, ...)
|
||||
{
|
||||
if ((tacacs_ctrl & PAM_TAC_DEBUG) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GENERATE_LOG_FROM_VA(logBuffer);
|
||||
fprintf(stderr, TACACS_LOG_FORMAT, logBuffer);
|
||||
syslog(LOG_DEBUG, TACACS_LOG_FORMAT, logBuffer);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send authorization message.
|
||||
* This method based on send_auth_msg in https://github.com/daveolson53/tacplus-auth/blob/master/tacplus-auth.c
|
||||
*/
|
||||
int send_authorization_message(
|
||||
int tac_fd,
|
||||
const char *user,
|
||||
const char *tty,
|
||||
const char *host,
|
||||
uint16_t taskid,
|
||||
const char *cmd,
|
||||
char **args,
|
||||
int argc)
|
||||
{
|
||||
char buf[128];
|
||||
struct tac_attrib *attr;
|
||||
int retval;
|
||||
struct areply re;
|
||||
int i;
|
||||
|
||||
attr=(struct tac_attrib *)xcalloc(1, sizeof(struct tac_attrib));
|
||||
|
||||
snprintf(buf, sizeof buf, "%hu", taskid);
|
||||
tac_add_attrib(&attr, "task_id", buf);
|
||||
tac_add_attrib(&attr, "protocol", "ssh");
|
||||
tac_add_attrib(&attr, "service", "shell");
|
||||
|
||||
tac_add_attrib(&attr, "cmd", (char*)cmd);
|
||||
|
||||
for(i=1; i<argc; i++) {
|
||||
// TACACS protocol allow max 255 bytes per argument. 'cmd-arg' will take 7 bytes.
|
||||
char tbuf[248];
|
||||
const char *arg;
|
||||
if(strlen(args[i]) >= sizeof(tbuf)) {
|
||||
snprintf(tbuf, sizeof tbuf, "%s", args[i]);
|
||||
arg = tbuf;
|
||||
}
|
||||
else {
|
||||
arg = args[i];
|
||||
}
|
||||
|
||||
tac_add_attrib(&attr, "cmd-arg", (char *)arg);
|
||||
}
|
||||
|
||||
re.msg = NULL;
|
||||
output_debug("send authorizatiom message with user: %s, tty: %s, host: %s\n", user, tty, host);
|
||||
retval = tac_author_send(tac_fd, (char *)user, (char *)tty, (char *)host, attr);
|
||||
output_debug("authorization result: %d\n", retval);
|
||||
|
||||
if(retval < 0) {
|
||||
output_error("send of authorization message failed: %s\n", strerror(errno));
|
||||
}
|
||||
else {
|
||||
retval = tac_author_read(tac_fd, &re);
|
||||
if (retval < 0) {
|
||||
output_debug("authorization response failed: %d\n", retval);
|
||||
}
|
||||
else if(re.status == AUTHOR_STATUS_PASS_ADD ||
|
||||
re.status == AUTHOR_STATUS_PASS_REPL) {
|
||||
retval = 0;
|
||||
}
|
||||
else {
|
||||
output_debug("command not authorized (%d)\n", re.status);
|
||||
retval = 1;
|
||||
}
|
||||
}
|
||||
|
||||
tac_free_attrib(&attr);
|
||||
if(re.msg != NULL) {
|
||||
free(re.msg);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send tacacs authorization request.
|
||||
* This method based on send_tacacs_auth in https://github.com/daveolson53/tacplus-auth/blob/master/tacplus-auth.c
|
||||
*/
|
||||
int tacacs_authorization(
|
||||
const char *user,
|
||||
const char *tty,
|
||||
const char *host,
|
||||
const char *cmd,
|
||||
char **args,
|
||||
int argc)
|
||||
{
|
||||
int result = 1, server_idx, server_fd, connected_servers=0;
|
||||
uint16_t task_id = (uint16_t)getpid();
|
||||
|
||||
for(server_idx = 0; server_idx < tac_srv_no; server_idx++) {
|
||||
server_fd = tac_connect_single(tac_srv[server_idx].addr, tac_srv[server_idx].key, tac_source_addr, tac_timeout, __vrfname);
|
||||
if(server_fd < 0) {
|
||||
// connect to tacacs server failed
|
||||
output_error("Failed to connecting to %s to request authorization for %s: %s\n", tac_ntop(tac_srv[server_idx].addr->ai_addr), cmd, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
// increase connected servers
|
||||
connected_servers++;
|
||||
result = send_authorization_message(server_fd, user, tty, host, task_id, cmd, args, argc);
|
||||
close(server_fd);
|
||||
if(result) {
|
||||
// authorization failed
|
||||
output_debug("%s not authorized from %s\n", cmd, tac_ntop(tac_srv[server_idx].addr->ai_addr));
|
||||
}
|
||||
else {
|
||||
// authorization successed
|
||||
output_debug("%s authorized from %s\n", cmd, tac_ntop(tac_srv[server_idx].addr->ai_addr));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// can't connect to any server
|
||||
if(!connected_servers) {
|
||||
result = -2;
|
||||
output_error("Failed to connect to TACACS server(s)\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send authorization request.
|
||||
* This method based on build_auth_req in https://github.com/daveolson53/tacplus-auth/blob/master/tacplus-auth.c
|
||||
*/
|
||||
int authorization_with_host_and_tty(const char *user, const char *cmd, char **argv, int argc)
|
||||
{
|
||||
// try get host name
|
||||
char hostname[64];
|
||||
memset(&hostname, 0, sizeof(hostname));
|
||||
|
||||
(void)gethostname(hostname, sizeof(hostname) -1);
|
||||
if (!hostname[0]) {
|
||||
snprintf(hostname, sizeof(hostname), "UNK");
|
||||
output_error("Failed to determine hostname, passing %s\n", hostname);
|
||||
}
|
||||
|
||||
// try get tty name
|
||||
char ttyname[64];
|
||||
memset(&ttyname, 0, sizeof(ttyname));
|
||||
|
||||
int i;
|
||||
for(i=0; i<3; i++) {
|
||||
int result;
|
||||
if (isatty(i)) {
|
||||
result = ttyname_r(i, ttyname, sizeof(ttyname) -1);
|
||||
if (result) {
|
||||
output_error("Failed to get tty name for fd %d: %s\n", i, strerror(result));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ttyname[0]) {
|
||||
snprintf(ttyname, sizeof(ttyname), "UNK");
|
||||
output_error("Failed to determine tty, passing %s\n", ttyname);
|
||||
}
|
||||
|
||||
// send tacacs authorization request
|
||||
return tacacs_authorization(user, ttyname, hostname, cmd, argv, argc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Load tacacs config.
|
||||
*/
|
||||
void load_tacacs_config()
|
||||
{
|
||||
// load config file: tacacs_config_file
|
||||
tacacs_ctrl = parse_config_file (tacacs_config_file);
|
||||
|
||||
output_debug("tacacs config updated:\n");
|
||||
int server_idx;
|
||||
for(server_idx = 0; server_idx < tac_srv_no; server_idx++) {
|
||||
output_debug("Server %d, address:%s, key length:%d\n", server_idx, tac_ntop(tac_srv[server_idx].addr->ai_addr),strlen(tac_srv[server_idx].key));
|
||||
}
|
||||
|
||||
output_debug("TACACS+ control flag: 0x%x\n", tacacs_ctrl);
|
||||
|
||||
if (tacacs_ctrl & AUTHORIZATION_FLAG_TACACS) {
|
||||
output_debug("TACACS+ per-command authorization enabled.\n");
|
||||
}
|
||||
|
||||
if (tacacs_ctrl & AUTHORIZATION_FLAG_LOCAL) {
|
||||
output_debug("Local per-command authorization enabled.\n");
|
||||
}
|
||||
|
||||
if (tacacs_ctrl & PAM_TAC_DEBUG) {
|
||||
output_debug("TACACS+ debug enabled.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Load tacacs config.
|
||||
*/
|
||||
void check_and_load_changed_tacacs_config()
|
||||
{
|
||||
struct stat attr;
|
||||
// get config file stat, check if file changed
|
||||
stat(tacacs_config_file, &attr);
|
||||
char date[CONFIG_FILE_TIME_STAMP_LEN];
|
||||
strftime(date, sizeof(date), CONFIG_FILE_TIME_STAMP_FORMAT, localtime(&(attr.st_mtime)));
|
||||
if (difftime(attr.st_mtime, config_file_attr.st_mtime) == 0) {
|
||||
output_debug("tacacs config file not change: last modified time: %s.\n", date);
|
||||
return;
|
||||
}
|
||||
|
||||
output_debug("tacacs config file changed: last modified time: %s.\n", date);
|
||||
|
||||
// config file changed, update file stat and reload config.
|
||||
config_file_attr = attr;
|
||||
|
||||
// load config file
|
||||
load_tacacs_config();
|
||||
}
|
||||
|
||||
/*
|
||||
* Tacacs plugin initialization.
|
||||
*/
|
||||
void plugin_init()
|
||||
{
|
||||
// get config file stat, will use this to check config file changed
|
||||
stat(tacacs_config_file, &config_file_attr);
|
||||
|
||||
// load config file: tacacs_config_file
|
||||
load_tacacs_config();
|
||||
|
||||
output_debug("tacacs plugin initialized.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Tacacs plugin release.
|
||||
*/
|
||||
void plugin_uninit()
|
||||
{
|
||||
output_debug("tacacs plugin un-initialize.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if current user is local user.
|
||||
*/
|
||||
int is_local_user(char *user)
|
||||
{
|
||||
if (user == unknown_username) {
|
||||
// for unknown user name, when tacacs enabled, always authorization with tacacs.
|
||||
return IS_REMOTE_USER;
|
||||
}
|
||||
|
||||
struct passwd pwd;
|
||||
struct passwd *pwdresult;
|
||||
char *buf;
|
||||
size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
|
||||
if (bufsize == -1) {
|
||||
bufsize = DEFAULT_SC_GETPW_R_SIZE_MAX;
|
||||
}
|
||||
|
||||
buf = malloc(bufsize);
|
||||
if (buf == NULL) {
|
||||
output_error("failed to allocate getpwnam_r buffer.\n");
|
||||
return ERROR_CHECK_LOCAL_USER;
|
||||
}
|
||||
|
||||
int s = getpwnam_r(user, &pwd, buf, bufsize, &pwdresult);
|
||||
int result = IS_LOCAL_USER;
|
||||
if (pwdresult == NULL) {
|
||||
if (s == 0)
|
||||
output_error("get user information user failed, user: %s not found\n", user);
|
||||
else {
|
||||
output_error("get user information failed, user: %s, errorno: %d\n", user, s);
|
||||
}
|
||||
|
||||
result = ERROR_CHECK_LOCAL_USER;
|
||||
}
|
||||
else if (strncmp(pwd.pw_gecos, REMOTE_USER_GECOS_PREFIX, strlen(REMOTE_USER_GECOS_PREFIX)) == 0) {
|
||||
output_debug("user: %s, UID: %d, GECOS: %s is remote user.\n", user, pwd.pw_uid, pwd.pw_gecos);
|
||||
result = IS_REMOTE_USER;
|
||||
}
|
||||
else {
|
||||
output_debug("user: %s, UID: %d, GECOS: %s is local user.\n", user, pwd.pw_uid, pwd.pw_gecos);
|
||||
result = IS_LOCAL_USER;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get user name.
|
||||
*/
|
||||
char* get_user_name(char *user)
|
||||
{
|
||||
if (user != NULL && strlen(user) != 0) {
|
||||
return user;
|
||||
}
|
||||
|
||||
// uid is the real user id: https://man7.org/linux/man-pages/man2/geteuid.2.html
|
||||
output_debug("Login user name is empty, try get user name by euid.\n");
|
||||
uid_t uid = getuid();
|
||||
struct passwd* userwd = getpwuid(uid);
|
||||
if (userwd != NULL && userwd->pw_name != NULL) {
|
||||
return userwd->pw_name;
|
||||
}
|
||||
|
||||
// euid is the effective user name, may not match real user id: https://man7.org/linux/man-pages/man2/geteuid.2.html
|
||||
output_debug("Login user name is empty, try get user name by euid.\n");
|
||||
uid_t euid = geteuid();
|
||||
struct passwd* euserwd = getpwuid(euid);
|
||||
if (euserwd != NULL && euserwd->pw_name != NULL) {
|
||||
return euserwd->pw_name;
|
||||
}
|
||||
|
||||
// if can't find user name by both euid or ruid, return UNKNOWN.
|
||||
return unknown_username;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tacacs authorization.
|
||||
*/
|
||||
int on_shell_execve (char *user, int shell_level, char *cmd, char **argv)
|
||||
{
|
||||
char* user_namd = get_user_name(user);
|
||||
output_debug("Authorization parameters:\n");
|
||||
output_debug(" Shell level: %d\n", shell_level);
|
||||
output_debug(" Current user: %s\n", user_namd);
|
||||
output_debug(" Command full path: %s\n", cmd);
|
||||
output_debug(" Parameters:\n");
|
||||
char **parameter_array_pointer = argv;
|
||||
int argc = 0;
|
||||
while (*parameter_array_pointer != NULL) {
|
||||
// output parameter
|
||||
output_debug(" %s\n", *parameter_array_pointer);
|
||||
|
||||
// move to next parameter
|
||||
parameter_array_pointer++;
|
||||
argc++;
|
||||
}
|
||||
|
||||
if (shell_level > 2) {
|
||||
// when shell_level > 1, it's a recursive command in shell script.
|
||||
output_debug("Recursive command %s ignored.\n", cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// reload config file when tacacs config changed
|
||||
check_and_load_changed_tacacs_config();
|
||||
|
||||
int check_local_user_result = is_local_user(user_namd);
|
||||
if (check_local_user_result != IS_REMOTE_USER) {
|
||||
/*
|
||||
Return 0 to check with linux permission control in following 2 scenario:
|
||||
1: ERROR_CHECK_LOCAL_USER: check if user is local user failed because can't get user information.
|
||||
In this case, as failback, check with linux permission control.
|
||||
2: IS_LOCAL_USER: user login as local user.
|
||||
In this case, tacacs authorization disabled for local user.
|
||||
*/
|
||||
output_debug("ignore TACACS+ authorization for current user, check with local permission.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tacacs_ctrl & AUTHORIZATION_FLAG_TACACS) {
|
||||
output_debug("start TACACS+ authorization for command %s with given arguments\n", cmd);
|
||||
int ret = authorization_with_host_and_tty(user_namd, cmd, argv, argc);
|
||||
switch (ret) {
|
||||
case 0:
|
||||
break;
|
||||
case -2:
|
||||
// -2 means no servers, so not authorized
|
||||
fprintf(stdout, "%s not authorized by TACACS+ with given arguments, not executing\n", cmd);
|
||||
break;
|
||||
default:
|
||||
fprintf(stdout, "%s authorize failed by TACACS+ with given arguments, not executing\n", cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((tacacs_ctrl & AUTHORIZATION_FLAG_LOCAL) == 0) {
|
||||
// when local authorization disabled, tacacs authorization failed will block user from run current command
|
||||
output_debug("local authorization disabled, TACACS+ authorization result: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// return 0, so bash will continue run user command and will check user permission with linux permission check.
|
||||
output_debug("start local authorization for command %s with given arguments\n", cmd);
|
||||
return 0;
|
||||
}
|
71
src/tacacs/bash_tacplus/configure.ac
Normal file
71
src/tacacs/bash_tacplus/configure.ac
Normal file
@ -0,0 +1,71 @@
|
||||
dnl
|
||||
dnl File: configure.in
|
||||
dnl Revision: $Id: configure.ac,v 1.0 2021/08/24 12:04:29 liuh@microsoft.com Exp $
|
||||
dnl Created: 2021/08/24
|
||||
dnl Author: Liu Hua <liuh@microsoft.com>
|
||||
dnl
|
||||
dnl Process this file with autoconf to produce a configure script
|
||||
dnl You need autoconf 2.59 or better!
|
||||
dnl
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
AC_PREREQ(2.59)
|
||||
AC_COPYRIGHT([
|
||||
See the included file: COPYING for copyright information.
|
||||
])
|
||||
AC_INIT(bash_tacplus, 1.0.0, [liuh@microsoft.com])
|
||||
|
||||
AC_CONFIG_AUX_DIR(config)
|
||||
AM_INIT_AUTOMAKE([foreign])
|
||||
AC_CONFIG_SRCDIR([bash_tacplus.c])
|
||||
AC_CONFIG_HEADER([config.h])
|
||||
AC_CONFIG_MACRO_DIR([config])
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Checks for programs.
|
||||
AC_PROG_CC
|
||||
AM_PROG_CC_C_O
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_MAKE_SET
|
||||
AC_ENABLE_SHARED
|
||||
AC_DISABLE_STATIC
|
||||
AM_PROG_LIBTOOL
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Checks for libraries.
|
||||
AC_CHECK_LIB(tac, tac_connect)
|
||||
AC_CHECK_LIB(tacsupport, parse_config_file)
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Checks for header files.
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/socket.h sys/time.h ])
|
||||
AC_CHECK_HEADER([libtac/libtac.h], [], [AC_MSG_ERROR([TAC libraries missing. ])] )
|
||||
AC_CHECK_HEADER([libtac/support.h], [], [AC_MSG_ERROR([TAC support libraries missing. ])] )
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
AC_TYPE_SIZE_T
|
||||
AC_HEADER_TIME
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Checks for library functions.
|
||||
AC_FUNC_REALLOC
|
||||
AC_FUNC_SELECT_ARGTYPES
|
||||
AC_TYPE_SIGNAL
|
||||
AC_CHECK_FUNCS([bzero gethostbyname gettimeofday inet_ntoa select socket logwtmp getrandom])
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Switch for plugin module dir
|
||||
AC_ARG_ENABLE([plugindir], [AS_HELP_STRING([--enable-plugindir],
|
||||
[Location to install the pam module ($libdir/security)])],
|
||||
[plugindir=$enableval], [plugindir=$libdir/security])
|
||||
AC_SUBST(plugindir)
|
||||
|
||||
dnl --------------------------------------------------------------------
|
||||
dnl Generate made files
|
||||
AC_CONFIG_FILES([Makefile
|
||||
unittest/Makefile])
|
||||
AC_OUTPUT
|
14
src/tacacs/bash_tacplus/debian/bash-tacplus.postinst
Normal file
14
src/tacacs/bash_tacplus/debian/bash-tacplus.postinst
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# postinst script for bash-tacplus
|
||||
|
||||
# find installed plugin
|
||||
bash_tacplus_plugin_path=$(find /usr/lib/ -type f -name "bash_tacplus.so")
|
||||
|
||||
# remove old config from bash plugin config file
|
||||
config_file_path="/etc/bash_plugins.conf"
|
||||
if [ -e $config_file_path ]; then
|
||||
sed -i '/plugin=.*bash_tacplus\.so/d' $config_file_path
|
||||
fi
|
||||
|
||||
# add new plugin path to plugin config file
|
||||
echo "plugin="$bash_tacplus_plugin_path >> $config_file_path
|
6
src/tacacs/bash_tacplus/debian/changelog
Normal file
6
src/tacacs/bash_tacplus/debian/changelog
Normal file
@ -0,0 +1,6 @@
|
||||
bash-tacplus (1.0.0) unstable; urgency=low
|
||||
|
||||
* First version of bash_tacplus debian package.
|
||||
|
||||
-- Liu Hua <liuh@microsoft.com> Thu, 9 Sep 2021 16:00:00 +0000
|
||||
|
1
src/tacacs/bash_tacplus/debian/compat
Normal file
1
src/tacacs/bash_tacplus/debian/compat
Normal file
@ -0,0 +1 @@
|
||||
10
|
11
src/tacacs/bash_tacplus/debian/control
Normal file
11
src/tacacs/bash_tacplus/debian/control
Normal file
@ -0,0 +1,11 @@
|
||||
Source: bash-tacplus
|
||||
Section: admin
|
||||
Priority: extra
|
||||
Maintainer: Liu Hua <liuh@microsoft.com>
|
||||
Build-Depends: autoconf-archive
|
||||
Description: Bash TACACS+ plugin.
|
||||
|
||||
Package: bash-tacplus
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libtac2
|
||||
Description: Bash TACACS+ plugin for per-command TACACS+ authorization.
|
27
src/tacacs/bash_tacplus/debian/rules
Normal file
27
src/tacacs/bash_tacplus/debian/rules
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/make -f
|
||||
# See debhelper(7) (uncomment to enable)
|
||||
# output every command that modifies files on the build system.
|
||||
#export DH_VERBOSE = 1
|
||||
|
||||
|
||||
# see FEATURE AREAS in dpkg-buildflags(1)
|
||||
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
|
||||
# see ENVIRONMENT in dpkg-buildflags(1)
|
||||
# package maintainers to append CFLAGS
|
||||
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
# package maintainers to append LDFLAGS
|
||||
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- --enable-manuals
|
||||
|
||||
override_dh_shlibdeps:
|
||||
dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info
|
||||
|
||||
override_dh_auto_test:
|
1
src/tacacs/bash_tacplus/debian/source/format
Normal file
1
src/tacacs/bash_tacplus/debian/source/format
Normal file
@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
14
src/tacacs/bash_tacplus/unittest/Makefile.am
Normal file
14
src/tacacs/bash_tacplus/unittest/Makefile.am
Normal file
@ -0,0 +1,14 @@
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
|
||||
noinst_PROGRAMS = plugin_test
|
||||
TESTS = plugin_test
|
||||
|
||||
# disable some warning because UT need test functions not in header file.
|
||||
CFLAGS_TEST = -Wno-parentheses -Wno-format-security -Wno-implicit-function-declaration -Wno-int-to-pointer-cast
|
||||
IFLAGS_TEST = -I.. -I../include -I../lib
|
||||
DBGFLAGS = -DDEBUG -DBASH_PLUGIN_UT
|
||||
|
||||
plugin_test_SOURCES = plugin_test.c mock_helper.c ../bash_tacplus.c
|
||||
|
||||
plugin_test_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_TEST) $(IFLAGS_TEST)
|
||||
plugin_test_LDADD = -lc -lcunit
|
209
src/tacacs/bash_tacplus/unittest/mock_helper.c
Normal file
209
src/tacacs/bash_tacplus/unittest/mock_helper.c
Normal file
@ -0,0 +1,209 @@
|
||||
/* mock_helper.c -- mock helper for bash plugin UT. */
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <CUnit/Basic.h>
|
||||
|
||||
/* Tacacs+ lib */
|
||||
#include <libtac/libtac.h>
|
||||
|
||||
#include "mock_helper.h"
|
||||
|
||||
// define BASH_PLUGIN_UT_DEBUG to output UT debug message.
|
||||
#if defined (BASH_PLUGIN_UT_DEBUG)
|
||||
#define debug_printf printf
|
||||
#define debug_vprintf vprintf
|
||||
#else
|
||||
#define debug_printf
|
||||
#define debug_vprintf
|
||||
#endif
|
||||
|
||||
/* Mock syslog buffer */
|
||||
char mock_syslog_message_buffer[1024];
|
||||
|
||||
/* define test scenarios for mock functions return different value by scenario. */
|
||||
int test_scenario;
|
||||
|
||||
/* Mock tac_netop method result buffer. */
|
||||
char tac_natop_result_buffer[128];
|
||||
|
||||
/* Mock tacplus_server_t. */
|
||||
typedef struct {
|
||||
struct addrinfo *addr;
|
||||
char key[256];
|
||||
} tacplus_server_t;
|
||||
|
||||
/* Mock VRF name. */
|
||||
char *__vrfname = "MOCK VRF name";
|
||||
|
||||
/* Mock tac timeout setting. */
|
||||
int tac_timeout = 10;
|
||||
|
||||
/* Mock TACACS servers. */
|
||||
int tac_srv_no = 3;
|
||||
tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS];
|
||||
struct addrinfo tac_srv_addr[TAC_PLUS_MAXSERVERS];
|
||||
struct sockaddr tac_sock_addr[TAC_PLUS_MAXSERVERS];
|
||||
|
||||
/* Mock tac_source_addr. */
|
||||
struct addrinfo tac_source_addr;
|
||||
|
||||
/* define memory allocate counter. */
|
||||
int memory_allocate_count;
|
||||
|
||||
/* Initialize tacacs servers for test*/
|
||||
void initialize_tacacs_servers()
|
||||
{
|
||||
for (int idx=0; idx < tac_srv_no; idx++)
|
||||
{
|
||||
// generate address with index
|
||||
struct addrinfo hints, *servers;
|
||||
char buffer[128];
|
||||
snprintf(buffer, sizeof(buffer), "1.2.3.%d", idx);
|
||||
getaddrinfo(buffer, "49", &hints, &servers);
|
||||
tac_srv[idx].addr = &(tac_srv_addr[idx]);
|
||||
memcpy(tac_srv[idx].addr, servers, sizeof(struct addrinfo));
|
||||
|
||||
tac_srv[idx].addr->ai_addr = &(tac_sock_addr[idx]);
|
||||
memcpy(tac_srv[idx].addr->ai_addr, servers->ai_addr, sizeof(struct sockaddr));
|
||||
|
||||
snprintf(tac_srv[idx].key, sizeof(tac_srv[idx].key), "key%d", idx);
|
||||
freeaddrinfo(servers);
|
||||
|
||||
debug_printf("MOCK: initialize_tacacs_servers with index: %d, address: %p\n", idx, tac_srv[idx].addr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set test scenario for test*/
|
||||
void set_test_scenario(int scenario)
|
||||
{
|
||||
test_scenario = scenario;
|
||||
}
|
||||
|
||||
/* Get test scenario for test*/
|
||||
int get_test_scenario()
|
||||
{
|
||||
return test_scenario;
|
||||
}
|
||||
|
||||
/* Set memory allocate count for test*/
|
||||
void set_memory_allocate_count(int count)
|
||||
{
|
||||
memory_allocate_count = count;
|
||||
}
|
||||
|
||||
/* Get memory allocate count for test*/
|
||||
int get_memory_allocate_count()
|
||||
{
|
||||
return memory_allocate_count;
|
||||
}
|
||||
|
||||
/* Mock xcalloc method */
|
||||
void *xcalloc(size_t count, size_t size)
|
||||
{
|
||||
memory_allocate_count++;
|
||||
debug_printf("MOCK: xcalloc memory count: %d\n", memory_allocate_count);
|
||||
return malloc(count*size);
|
||||
}
|
||||
|
||||
/* Mock tac_free_attrib method */
|
||||
void tac_add_attrib(struct tac_attrib **attr, char *attrname, char *attrvalue)
|
||||
{
|
||||
debug_printf("MOCK: tac_add_attrib add attribute: %s, value: %s\n", attrname, attrvalue);
|
||||
}
|
||||
|
||||
/* Mock tac_free_attrib method */
|
||||
void tac_free_attrib(struct tac_attrib **attr)
|
||||
{
|
||||
memory_allocate_count--;
|
||||
debug_printf("MOCK: tac_free_attrib memory count: %d\n", memory_allocate_count);
|
||||
|
||||
// the mock code here only free first allocated memory, because the mock tac_add_attrib implementation not allocate new memory.
|
||||
free(*attr);
|
||||
}
|
||||
|
||||
/* Mock tac_author_send method */
|
||||
int tac_author_send(int tac_fd, const char *user, char *tty, char *host,struct tac_attrib *attr)
|
||||
{
|
||||
debug_printf("MOCK: tac_author_send with fd: %d, user:%s, tty:%s, host:%s, attr:%p\n", tac_fd, user, tty, host, attr);
|
||||
if(TEST_SCEANRIO_CONNECTION_SEND_FAILED_RESULT == test_scenario)
|
||||
{
|
||||
// send auth message failed
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mock tac_author_read method */
|
||||
int tac_author_read(int tac_fd, struct areply *reply)
|
||||
{
|
||||
// TODO: fill reply message here for test
|
||||
debug_printf("MOCK: tac_author_read with fd: %d\n", tac_fd);
|
||||
if (TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_READ_FAILED == test_scenario)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT == test_scenario)
|
||||
{
|
||||
reply->status = AUTHOR_STATUS_FAIL;
|
||||
}
|
||||
else
|
||||
{
|
||||
reply->status = AUTHOR_STATUS_PASS_REPL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mock tac_connect_single method */
|
||||
int tac_connect_single(const struct addrinfo *address, const char *key, struct addrinfo *source_address, int timeout, char *vrfname)
|
||||
{
|
||||
debug_printf("MOCK: tac_connect_single with address: %p\n", address);
|
||||
|
||||
switch (test_scenario)
|
||||
{
|
||||
case TEST_SCEANRIO_CONNECTION_ALL_FAILED:
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mock tac_ntop method */
|
||||
char *tac_ntop(const struct sockaddr *address)
|
||||
{
|
||||
for (int idx=0; idx < tac_srv_no; idx++)
|
||||
{
|
||||
if (address == &(tac_sock_addr[idx]))
|
||||
{
|
||||
snprintf(tac_natop_result_buffer, sizeof(tac_natop_result_buffer), "TestAddress%d", idx);
|
||||
return tac_natop_result_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
return "UnknownTestAddress";
|
||||
}
|
||||
|
||||
/* Mock parse_config_file method */
|
||||
int parse_config_file(const char *file)
|
||||
{
|
||||
debug_printf("MOCK: parse_config_file: %s\n", file);
|
||||
}
|
||||
|
||||
/* Mock syslog method */
|
||||
void mock_syslog(int priority, const char *format, ...)
|
||||
{
|
||||
// set mock message data to buffer for UT.
|
||||
memset(mock_syslog_message_buffer, 0, sizeof(mock_syslog_message_buffer));
|
||||
|
||||
va_list args;
|
||||
va_start (args, format);
|
||||
// save message to buffer to UT check later
|
||||
vsnprintf(mock_syslog_message_buffer, sizeof(mock_syslog_message_buffer), format, args);
|
||||
va_end (args);
|
||||
|
||||
debug_printf("MOCK: syslog: %s\n", mock_syslog_message_buffer);
|
||||
}
|
46
src/tacacs/bash_tacplus/unittest/mock_helper.h
Normal file
46
src/tacacs/bash_tacplus/unittest/mock_helper.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* plugin.h - functions from plugin.c. */
|
||||
|
||||
/* Copyright (C) 1993-2015 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Bash, the Bourne Again SHell.
|
||||
|
||||
Bash is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Bash is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Bash. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if !defined (_MOCK_HELPER_H_)
|
||||
#define _MOCK_HELPER_H_
|
||||
|
||||
/* Mock syslog buffer */
|
||||
extern char mock_syslog_message_buffer[1024];
|
||||
|
||||
#define TEST_SCEANRIO_CONNECTION_ALL_FAILED 1
|
||||
#define TEST_SCEANRIO_CONNECTION_SEND_FAILED_RESULT 2
|
||||
#define TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_READ_FAILED 3
|
||||
#define TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT 4
|
||||
#define TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT 5
|
||||
|
||||
/* Set test scenario for test*/
|
||||
void set_test_scenario(int scenario);
|
||||
|
||||
/* Get test scenario for test*/
|
||||
int get_test_scenario();
|
||||
|
||||
/* Set memory allocate count for test*/
|
||||
void set_memory_allocate_count(int count);
|
||||
|
||||
/* Get memory allocate count for test*/
|
||||
int get_memory_allocate_count();
|
||||
|
||||
|
||||
#endif /* _MOCK_HELPER_H_ */
|
219
src/tacacs/bash_tacplus/unittest/plugin_test.c
Normal file
219
src/tacacs/bash_tacplus/unittest/plugin_test.c
Normal file
@ -0,0 +1,219 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <CUnit/Basic.h>
|
||||
#include "mock_helper.h"
|
||||
#include <libtac/support.h>
|
||||
|
||||
/* tacacs debug flag */
|
||||
extern int tacacs_ctrl;
|
||||
|
||||
int clean_up() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int start_up() {
|
||||
initialize_tacacs_servers();
|
||||
tacacs_ctrl = PAM_TAC_DEBUG;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Test tacacs_authorization all tacacs server connect failed case */
|
||||
void testcase_tacacs_authorization_all_failed() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
|
||||
|
||||
// test connection failed case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_ALL_FAILED);
|
||||
int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2);
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "Failed to connect to TACACS server(s)\n");
|
||||
|
||||
// check return value, -2 for all server not reachable
|
||||
CU_ASSERT_EQUAL(result, -2);
|
||||
}
|
||||
|
||||
/* Test tacacs_authorization get failed result case */
|
||||
void testcase_tacacs_authorization_faled() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
|
||||
// test connection failed case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_FAILED_RESULT);
|
||||
int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2);
|
||||
|
||||
// send auth message failed.
|
||||
CU_ASSERT_EQUAL(result, -1);
|
||||
}
|
||||
|
||||
/* Test tacacs_authorization read failed case */
|
||||
void testcase_tacacs_authorization_read_failed() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
|
||||
// test connection failed case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_READ_FAILED);
|
||||
int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2);
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command not authorized from TestAddress2\n");
|
||||
|
||||
// read auth message failed.
|
||||
CU_ASSERT_EQUAL(result, -1);
|
||||
}
|
||||
|
||||
/* Test tacacs_authorization get denined case */
|
||||
void testcase_tacacs_authorization_denined() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
|
||||
// test connection denined case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT);
|
||||
int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2);
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command not authorized from TestAddress2\n");
|
||||
|
||||
// send auth message denined.
|
||||
CU_ASSERT_EQUAL(result, 1);
|
||||
}
|
||||
|
||||
/* Test tacacs_authorization get success case */
|
||||
void testcase_tacacs_authorization_success() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
|
||||
// test connection success case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT);
|
||||
int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2);
|
||||
|
||||
// wuthorization success
|
||||
CU_ASSERT_EQUAL(result, 0);
|
||||
}
|
||||
|
||||
/* Test authorization_with_host_and_tty get success case */
|
||||
void testcase_authorization_with_host_and_tty_success() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
|
||||
// test connection success case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT);
|
||||
int result = authorization_with_host_and_tty("test_user","test_command",testargv,2);
|
||||
|
||||
// wuthorization success
|
||||
CU_ASSERT_EQUAL(result, 0);
|
||||
}
|
||||
|
||||
/* Test check_and_load_changed_tacacs_config */
|
||||
void testcase_check_and_load_changed_tacacs_config() {
|
||||
|
||||
// test connection failed case
|
||||
check_and_load_changed_tacacs_config();
|
||||
|
||||
// check server config updated.
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "Server 2, address:TestAddress2, key:key2\n");
|
||||
|
||||
// check and load file again.
|
||||
check_and_load_changed_tacacs_config();
|
||||
|
||||
// check server config not update.
|
||||
char* configNotChangeLog = "tacacs config file not change: last modified time";
|
||||
CU_ASSERT_TRUE(strncmp(mock_syslog_message_buffer, configNotChangeLog, strlen(configNotChangeLog)) == 0);
|
||||
}
|
||||
|
||||
/* Test on_shell_execve authorization successed */
|
||||
void testcase_on_shell_execve_success() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
testargv[2] = 0;
|
||||
|
||||
// test connection failed case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT);
|
||||
on_shell_execve("test_user", 1, "test_command", testargv);
|
||||
|
||||
// check authorized success.
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command authorize successed by TACACS+ with given arguments\n");
|
||||
}
|
||||
|
||||
/* Test on_shell_execve authorization denined */
|
||||
void testcase_on_shell_execve_denined() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
testargv[2] = 0;
|
||||
|
||||
// test connection failed case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT);
|
||||
on_shell_execve("test_user", 1, "test_command", testargv);
|
||||
|
||||
// check authorized failed.
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command authorize failed by TACACS+ with given arguments, not executing\n");
|
||||
}
|
||||
|
||||
/* Test on_shell_execve authorization failed */
|
||||
void testcase_on_shell_execve_failed() {
|
||||
char *testargv[2];
|
||||
testargv[0] = "arg1";
|
||||
testargv[1] = "arg2";
|
||||
testargv[2] = 0;
|
||||
|
||||
// test connection failed case
|
||||
set_test_scenario(TEST_SCEANRIO_CONNECTION_ALL_FAILED);
|
||||
on_shell_execve("test_user", 1, "test_command", testargv);
|
||||
|
||||
// check not authorized.
|
||||
CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command not authorized by TACACS+ with given arguments, not executing\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (CUE_SUCCESS != CU_initialize_registry()) {
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
CU_pSuite ste = CU_add_suite("plugin_test", start_up, clean_up);
|
||||
if (NULL == ste) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
if (CU_get_error() != CUE_SUCCESS) {
|
||||
fprintf(stderr, "Error creating suite: (%d)%s\n", CU_get_error(), CU_get_error_msg());
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
if (!CU_add_test(ste, "Test testcase_tacacs_authorization_all_failed()...\n", testcase_tacacs_authorization_all_failed)
|
||||
|| !CU_add_test(ste, "Test testcase_tacacs_authorization_faled()...\n", testcase_tacacs_authorization_faled)
|
||||
|| !CU_add_test(ste, "Test testcase_tacacs_authorization_read_failed()...\n", testcase_tacacs_authorization_read_failed)
|
||||
|| !CU_add_test(ste, "Test testcase_tacacs_authorization_denined()...\n", testcase_tacacs_authorization_denined)
|
||||
|| !CU_add_test(ste, "Test testcase_tacacs_authorization_success()...\n", testcase_tacacs_authorization_success)
|
||||
|| !CU_add_test(ste, "Test testcase_authorization_with_host_and_tty_success()...\n", testcase_authorization_with_host_and_tty_success)
|
||||
|| !CU_add_test(ste, "Test testcase_check_and_load_changed_tacacs_config()...\n", testcase_check_and_load_changed_tacacs_config)
|
||||
|| !CU_add_test(ste, "Test testcase_on_shell_execve_success()...\n", testcase_on_shell_execve_success)
|
||||
|| !CU_add_test(ste, "Test testcase_on_shell_execve_denined()...\n", testcase_on_shell_execve_denined)
|
||||
|| !CU_add_test(ste, "Test testcase_on_shell_execve_failed()...\n", testcase_on_shell_execve_failed)) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
if (CU_get_error() != CUE_SUCCESS) {
|
||||
fprintf(stderr, "Error adding test: (%d)%s\n", CU_get_error(), CU_get_error_msg());
|
||||
}
|
||||
|
||||
// run all test
|
||||
CU_basic_set_mode(CU_BRM_VERBOSE);
|
||||
CU_ErrorCode run_errors = CU_basic_run_suite(ste);
|
||||
if (run_errors != CUE_SUCCESS) {
|
||||
fprintf(stderr, "Error running tests: (%d)%s\n", run_errors, CU_get_error_msg());
|
||||
}
|
||||
|
||||
CU_basic_show_failures(CU_get_failure_list());
|
||||
|
||||
// use failed UT count as return value
|
||||
return CU_get_number_of_failure_records();
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
From d820001f60e0a9f5e5df83b1edb229be5212e0b5 Mon Sep 17 00:00:00 2001
|
||||
From 81a8b6135cb0c97a291195b04375d0ca33943621 Mon Sep 17 00:00:00 2001
|
||||
From: liuh-80 <58683130+liuh-80@users.noreply.github.com>
|
||||
Date: Tue, 12 Oct 2021 10:09:10 +0800
|
||||
Subject: [PATCH 3/4] Extract tacacs support functions into library.
|
||||
Subject: [PATCH] Extract tacacs support functions into library.
|
||||
|
||||
---
|
||||
Makefile.am | 16 ++-
|
||||
configure.ac | 3 +-
|
||||
libtacsupport.pc.in | 11 ++
|
||||
pam_tacplus.c | 3 -
|
||||
pam_tacplus.h | 6 --
|
||||
support.c | 255 ++++++++++++++++++++++++++------------------
|
||||
pam_tacplus.h | 6 -
|
||||
support.c | 288 ++++++++++++++++++++++++++++----------------
|
||||
support.h | 14 +++
|
||||
7 files changed, 194 insertions(+), 114 deletions(-)
|
||||
7 files changed, 222 insertions(+), 119 deletions(-)
|
||||
create mode 100644 libtacsupport.pc.in
|
||||
|
||||
diff --git a/Makefile.am b/Makefile.am
|
||||
index c90c582..2ac9ea0 100644
|
||||
index c90c582..b22c78b 100644
|
||||
--- a/Makefile.am
|
||||
+++ b/Makefile.am
|
||||
@@ -20,7 +20,7 @@ libtac/include/tacplus.h \
|
||||
@ -61,7 +61,7 @@ index c90c582..2ac9ea0 100644
|
||||
+pkgconfig_DATA = libtac.pc libtacsupport.pc
|
||||
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index f67e2ba..0f917a8 100644
|
||||
index f67e2ba..e2e3fa9 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -95,6 +95,7 @@ AM_CONDITIONAL(DOC, test "x$enable_doc" != "xno")
|
||||
@ -75,7 +75,7 @@ index f67e2ba..0f917a8 100644
|
||||
AC_OUTPUT
|
||||
diff --git a/libtacsupport.pc.in b/libtacsupport.pc.in
|
||||
new file mode 100644
|
||||
index 0000000..1f12fe0
|
||||
index 0000000..9698094
|
||||
--- /dev/null
|
||||
+++ b/libtacsupport.pc.in
|
||||
@@ -0,0 +1,11 @@
|
||||
@ -122,21 +122,47 @@ index bc71b54..e7b30f7 100644
|
||||
#define PAM_TAC_VMAJ 1
|
||||
#define PAM_TAC_VMIN 3
|
||||
diff --git a/support.c b/support.c
|
||||
index e22fa31..5b6e1fa 100644
|
||||
index 2f77bc8..5f43b1a 100644
|
||||
--- a/support.c
|
||||
+++ b/support.c
|
||||
@@ -29,6 +29,7 @@
|
||||
@@ -29,7 +29,11 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
+#include <ctype.h> /* isspace() */
|
||||
|
||||
+/* tacacs config file splitter */
|
||||
+#define CONFIG_FILE_SPLITTER " ,\t\n\r\f"
|
||||
+
|
||||
/* tacacs server information */
|
||||
tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS];
|
||||
@@ -236,9 +237,160 @@ void set_source_ip(const char *tac_source_ip) {
|
||||
struct addrinfo tac_srv_addr[TAC_PLUS_MAXSERVERS];
|
||||
@@ -234,11 +238,182 @@ void set_source_ip(const char *tac_source_ip) {
|
||||
freeaddrinfo(source_address);
|
||||
_pam_log(LOG_DEBUG, "source ip is set");
|
||||
}
|
||||
}
|
||||
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Reset configuration variables.
|
||||
+ * This method need to be called before parse config, otherwise the server list will grow with each call.
|
||||
+ */
|
||||
+int reset_config_variables () {
|
||||
+ memset(tac_srv, 0, sizeof(tacplus_server_t) * TAC_PLUS_MAXSERVERS);
|
||||
+ tac_srv_no = 0;
|
||||
+
|
||||
+ tac_service[0] = 0;
|
||||
+ tac_protocol[0] = 0;
|
||||
+ tac_prompt[0] = 0;
|
||||
+ tac_login[0] = 0;
|
||||
+ tac_source_ip[0] = 0;
|
||||
+
|
||||
+ if (tac_source_addr != NULL) {
|
||||
+ /* reset source address */
|
||||
+ tac_source_addr = NULL;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Parse one arguments.
|
||||
+ * Use this method for both:
|
||||
@ -254,7 +280,6 @@ index e22fa31..5b6e1fa 100644
|
||||
+ return ctrl;
|
||||
+} /* _pam_parse_arg */
|
||||
+
|
||||
+
|
||||
+/*
|
||||
+ * Parse config file.
|
||||
+ */
|
||||
@ -263,30 +288,31 @@ index e22fa31..5b6e1fa 100644
|
||||
+ char line_buffer[256];
|
||||
+ int ctrl = 0;
|
||||
+
|
||||
+ /* otherwise the list will grow with each call */
|
||||
+ reset_config_variables();
|
||||
+
|
||||
+ config_file = fopen(file, "r");
|
||||
+ if(config_file == NULL) {
|
||||
+ _pam_log(LOG_ERR, "Failed to open config file %s: %m", file);
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ if (tac_source_addr != NULL) {
|
||||
+ /* reset source address */
|
||||
+ tac_source_addr = NULL;
|
||||
+ }
|
||||
+
|
||||
+ char current_secret[256];
|
||||
+ memset(current_secret, 0, sizeof(current_secret));
|
||||
+ while (fgets(line_buffer, sizeof line_buffer, config_file)) {
|
||||
+ if(*line_buffer == '#' || isspace(*line_buffer))
|
||||
+ continue; /* skip comments and blank line. */
|
||||
+ strtok(line_buffer, " \t\n\r\f");
|
||||
+ ctrl |= _pam_parse_arg(line_buffer, current_secret, sizeof(current_secret));
|
||||
+ char* config_item = strtok(line_buffer, CONFIG_FILE_SPLITTER);
|
||||
+ while (config_item != NULL) {
|
||||
+ ctrl |= _pam_parse_arg(config_item, current_secret, sizeof(current_secret));
|
||||
+ config_item = strtok(NULL, CONFIG_FILE_SPLITTER);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ fclose(config_file);
|
||||
+ return ctrl;
|
||||
+}
|
||||
+
|
||||
}
|
||||
|
||||
int _pam_parse (int argc, const char **argv) {
|
||||
int ctrl = 0;
|
||||
- const char *current_secret = NULL;
|
||||
@ -295,7 +321,20 @@ index e22fa31..5b6e1fa 100644
|
||||
|
||||
/* otherwise the list will grow with each call */
|
||||
memset(tac_srv, 0, sizeof(tacplus_server_t) * TAC_PLUS_MAXSERVERS);
|
||||
@@ -256,106 +408,7 @@ int _pam_parse (int argc, const char **argv) {
|
||||
@@ -248,114 +423,15 @@ int _pam_parse (int argc, const char **argv) {
|
||||
tac_protocol[0] = 0;
|
||||
tac_prompt[0] = 0;
|
||||
tac_login[0] = 0;
|
||||
- tac_source_ip[0] = 0;
|
||||
-
|
||||
- if (tac_source_addr != NULL) {
|
||||
- /* reset source address */
|
||||
- tac_source_addr = NULL;
|
||||
+ tac_source_ip[0] = 0;
|
||||
+
|
||||
+ if (tac_source_addr != NULL) {
|
||||
+ /* reset source address */
|
||||
+ tac_source_addr = NULL;
|
||||
}
|
||||
|
||||
for (ctrl = 0; argc-- > 0; ++argv) {
|
||||
@ -404,7 +443,7 @@ index e22fa31..5b6e1fa 100644
|
||||
|
||||
if (ctrl & PAM_TAC_DEBUG) {
|
||||
diff --git a/support.h b/support.h
|
||||
index 6bcb07f..569172e 100644
|
||||
index 6bcb07f..27f66de 100644
|
||||
--- a/support.h
|
||||
+++ b/support.h
|
||||
@@ -26,6 +26,14 @@
|
||||
|
Loading…
Reference in New Issue
Block a user