Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,14 @@ task:
image: debian:oldstable
env:
PGVERSION: 11
- container:
image: ubuntu:22.04
env:
configure_args: '--with-server-gssenc'
setup_script:
- apt-get update
- apt-get -y install curl gnupg lsb-release
- env DEBIAN_FRONTEND=noninteractive apt-get -y install curl gnupg lsb-release krb5-kdc krb5-admin-server krb5-user libkrb5-dev
- ./test/gss/newkdc.sh
- curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
- echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
- apt-get update
Expand Down Expand Up @@ -145,7 +150,7 @@ task:
- image: alpine:latest
setup_script:
- apk update
- apk add autoconf automake build-base libevent-dev libtool openssl openssl-dev pkgconf postgresql python3 wget
- apk add autoconf automake bash build-base libevent-dev libtool openssl openssl-dev pkgconf postgresql python3 wget
- wget -O /tmp/pandoc.tar.gz https://github.com/jgm/pandoc/releases/download/2.10.1/pandoc-2.10.1-linux-amd64.tar.gz
- tar xvzf /tmp/pandoc.tar.gz --strip-components 1 -C /usr/local/
- adduser --disabled-password user
Expand Down
20 changes: 20 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ if test "$with_systemd" = yes; then
AC_SEARCH_LIBS(sd_notify, systemd)
fi

dnl Check for server GSSAPI Encryption support
server_gssenc_support=yes
AC_ARG_WITH(server-gssenc,
AC_HELP_STRING([--with-server-gssenc], [build with server GSSAPI Encryption support]),
[ GSS=
if test "$withval" != no; then
# Look for GSSAPI header and lib
AC_CHECK_HEADERS(gssapi.h, [have_gss_header=y])
AC_SEARCH_LIBS(gss_accept_sec_context, [gssapi_krb5 gssapi], [have_libgss=t])
if test x"${have_gss_header}" != x -a x"${have_libgss}" != x; then
server_gssenc_support=yes
AC_DEFINE(HAVE_SERVER_GSSENC, 1, [Server GSSAPI Encryption support])
else
server_gssenc_support=no
AC_DEFINE(HAVE_SERVER_GSSENC, 0, [Server GSSAPI Encryption support])
fi
fi
], [])

##
## DNS backend
##
Expand Down Expand Up @@ -236,4 +255,5 @@ fi
echo " pam = $pam_support"
echo " systemd = $with_systemd"
echo " tls = $tls_support"
echo " servergssenc = $server_gssenc_support"
echo ""
14 changes: 14 additions & 0 deletions doc/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,20 @@ version 1.3 connections.

Default: `fast`

### server_gssencmode

This option determines whether or with what priority a secure GSS TCP/IP connection will be negotiated with the server. There are three modes:

disable
: only try a non-GSSAPI-encrypted connection

prefer (default)
: if there are GSSAPI credentials present (i.e., in a credentials cache), first try a GSSAPI-encrypted connection; if that fails or there are no credentials, try a non-GSSAPI-encrypted connection. This is the default when pgbouncer has been compiled with GSSAPI support.

require
:only try a GSSAPI-encrypted connection

server_gssencmode is ignored for Unix domain socket communication. If pgbouncer is compiled without GSSAPI support, using the require option will cause an error, while prefer will be accepted but pgbouncer will not actually attempt a GSSAPI-encrypted connection.

## Dangerous timeouts

Expand Down
7 changes: 7 additions & 0 deletions etc/pgbouncer.ini
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ listen_port = 6432
;; fast, normal, secure, legacy, <ciphersuite string>
;server_tls_ciphers = fast

;;;
;;; GSSAPI settings for connecting to backend databases
;;;

;; disable, allow, require
;server_gssencmode = disable

;;;
;;; Authentication settings
;;;
Expand Down
18 changes: 18 additions & 0 deletions include/bouncer.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
#define sd_notifyf(ue, f, ...)
#endif

#ifdef HAVE_GSSAPI_H
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_ext.h>
#include <gssapi/gssapi_krb5.h>
#endif



/* global libevent handle */
extern struct event_base *pgb_event_base;
Expand Down Expand Up @@ -81,6 +88,12 @@ enum SSLMode {
SSLMODE_VERIFY_FULL
};

