Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • icipguru/goatherd
  • arw/goatherd
2 results
Show changes
Commits on Source (110)
goatherd
goatherd.test
debian/libpam-goatherd
stages:
- test
services:
- postgres:latest
variables:
POSTGRES_USER: goatherd
POSTGRES_PASSWORD: Toh6ahrah4eishaim0ei
before_script:
- ln -s /builds /go/src/gitlab.cs.fau.de
- cd /go/src/gitlab.cs.fau.de/${CI_PROJECT_PATH}
- go get -d
- export PGHOST=postgres PGUSER=$POSTGRES_USER PGPASSWORD=$POSTGRES_PASSWORD PGSSLMODE=disable
- export DB_URL="host=postgres user=$POSTGRES_USER password=$POSTGRES_PASSWORD sslmode=disable"
- apt update -qq
- apt upgrade --yes
- apt install --yes libpam0g-dev libgnutls28-dev libpam-wrapper pamtester oathtool postgresql xxd
go-1.15-bullseye:
image: golang:1.16-bullseye
script:
- make test
tags:
- shared runner
go-latest-bullseye:
image: golang:bullseye
script:
- make test
tags:
- shared runner
bullseye:
image: debian:bullseye
before_script:
- export PGHOST=postgres PGUSER=$POSTGRES_USER PGPASSWORD=$POSTGRES_PASSWORD PGSSLMODE=disable
- export DB_URL="host=postgres user=$POSTGRES_USER password=$POSTGRES_PASSWORD sslmode=disable"
- apt update -qq
- apt upgrade --yes
# The bullseye container does not have pkill installed -> procps
- apt install --yes golang build-essential ca-certificates git libpam0g-dev libgnutls28-dev libpam-wrapper pamtester oathtool postgresql xxd procps
- go get -d
script:
- make test
tags:
- shared runner
CFLAGS += -std=c11 -D_POSIX_C_SOURCE=201112 -Wall -Wconversion -fPIC
CFLAGS += -std=c11 -D_GNU_SOURCE -Wall -Wconversion -Wformat -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fPIC
PAMDIR ?= /lib/security
all: goatherd pam_goatherd.so
goatherd: goatherd.go
go build goatherd.go
go build .
test_sqlite: goatherd.go goatherd_test.go
go test
test_pg: goatherd.go goatherd_test.go test_pg.sh
test_pg: goatherd.go goatherd_test.go test_pg.sh goatherd
sh test_pg.sh
test: test_sqlite test_pg
test_pam_goatherd: goatherd pam_goatherd.so test_pam_goatherd.sh
sh test_pam_goatherd.sh
test: test_pg test_pam_goatherd
pam_goatherd.so: pam_goatherd.o
$(LINK.c) -lpam -lgnutls -Wl,-soname,$@ -shared $< -o $@
$(LINK.c) $< -o $@ -lpam -lgnutls -Wl,-soname,$@ -shared
clean:
rm -f pam_goatherd.o pam_goatherd.so goatherd
......@@ -28,3 +28,5 @@ install_daemon: goatherd
install -D -t ${DESTDIR}/usr/sbin goatherd
install: install_pam install_daemon
.PHONY: all clean test test_pg test_pam_goatherd install install_daemon install_pam
goatherd (0.9) unstable; urgency=medium
* add dwz override to debian/rules
-- Lorena Kretzschmar <qy15sije@cip.cs.fau.de> Fri, 12 May 2023 16:34:20 +0200
goatherd (0.8+i4+nmu1) unstable; urgency=medium
* Add a quorum_size parameter to the PAM module
-- Simon Schuster <schuster@cs.fau.de> Mon, 27 Jun 2022 16:30:27 +0200
goatherd (0.8+i4) unstable; urgency=medium
* Add TOTP support
* Add support for multiple secrets per user. The additional secrets use "$USERNAME/suffix" as username
* Autoresync for HOTP is now triggered immediately once enough consecutive counters were entered
* Remove goto usage from Go code
* starting goatherd without parameters now prints a help message
-- Michael Eischer <eischer@cs.fau.de> Sat, 25 Jun 2022 17:35:00 +0200
goatherd (0.7) unstable; urgency=medium
* Fix authentication issues on bullseye by fixing tls to version 1.2
* Cleanup ci-pipelines
* Minor code cleanup
* Remove vendor-files and use go-get during package buildprocess
-- Thomas Preisner <ty28wuqu@cip.cs.fau.de> Mon, 24 Jan 2022 22:15:53 +0100
goatherd (0.6) unstable; urgency=medium
* Makefile: reference external libraries after source files
-- Thomas Preisner <ty28wuqu@cip.cs.fau.de> Fri, 15 Oct 2021 15:37:12 +0200
goatherd (0.5) unstable; urgency=medium
* Fix config file parsing to use the old format again (the change was not
intentional)
-- Julian Brost <vu20wyli@cip.cs.fau.de> Thu, 08 Aug 2019 14:05:29 +0200
goatherd (0.4) unstable; urgency=medium
* Disable dh_auto_test and bump version
-- David Sauerwein <la38vyti-adm@stud.informatik.uni-erlangen.de> Tue, 30 Jul 2019 12:01:28 +0200
goatherd (0.3) unstable; urgency=medium
* subcommands instead of flags for actions
* different userlist format
* replication (changes to PAM arguments)
* dropped support for sqlite
-- Lukas Braun <no25qusu@stud.informatik.uni-erlangen.de> Tue, 29 Aug 2017 00:37:14 +0200
goatherd (0.2) unstable; urgency=medium
* packaging fixes, see git log
-- Lukas Braun <no25qusu@stud.informatik.uni-erlangen.de> Tue, 29 Aug 2017 00:02:23 +0200
goatherd (0.1) unstable; urgency=medium
* Initial Release.
-- Lukas Braun <no25qusu@stud.informatik.uni-erlangen.de> Sun, 12 Mar 2017 15:38:18 +0100
12
Source: goatherd
Priority: optional
Maintainer: CIP-Admins <problems@cip.cs.fau.de>
Uploaders: Lukas Braun <no25qusu@stud.informatik.uni-erlangen.de>
Build-Depends: debhelper (>= 9),
libpam0g-dev,
libgnutls28-dev,
golang-go,
ca-certificates
Standards-Version: 3.9.8
Section: admin
Homepage: https://gitlab.cs.fau.de/icipguru/goatherd
#Vcs-Git: https://anonscm.debian.org/collab-maint/goatherd.git
#Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/goatherd.git
Package: libpam-goatherd
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: goatherd PAM module
Package: goatherd
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: goatherd authentication server
{
"db_url": "/var/lib/goatherd/goatherd.sqlite3"
}
usr/sbin/goatherd
#!/bin/sh
set -eu
if test "$1" = "configure"; then
statedir=/var/lib/goatherd
if ! getent passwd goatherd >/dev/null; then
adduser --quiet --system --group --no-create-home --home $statedir goatherd
fi
if ! dpkg-statoverride --list $statedir >/dev/null; then
dpkg-statoverride --quiet --update --add goatherd goatherd 0700 $statedir
fi
fi
#DEBHELPER#
exit 0
#!/bin/sh
set -eu
#DEBHELPER#
if test "$1" = "purge"; then
statedir=/var/lib/goatherd
rm -f $statedir/goatherd.sqlite3
rmdir $statedir >/dev/null 2>/dev/null || true
dpkg-statoverride --remove $statedir >/dev/null 2>/dev/null || true
fi
exit 0
[Unit]
Description=HOTP authentication daemon
Wants=network.target
[Service]
Type=simple
ExecStart=/usr/sbin/goatherd serve
User=goatherd
[Install]
WantedBy=multi-user.target
lib/*/security/pam_goatherd.so
#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export PAMDIR := /lib/$(DEB_HOST_MULTIARCH)/security
export GOPATH=$(CURDIR)/debian/go_build
%:
dh $@
override_dh_install:
dh_install
install -D -t $(CURDIR)/debian/goatherd/etc/ $(CURDIR)/debian/goatherd.conf
install -Dd -m 0700 $(CURDIR)/debian/goatherd/var/lib/goatherd
override_dh_auto_test:
override_dh_dwz:
3.0 (native)
module goatherd
go 1.15
require (
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
github.com/gokyle/twofactor v1.0.2-0.20201031150611-619c08a13fc7
github.com/lib/pq v1.10.4
rsc.io/qr v0.2.0 // indirect
)
This diff is collapsed.
This diff is collapsed.
......@@ -10,22 +10,30 @@
#include <netdb.h>
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#define PAM_SM_AUTH
#include <security/pam_modules.h>
#include <security/pam_ext.h>
// arbitrary but small because currently all servers are asked sequentially
#define MAX_SERVERS 7
struct cfg {
int debug;
const char *server;
const char *port;
const char *servers[MAX_SERVERS];
const char *ports[MAX_SERVERS];
const char *certs;
size_t quorum_size;
};
static const char arg_server[] = "server=";
static const char arg_port[] = "port=";
static const char arg_quorum[] = "quorum_size=";
static const char arg_certs[] = "certs=";
static const char str_ok[] = "OK\n";
......@@ -42,6 +50,7 @@ static const char str_fail[] = "FAIL\n";
static int verify_cb(gnutls_session_t session) {
int err;
// used in debug macros
struct cfg cfg = *(const struct cfg *)gnutls_session_get_ptr(session);
unsigned int status;
if ((err = gnutls_certificate_verify_peers2(session, &status)) < 0)
......@@ -53,7 +62,7 @@ static int verify_cb(gnutls_session_t session) {
if (status != 0)
{
dbgp("cert verify failed");
int type = gnutls_certificate_type_get(session);
gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
gnutls_datum_t status_str;
if ((err = gnutls_certificate_verification_status_print(status, type, &status_str, 0)) < 0)
{
......@@ -71,19 +80,22 @@ static int verify_cb(gnutls_session_t session) {
static int send_full(gnutls_session_t session, const void *data, size_t len)
{
ssize_t sent;
while (len > 0 && (sent = gnutls_record_send(session, data, len)) > 0) {
ssize_t sent = 0;
while (len > 0 && (sent = gnutls_record_send(session, data, len)) > 0)
{
data += sent;
len -= (size_t)sent;
}
if (sent < 0)
{
return (int)sent;
}
return 0;
}
static int check_hotp(struct cfg cfg, const char *user, const char *hotp)
static int check_otp(struct cfg cfg, size_t n_server, const char *user, const char *otp)
{
int err;
......@@ -133,9 +145,9 @@ static int check_hotp(struct cfg cfg, const char *user, const char *hotp)
// resolve name and connect
dbgp("connecting...");
struct addrinfo hint = { .ai_socktype = SOCK_STREAM };
struct addrinfo hint = { .ai_socktype = SOCK_STREAM, .ai_family = AF_UNSPEC };
struct addrinfo *addris_start;
if ((err = getaddrinfo(cfg.server, cfg.port, &hint, &addris_start)) != 0)
if ((err = getaddrinfo(cfg.servers[n_server], cfg.ports[n_server], &hint, &addris_start)) != 0)
{
dbgp(gai_strerror(err));
err = PAM_AUTHINFO_UNAVAIL;
......@@ -175,7 +187,9 @@ static int check_hotp(struct cfg cfg, const char *user, const char *hotp)
do {
err = gnutls_handshake(session);
} while (err < 0 && !gnutls_error_is_fatal(err));
}
while (err < 0 && !gnutls_error_is_fatal(err));
if (err < 0)
{
dbgp("handshake failed");
......@@ -188,7 +202,7 @@ static int check_hotp(struct cfg cfg, const char *user, const char *hotp)
char ln = '\n';
if ((err = send_full(session, user, strlen(user))) < 0
|| (err = send_full(session, &ln, 1)) < 0
|| (err = send_full(session, hotp, strlen(hotp))) < 0
|| (err = send_full(session, otp, strlen(otp))) < 0
|| (err = send_full(session, &ln, 1)) < 0)
{
dbgp2("error in send:", gnutls_strerror(err));
......@@ -196,7 +210,8 @@ static int check_hotp(struct cfg cfg, const char *user, const char *hotp)
goto bye;
}
char buf[5];
// str_fail is the biggest expected response string
char buf[sizeof(str_fail)];
ssize_t recvd;
if ((recvd = gnutls_record_recv(session, &buf, sizeof(buf) - 1)) < 0)
{
......@@ -209,10 +224,14 @@ static int check_hotp(struct cfg cfg, const char *user, const char *hotp)
if (recvd >= strlen(str_ok) && !strncmp(buf, str_ok, strlen(str_ok))) {
dbgp("OK");
err = PAM_SUCCESS;
} else if (recvd >= strlen(str_fail) && !strncmp(buf, str_fail, strlen(str_fail))) {
}
else if (recvd >= strlen(str_fail) && !strncmp(buf, str_fail, strlen(str_fail)))
{
dbgp("FAIL");
err = PAM_AUTH_ERR;
} else {
}
else
{
dbgp("Unexpected response");
err = PAM_AUTHINFO_UNAVAIL;
}
......@@ -236,6 +255,7 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
int err;
struct cfg cfg = { 0 };
size_t n_servers = 0;
for (int i = 0; i < argc; i++)
{
......@@ -243,13 +263,85 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
cfg.debug = 1;
}
else if (!strncmp(argv[i], arg_server, strlen(arg_server)))
else if (!strncmp(argv[i], arg_quorum, strlen(arg_quorum)))
{
cfg.server = &argv[i][strlen(arg_server)];
const char *quorum_param = argv[i] + strlen(arg_quorum);
char *strtol_end = NULL;
errno = 0;
unsigned long quorum_long = strtoul(quorum_param, &strtol_end, 10);
if (errno == ERANGE || quorum_long > SIZE_MAX) {
fprintf(stderr, "Parameter to quorum_size too large\n");
return PAM_AUTHINFO_UNAVAIL;
}
if (quorum_param == strtol_end) {
fprintf(stderr, "Parameter quorum_size requires an argument\n");
}
if (*strtol_end != '\0') {
fprintf(stderr, "Garbage found after parameter quorum_size: %s\n", strtol_end);
}
cfg.quorum_size = quorum_long;
}
else if (!strncmp(argv[i], arg_port, strlen(arg_port)))
else if (!strncmp(argv[i], arg_server, strlen(arg_server)))
{
cfg.port = &argv[i][strlen(arg_port)];
if (n_servers >= MAX_SERVERS)
{
fprintf(stderr, "Too many servers specified (max %i)\n", MAX_SERVERS);
return PAM_AUTHINFO_UNAVAIL;
}
// Duplicate the string so we can use strtok to split address
// and port (pam argv entries are const). Use strdupa to
// allocate on the stack and avoid free() headaches (needs
// _GNU_SOURCE).
char *serverport = strdupa(&argv[i][strlen(arg_server)]);
if (serverport[0] == '\0')
{
continue;
}
// Gotta handle IPv6 addresses differently because they contain colons
if (serverport[0] == '[')
{
char *closing_bracket = strchr(serverport+1, ']');
if (!closing_bracket)
{
dbgp2("Fatal: expecting closing bracket in server address", serverport);
return PAM_AUTHINFO_UNAVAIL;
}
if (closing_bracket[1] != ':')
{
dbgp2("Fatal: expecting :port after closing bracket in server address", serverport);
return PAM_AUTHINFO_UNAVAIL;
}
if (closing_bracket[2] == '\0')
{
dbgp2("Fatal: expecting port after colon in server address", serverport);
return PAM_AUTHINFO_UNAVAIL;
}
closing_bracket[0] = '\0';
cfg.servers[n_servers] = serverport+1;
cfg.ports[n_servers] = closing_bracket+2;
} else {
char *colon = strchr(serverport, ':');
if (!colon)
{
dbgp2("Fatal: need port for", serverport);
return PAM_AUTHINFO_UNAVAIL;
}
if (colon[1] == '\0')
{
dbgp2("Fatal: expecting port after colon in server address", serverport);
return PAM_AUTHINFO_UNAVAIL;
}
colon[0] = '\0';
cfg.servers[n_servers] = serverport;
cfg.ports[n_servers] = colon+1;
}
n_servers++;
}
else if (!strncmp(argv[i], arg_certs, strlen(arg_certs)))
{
......@@ -257,21 +349,20 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
}
}
if (!cfg.server)
if (n_servers == 0)
{
dbgp("Fatal: No server specified");
return PAM_AUTHINFO_UNAVAIL;
}
if (!cfg.port)
{
dbgp("Fatal: No port specified");
return PAM_AUTHINFO_UNAVAIL;
}
if (!cfg.certs)
{
dbgp("Fatal: No certs specified");
return PAM_AUTHINFO_UNAVAIL;
}
// set the default value for quorum size
if (cfg.quorum_size == 0) {
cfg.quorum_size = n_servers/2 + 1;
}
dbgp("retrieving user");
const char *user;
......@@ -292,7 +383,7 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
struct pam_message msg = {
.msg_style = PAM_PROMPT_ECHO_OFF,
.msg = "HOTP: "
.msg = "OTP: "
};
const struct pam_message *msgs = &msg;
......@@ -304,13 +395,25 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
return err;
}
char *hotp = resp[0].resp;
char *otp = resp[0].resp;
free(resp);
err = check_hotp(cfg, user, hotp);
unsigned int acks = 0;
for (size_t i = 0; i < n_servers; i++)
{
err = check_otp(cfg, i, user, otp);
if (err == PAM_SUCCESS)
{
acks++;
}
else if (err == PAM_AUTH_ERR)
{
pam_syslog(pamh, LOG_AUTH | LOG_NOTICE, "goatherd at %s:%s rejected OTP", cfg.servers[i], cfg.ports[i]);
}
}
free(hotp);
return err;
free(otp);
return acks >= cfg.quorum_size ? PAM_SUCCESS : PAM_AUTH_ERR;
}
PAM_EXTERN int
......
{
"db_driver": "postgres"
}