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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ Select [CURL options](https://curl.se/libcurl/c/curl_easy_setopt.html) are avail
* [CURLOPT_CONNECTTIMEOUT](https://curl.se/libcurl/c/CURLOPT_CONNECTTIMEOUT.html)
* [CURLOPT_CONNECTTIMEOUT_MS](https://curl.se/libcurl/c/CURLOPT_CONNECTTIMEOUT_MS.html)
* [CURLOPT_DNS_SERVERS](https://curl.se/libcurl/c/CURLOPT_DNS_SERVERS.html)
* [CURLOPT_HTTPAUTH](https://curl.se/libcurl/c/CURLOPT_HTTPAUTH.html)
* [CURLOPT_PRE_PROXY](https://curl.se/libcurl/c/CURLOPT_PRE_PROXY.html)
* [CURLOPT_PROXY](https://curl.se/libcurl/c/CURLOPT_PROXY.html)
* [CURLOPT_PROXYPASSWORD](https://curl.se/libcurl/c/CURLOPT_PROXYPASSWORD.html)
Expand Down Expand Up @@ -354,6 +355,29 @@ When a timeout occurs during a request, a SQL error will be raised:
ERROR: Operation timed out after 200 milliseconds with 0 bytes received
```

## GSSAPI Auth Passthrough

If the client connection authenticated into postgres via [GSSAPI](https://www.postgresql.org/docs/current/gssapi-auth.html), it is possible for pgsql-http to use the same credentials to authenticate to GSSAPI secured HTTP endpoints. This is similar to how GSSAPI auth pass through works with `postgres_fdw` and `dblink`.

In order to use this functionality [gss_accept_delegation](https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-GSS-ACCEPT-DELEGATION) should be set to `on` the postgres config, the client should set the (gssdelegation)[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSDELEGATION] connection parameter to `1`, and the `pgsql-http` should set `CURLOPT_HTTPAUTH` and `CURLOPT_USERPWD` as shown below.

For example
```
-- gss_accept_delegation should be true in server config
SHOW gss_accept_delegation;

-- gssdelegation was set to 1 in driver
SELECT credentials_delegated FROM pg_stat_gssapi where pid = pg_backend_pid();

-- set curlopts to use GSSAPI
SELECT http_set_curlopt('CURLOPT_HTTPAUTH', 'CURLAUTH_NEGOTIATE');
SELECT http_set_curlopt('CURLOPT_USERPWD', ':');

-- pgsql-http should now be set up for GSSAPI HTTP auth
SELECT *
FROM http_get('http://gssapi_protected_hostname/gssapi_protected_path');
```

## Installation

### Debian / Ubuntu apt.postgresql.org
Expand Down
27 changes: 27 additions & 0 deletions expected/http.out
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,30 @@ SELECT round(extract(epoch FROM now() - start) * 10) AS m
(1 row)

DROP TABLE timer;
SELECT http_set_curlopt('CURLOPT_HTTPAUTH', 'CURLAUTH_NEGOTIATE');
http_set_curlopt
------------------
t
(1 row)

SHOW http.CURLOPT_HTTPAUTH;
http.curlopt_httpauth
-----------------------
CURLAUTH_NEGOTIATE
(1 row)

RESET http.CURLOPT_HTTPAUTH;
SHOW http.CURLOPT_HTTPAUTH;
http.curlopt_httpauth
-----------------------

(1 row)

SELECT http_set_curlopt('CURLOPT_HTTPAUTH', 'DOES_NOT_EXIST');
ERROR: curl httpauth option 'DOES_NOT_EXIST' invalid
SHOW http.CURLOPT_HTTPAUTH;
http.curlopt_httpauth
-----------------------

(1 row)

93 changes: 91 additions & 2 deletions http.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,42 @@ typedef enum {
typedef enum {
CURLOPT_STRING,
CURLOPT_LONG,
CURLOPT_BLOB
CURLOPT_BLOB,
CURLOPT_LONG_BITMASK
} http_curlopt_type;

/* CURLOPT_HTTPAUTH string/enum value mapping */
typedef struct {
char *str;
unsigned long val;
} http_curlopt_auth;

/* CURLOPT_HTTPAUTH values we allow user to set at run-time */
static http_curlopt_auth settable_curlopts_auth[] = {
{ "CURLAUTH_BASIC", CURLAUTH_BASIC },
{ "CURLAUTH_DIGEST", CURLAUTH_DIGEST },
{ "CURLAUTH_NEGOTIATE", CURLAUTH_NEGOTIATE },
{ "CURLAUTH_NTLM", CURLAUTH_NTLM },
{ "CURLAUTH_ANY", CURLAUTH_ANY },
{ "CURLAUTH_ANYSAFE", CURLAUTH_ANYSAFE },
#if LIBCURL_VERSION_NUM >= 0x071303 /* 7.19.3 */
{ "CURLAUTH_DIGEST_IE", CURLAUTH_DIGEST_IE },
#endif
#if LIBCURL_VERSION_NUM >= 0x071503 /* 7.21.3 */
{ "CURLAUTH_ONLY", CURLAUTH_ONLY },
#endif
#if LIBCURL_VERSION_NUM >= 0x071600 /* 7.22.0 */
{ "CURLAUTH_NTLM_WB", CURLAUTH_NTLM_WB },
#endif
#if LIBCURL_VERSION_NUM >= 0x073D00 /* 7.61.0 */
{ "CURLAUTH_BEARER", CURLAUTH_BEARER },
#endif
#if LIBCURL_VERSION_NUM >= 0x074a00 /* 7.74.0 */
{ "CURLAUTH_AWS_SIGV4", CURLAUTH_AWS_SIGV4 },
#endif
{ NULL, 0 },
};

/* CURLOPT string/enum value mapping */
typedef struct {
CURLoption curlopt;
Expand All @@ -156,7 +189,6 @@ typedef struct {
char *curlopt_guc;
} http_curlopt;


/* CURLOPT values we allow user to set at run-time */
/* Be careful adding these, as they can be a security risk */
static http_curlopt settable_curlopts[] = {
Expand All @@ -171,6 +203,9 @@ static http_curlopt settable_curlopts[] = {
#if LIBCURL_VERSION_NUM >= 0x070903 /* 7.9.3 */
{ CURLOPT_SSLCERTTYPE, CURLOPT_STRING, false, "CURLOPT_SSLCERTTYPE", NULL, NULL },
#endif
#if LIBCURL_VERSION_NUM >= 0x070a06 /* 7.10.6 */
{ CURLOPT_HTTPAUTH, CURLOPT_LONG_BITMASK, false, "CURLOPT_HTTPAUTH", NULL, NULL },
#endif
#if LIBCURL_VERSION_NUM >= 0x070e01 /* 7.14.1 */
{ CURLOPT_PROXY, CURLOPT_STRING, false, "CURLOPT_PROXY", NULL, NULL },
{ CURLOPT_PROXYPORT, CURLOPT_LONG, false, "CURLOPT_PROXYPORT", NULL, NULL },
Expand Down Expand Up @@ -957,6 +992,39 @@ set_curlopt(CURL* handle, const http_curlopt *opt)
err = curl_easy_setopt(handle, opt->curlopt, &blob);
elog(DEBUG2, "pgsql-http: set '%s' to value '%s', return value = %d", opt->curlopt_guc, opt->curlopt_val, err);
}
#endif
#if LIBCURL_VERSION_NUM >= 0x070a06 /* 7.10.6 */
/* Only used for CURLOPT_HTTPAUTH */
else if (opt->curlopt_type == CURLOPT_LONG_BITMASK)
{
if (opt->curlopt == CURLOPT_HTTPAUTH)
{
http_curlopt_auth *opt_auth = settable_curlopts_auth;
bool curlopt_httpauth_found = false;
while (opt_auth->str)
{
if (strcasecmp(opt_auth->str, opt->curlopt_val) == 0)
{

err = curl_easy_setopt(handle, opt->curlopt, opt_auth->val);
curlopt_httpauth_found = true;
break;
}

opt_auth++;
}
if (!curlopt_httpauth_found)
{
elog(ERROR, "invalid curl httpauth option, '%s'", opt->curlopt_val);
return false;
}
}
else
{
/* Never get here */
elog(ERROR, "curl option '%d' is not available for run-time configuration", opt->curlopt);
}
}
#endif
else
{
Expand Down Expand Up @@ -1123,6 +1191,27 @@ Datum http_set_curlopt(PG_FUNCTION_ARGS)
{
if (strcasecmp(opt->curlopt_str, curlopt) == 0)
{
if (opt->curlopt == CURLOPT_HTTPAUTH)
{
http_curlopt_auth *opt_auth = settable_curlopts_auth;
bool curlopt_httpauth_found = false;
while (opt_auth->str)
{
if (strcasecmp(opt_auth->str, value) == 0)
{
curlopt_httpauth_found = true;
break;
}
opt_auth++;
}
if (!curlopt_httpauth_found)
{
elog(ERROR, "curl httpauth option '%s' invalid", value);
PG_RETURN_BOOL(false);
}

}

if (opt->curlopt_val) guc_free(opt->curlopt_val);
opt->curlopt_val = guc_strdup(ERROR, value);
PG_RETURN_BOOL(set_curlopt(handle, opt));
Expand Down
12 changes: 12 additions & 0 deletions sql/http.sql
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,15 @@ SELECT *
SELECT round(extract(epoch FROM now() - start) * 10) AS m
FROM timer;
DROP TABLE timer;

SELECT http_set_curlopt('CURLOPT_HTTPAUTH', 'CURLAUTH_NEGOTIATE');

SHOW http.CURLOPT_HTTPAUTH;

RESET http.CURLOPT_HTTPAUTH;

SHOW http.CURLOPT_HTTPAUTH;

SELECT http_set_curlopt('CURLOPT_HTTPAUTH', 'DOES_NOT_EXIST');

SHOW http.CURLOPT_HTTPAUTH;