enum GSSEncMode {
GSSENCMODE_DISABLE,
GSSENCMODE_PREFER,
GSSENCMODE_REQUIRE
};

#define is_server_socket(sk) ((sk)->state >= SV_FREE)


Expand Down Expand Up @@ -349,6 +362,7 @@ struct PgDatabase {
int pool_mode; /* pool mode for this database */
int max_db_connections; /* max server connections between all pools */
char *connect_query; /* startup commands to send to server after connect */
char *gssapi_spn; /* GSSAPI SPN (Service Principal Name) */

struct PktBuf *startup_params; /* partial StartupMessage (without user) be sent to server */
const char *dbname; /* server-side name, pointer to inside startup_msg */
Expand Down Expand Up @@ -409,6 +423,7 @@ struct PgSocket {
bool wait_for_response:1;/* console client: waits for completion of PAUSE/SUSPEND cmd */

bool wait_sslchar:1; /* server: waiting for ssl response: S/N */
bool wait_gssencchar:1; /* server: waiting for gss enc response: G/N */

int expect_rfq_count; /* client: count of ReadyForQuery packets client should see */

Expand Down Expand Up @@ -558,6 +573,9 @@ extern char *cf_server_tls_cert_file;
extern char *cf_server_tls_key_file;
extern char *cf_server_tls_ciphers;

extern int cf_server_gssencmode;


extern const struct CfLookup pool_mode_map[];

extern usec_t g_suspend_start;
Expand Down
3 changes: 3 additions & 0 deletions include/pktbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ void pktbuf_write_ExtQuery(PktBuf *buf, const char *query, int nargs, ...);
#define pktbuf_write_SSLRequest(buf) \
pktbuf_write_generic(buf, PKT_SSLREQ, "")

#define pktbuf_write_GSSEncRequest(buf) \
pktbuf_write_generic(buf, PKT_GSSENCREQ, "")

/*
* Shortcut for creating DataRow in memory.
*/
Expand Down
1 change: 1 addition & 0 deletions include/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ bool welcome_client(PgSocket *client) _MUSTCHECK;
bool answer_authreq(PgSocket *server, PktHdr *pkt) _MUSTCHECK;

bool send_startup_packet(PgSocket *server) _MUSTCHECK;
bool send_gssencreq_packet(PgSocket *server) _MUSTCHECK;
bool send_sslreq_packet(PgSocket *server) _MUSTCHECK;

int scan_text_result(struct MBuf *pkt, const char *tupdesc, ...) _MUSTCHECK;
Expand Down
38 changes: 37 additions & 1 deletion include/sbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ typedef enum {
SBUF_EV_CONNECT_OK, /* got connection */
SBUF_EV_FLUSH, /* data is sent, buffer empty */
SBUF_EV_PKT_CALLBACK, /* next part of pkt data */
SBUF_EV_GSSENC_READY, /* GSSENC was established */
SBUF_EV_TLS_READY /* TLS was established */
} SBufEvent;

Expand All @@ -42,6 +43,13 @@ typedef enum {
*/
#define SBUF_SMALL_PKT 64

#ifdef HAVE_GSSAPI_H

#define GSSENC_WANT_POLLOUT -3
#define GSSENC_WANT_POLLIN -2

#endif

struct tls;

/* fwd def */
Expand All @@ -67,12 +75,15 @@ struct SBufIO {
* Stream is divided to packets. On each packet start
* protocol handler is called that decides what to do.
*/
typedef unsigned int uint32; /* == 32 bits */

struct SBuf {
struct event ev; /* libevent handle */

uint8_t wait_type; /* track wait state */
uint8_t pkt_action; /* method for handling current pkt */
uint8_t tls_state; /* progress of tls */
uint8_t gssenc_state; /* progress of gssenc */

int sock; /* fd for this socket */

Expand All @@ -84,8 +95,27 @@ struct SBuf {

IOBuf *io; /* data buffer, lazily allocated */

const SBufIO *ops; /* normal vs. TLS */
const SBufIO *ops; /* normal vs. TLS vs. GSS */
struct tls *tls; /* TLS context */
struct gss_ctx_id_struct *gss;
#ifdef HAVE_GSSAPI_H
char *gss_SendBuffer; /* Encrypted data waiting to be sent */
int gss_SendLength; /* End of data available in gss_SendBuffer */
int gss_SendNext; /* Next index to send a byte from
* gss_SendBuffer */
int gss_SendConsumed; /* Number of *unencrypted* bytes consumed
* for current contents of gss_SendBuffer */
char *gss_RecvBuffer; /* Received, encrypted data */
int gss_RecvLength; /* End of data available in gss_RecvBuffer */
char *gss_ResultBuffer; /* Decryption of data in gss_RecvBuffer */
int gss_ResultLength; /* End of data available in
* gss_ResultBuffer */
int gss_ResultNext; /* Next index to read a byte from
* gss_ResultBuffer */
uint32 gss_MaxPktSize; /* Maximum size we can encrypt and fit the
* results into our output buffer */
bool write_failed; /* have we had a write failure on sock? */
#endif
const char *tls_host; /* target hostname */
};

Expand All @@ -105,15 +135,21 @@ bool sbuf_connect(SBuf *sbuf, const struct sockaddr *sa, socklen_t sa_len, time_
* usually you should use this variable over cf_client_tls_sslmode.
*/
extern int client_accept_sslmode;

/*
* Same as client_accept_sslmode, but for server connections.
*/
extern int server_connect_sslmode;

extern int server_connect_gssencmode;

bool sbuf_gssenc_connect(SBuf *sbuf, char *gssapi_spn) _MUSTCHECK;
bool sbuf_tls_setup(void);
bool sbuf_tls_accept(SBuf *sbuf) _MUSTCHECK;
bool sbuf_tls_connect(SBuf *sbuf, const char *hostname) _MUSTCHECK;

bool sbuf_gssenc_setup(void);

bool sbuf_pause(SBuf *sbuf) _MUSTCHECK;
void sbuf_continue(SBuf *sbuf);
bool sbuf_close(SBuf *sbuf) _MUSTCHECK;
Expand Down
12 changes: 11 additions & 1 deletion src/admin.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ static bool admin_set(PgSocket *admin, const char *key, const char *val)
if (!sbuf_tls_setup())
pktbuf_write_Notice(buf, "TLS settings could not be applied, still using old configuration");
}
if (strstr(key, "_gss") != NULL) {
if (!sbuf_gssenc_setup())
pktbuf_write_Notice(buf, "GSSENC settings could not be applied, still using old configuration")
}
snprintf(tmp, sizeof(tmp), "SET %s=%s", key, val);
return admin_flush(admin, buf, tmp);
} else {
Expand Down Expand Up @@ -292,7 +296,7 @@ static bool send_one_fd(PgSocket *admin,
msg.msg_iovlen = 1;

/* attach a fd */
if (pga_is_unix(&admin->remote_addr) && admin->own_user && !admin->sbuf.tls) {
if (pga_is_unix(&admin->remote_addr) && admin->own_user && !admin->sbuf.tls && !admin->sbuf.gss) {
msg.msg_control = cntbuf;
msg.msg_controllen = sizeof(cntbuf);

Expand Down Expand Up @@ -341,6 +345,10 @@ static bool show_one_fd(PgSocket *admin, PgSocket *sk)
if (sk->sbuf.tls || (sk->link && sk->link->sbuf.tls))
return true;

/* Skip GSSENC sockets */
if (sk->sbuf.gss || (sk->link && sk->link->sbuf.gss))
return true;

mbuf_init_fixed_reader(&tmp, sk->cancel_key, 8);
if (!mbuf_get_uint64be(&tmp, &ckey))
return false;
Expand Down Expand Up @@ -984,6 +992,8 @@ static bool admin_cmd_reload(PgSocket *admin, const char *arg)
load_config();
if (!sbuf_tls_setup())
log_error("TLS configuration could not be reloaded, keeping old configuration");
if (!sbuf_gssenc_setup())
log_error("GSSENC configuration could not be reloaded, keeping old configuration");
return admin_ready(admin, "RELOAD");
}

Expand Down
1 change: 1 addition & 0 deletions src/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ bool client_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *data)
switch (evtype) {
case SBUF_EV_CONNECT_OK:
case SBUF_EV_CONNECT_FAILED:
case SBUF_EV_GSSENC_READY:
/* ^ those should not happen */
case SBUF_EV_RECV_FAILED:
/*
Expand Down
1 change: 1 addition & 0 deletions src/janitor.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ void kill_database(PgDatabase *db)
if (db->forced_user)
slab_free(user_cache, db->forced_user);
free(db->connect_query);
free(db->gssapi_spn);
if (db->inactive_time) {
statlist_remove(&autodatabase_idle_list, &db->head);
} else {
Expand Down
12 changes: 12 additions & 0 deletions src/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ bool parse_database(void *base, const char *name, const char *connstr)
char *datestyle = NULL;
char *timezone = NULL;
char *connect_query = NULL;
char *gssapi_spn = NULL;
char *appname = NULL;

cv.value_p = &pool_mode;
Expand Down Expand Up @@ -247,6 +248,12 @@ bool parse_database(void *base, const char *name, const char *connstr)
log_error("out of memory");
goto fail;
}
} else if (strcmp("gssapi_spn", key) == 0) {
gssapi_spn = strdup(val);
if (!gssapi_spn) {
log_error("out of memory");
goto fail;
}
} else if (strcmp("application_name", key) == 0) {
appname = val;
} else {
Expand Down Expand Up @@ -286,6 +293,9 @@ bool parse_database(void *base, const char *name, const char *connstr)
} else if (!!connect_query != !!db->connect_query
|| (connect_query && strcmp(connect_query, db->connect_query) != 0)) {
changed = true;
} else if (!!gssapi_spn != !!db->gssapi_spn
|| (gssapi_spn && strcmp(gssapi_spn, db->gssapi_spn) != 0)) {
changed = true;
}
if (changed)
tag_database_dirty(db);
Expand All @@ -301,6 +311,8 @@ bool parse_database(void *base, const char *name, const char *connstr)
db->max_db_connections = max_db_connections;
free(db->connect_query);
db->connect_query = connect_query;
free(db->gssapi_spn);
db->gssapi_spn = gssapi_spn;

if (db->startup_params) {
msg = db->startup_params;
Expand Down
15 changes: 15 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ char *cf_client_tls_ciphers;
char *cf_client_tls_dheparams;
char *cf_client_tls_ecdhecurve;

int cf_server_gssencmode;

int cf_server_tls_sslmode;
char *cf_server_tls_protocols;
char *cf_server_tls_ca_file;
Expand Down Expand Up @@ -222,6 +224,13 @@ const struct CfLookup sslmode_map[] = {
{ NULL }
};

const struct CfLookup gssencmode_map[] = {
{ "disable", GSSENCMODE_DISABLE },
{ "prefer", GSSENCMODE_PREFER },
{ "require", GSSENCMODE_REQUIRE },
{ NULL }
};

/*
* Add new parameters in alphabetical order. This order is used by SHOW CONFIG.
*/
Expand Down Expand Up @@ -280,6 +289,7 @@ CF_ABS("server_check_query", CF_STR, cf_server_check_query, 0, "select 1"),
CF_ABS("server_connect_timeout", CF_TIME_USEC, cf_server_connect_timeout, 0, "15"),
CF_ABS("server_fast_close", CF_INT, cf_server_fast_close, 0, "0"),
CF_ABS("server_idle_timeout", CF_TIME_USEC, cf_server_idle_timeout, 0, "600"),
CF_ABS("server_gssencmode", CF_LOOKUP(gssencmode_map), cf_server_gssencmode, 0, "prefer"), /* libpq default */
CF_ABS("server_lifetime", CF_TIME_USEC, cf_server_lifetime, 0, "3600"),
CF_ABS("server_login_retry", CF_TIME_USEC, cf_server_login_retry, 0, "15"),
CF_ABS("server_reset_query", CF_STR, cf_server_reset_query, 0, "DISCARD ALL"),
Expand Down Expand Up @@ -508,6 +518,8 @@ static void handle_sighup(int sock, short flags, void *arg)
load_config();
if (!sbuf_tls_setup())
log_error("TLS configuration could not be reloaded, keeping old configuration");
if (!sbuf_gssenc_setup())
log_error("GSSENC configuration could not be reloaded, keeping old configuration");
sd_notify(0, "READY=1");
}
#endif
Expand Down Expand Up @@ -943,6 +955,9 @@ int main(int argc, char *argv[])
if (!sbuf_tls_setup())
die("TLS setup failed");

if (!sbuf_gssenc_setup())
die("GSSENC setup failed");

/* prefer cmdline over config for username */
if (arg_username) {
free(cf_username);
Expand Down
Loading